flatware 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +11 -0
- data/lib/flatware/rspec.rb +11 -11
- data/lib/flatware/rspec/checkpoint.rb +1 -1
- data/lib/flatware/rspec/job_builder.rb +110 -0
- data/lib/flatware/sink.rb +1 -1
- data/lib/flatware/version.rb +3 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 81d5bd7d6b9f6e71cf86030bdb1e10e9f699b8e63053b7795573438df8aa1ba9
|
4
|
+
data.tar.gz: 141903472d1d3d2998f1a19fec837fc79dfca9a966996c91ac5b16986e1c4906
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11985434629de54efbb5ad7d1b14ecb27bdd9047a72893a2367d0747e60bc67042e876d3374ab39c85b2c5617312243e92458c62f03ccb372b371e76cacda5ec
|
7
|
+
data.tar.gz: e763a95035658051651a8c44b1a40ea014a9e9f41b8a08faa13c10fd1cfe7985a27771bf9ebc3ba8178b163ddab2847644b5f522a4a889c15baed371897a0c97
|
data/README.md
CHANGED
@@ -73,6 +73,17 @@ To run your entire suite with the default rspec options add the `flatware-rspec`
|
|
73
73
|
$ flatware rspec
|
74
74
|
```
|
75
75
|
|
76
|
+
The rspec runner can balance worker loads, making your suite even faster.
|
77
|
+
|
78
|
+
It forms balaced groups of spec files according to their last run times, if you've set `example_status_persistence_file_path` [in your RSpec config](https://relishapp.com/rspec/rspec-core/v/3-8/docs/command-line/only-failures).
|
79
|
+
|
80
|
+
For this to work the configuration option must be loaded before any specs are run. The `.rspec` file is one way to achive this:
|
81
|
+
|
82
|
+
--require spec_helper
|
83
|
+
|
84
|
+
But beware, if you're using ActiveRecord in your suite you'll need to avoid doing things that cause it to establish a database connection in `spec_helper.rb`. If ActiveRecord connects before flatware forks off workers, each will die messily. All of this will just work if you're following [the recomended pattern of splitting your helpers into `spec_helper` and `rails_helper`](https://github.com/rspec/rspec-rails/blob/v3.8.2/lib/generators/rspec/install/templates/spec/rails_helper.rb).
|
85
|
+
|
86
|
+
|
76
87
|
### Options
|
77
88
|
|
78
89
|
If you'd like to limit the number of forked workers, you can pass the 'w' flag:
|
data/lib/flatware/rspec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rspec/core'
|
2
4
|
require 'rspec/expectations'
|
3
5
|
require 'flatware/rspec/cli'
|
@@ -6,23 +8,21 @@ module Flatware
|
|
6
8
|
module RSpec
|
7
9
|
require 'flatware/rspec/formatters/console'
|
8
10
|
require 'flatware/rspec/formatter'
|
11
|
+
require 'flatware/rspec/job_builder'
|
9
12
|
|
10
13
|
def self.extract_jobs_from_args(args, workers:)
|
14
|
+
JobBuilder.new(args, workers: workers).jobs
|
15
|
+
end
|
11
16
|
|
12
|
-
|
13
|
-
configuration = ::RSpec::Core::Configuration.new
|
14
|
-
def configuration.command() 'rspec' end
|
15
|
-
options.configure(configuration)
|
16
|
-
configuration.files_to_run.uniq.map do |file|
|
17
|
-
Job.new(file, args)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.run(job, options={})
|
17
|
+
def self.run(job, _options = {})
|
22
18
|
runner = ::RSpec::Core::Runner
|
23
19
|
def runner.trap_interrupt() end
|
24
20
|
|
25
|
-
|
21
|
+
args = %w[
|
22
|
+
--format Flatware::RSpec::Formatter
|
23
|
+
] + Array(job)
|
24
|
+
|
25
|
+
runner.run(args, $stderr, $stdout)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flatware
|
4
|
+
module RSpec
|
5
|
+
# groups spec files into one job per worker.
|
6
|
+
# reads from persisted example statuses, if available,
|
7
|
+
# and attempts to ballence the jobs accordingly.
|
8
|
+
class JobBuilder
|
9
|
+
extend Forwardable
|
10
|
+
attr_reader :args, :workers, :configuration
|
11
|
+
def_delegators(
|
12
|
+
:configuration,
|
13
|
+
:files_to_run,
|
14
|
+
:example_status_persistence_file_path
|
15
|
+
)
|
16
|
+
|
17
|
+
def initialize(args, workers:)
|
18
|
+
@args = args
|
19
|
+
@workers = workers
|
20
|
+
|
21
|
+
@configuration = ::RSpec.configuration
|
22
|
+
configuration.define_singleton_method(:command) { 'rspec' }
|
23
|
+
|
24
|
+
::RSpec::Core::ConfigurationOptions.new(args).configure(@configuration)
|
25
|
+
end
|
26
|
+
|
27
|
+
def jobs
|
28
|
+
bucket_count = [files_to_run.size, workers].min
|
29
|
+
|
30
|
+
seconds_per_file = load_persisted_example_statuses
|
31
|
+
.select(&passing)
|
32
|
+
.map(&parse_example)
|
33
|
+
.reduce({}, &sum_by_example_file)
|
34
|
+
|
35
|
+
timed_files, untimed_files = files_to_run
|
36
|
+
.map(&method(:normalize_path))
|
37
|
+
.reduce(
|
38
|
+
[[], []]
|
39
|
+
) do |(timed, untimed), file|
|
40
|
+
if (time = seconds_per_file[file])
|
41
|
+
[timed.append([file, time]), untimed]
|
42
|
+
else
|
43
|
+
[timed, untimed.append(file)]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
balance_by(bucket_count, timed_files, &:last)
|
48
|
+
.map { |bucket| bucket.map(&:first) }
|
49
|
+
.zip(
|
50
|
+
round_robin(bucket_count, untimed_files)
|
51
|
+
).map(&:flatten)
|
52
|
+
.map do |files|
|
53
|
+
Job.new(files, args)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def normalize_path(path)
|
60
|
+
::RSpec::Core::Metadata.relative_path(File.expand_path(path))
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_persisted_example_statuses
|
64
|
+
::RSpec::Core::ExampleStatusPersister.load_from(
|
65
|
+
example_status_persistence_file_path || ''
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def sum_by_example_file
|
70
|
+
lambda do |times, file_name:, seconds:|
|
71
|
+
times.merge(file_name => seconds) { |_, old = 0, new| old + new }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def passing
|
76
|
+
->(status:, **) { status =~ /pass/i }
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse_example
|
80
|
+
lambda do |example_id:, run_time:, **|
|
81
|
+
seconds = run_time.match(/\d+(\.\d+)?/).to_s.to_f
|
82
|
+
file_name = ::RSpec::Core::Example.parse_id(example_id).first
|
83
|
+
{ seconds: seconds, file_name: file_name }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def round_robin(count, items)
|
88
|
+
Array.new(count) { [] }.tap do |groups|
|
89
|
+
items.each_with_index do |entry, i|
|
90
|
+
groups[i % count] << entry
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def balance_by(count, items, &block)
|
96
|
+
# find the group with the smallest sum and add it there
|
97
|
+
Array.new(count) { [] }.tap do |groups|
|
98
|
+
items
|
99
|
+
.sort_by(&block)
|
100
|
+
.reverse
|
101
|
+
.each do |entry|
|
102
|
+
groups.min_by do |group|
|
103
|
+
group.map(&block).reduce(:+) || 0
|
104
|
+
end.push(entry)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/flatware/sink.rb
CHANGED
data/lib/flatware/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flatware
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Dunn
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-06-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi-rzmq
|
@@ -103,6 +103,7 @@ files:
|
|
103
103
|
- lib/flatware/rspec/examples_notification.rb
|
104
104
|
- lib/flatware/rspec/formatter.rb
|
105
105
|
- lib/flatware/rspec/formatters/console.rb
|
106
|
+
- lib/flatware/rspec/job_builder.rb
|
106
107
|
- lib/flatware/rspec/summary.rb
|
107
108
|
- lib/flatware/serialized_exception.rb
|
108
109
|
- lib/flatware/sink.rb
|
@@ -130,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
131
|
version: '0'
|
131
132
|
requirements: []
|
132
133
|
rubyforge_project:
|
133
|
-
rubygems_version: 2.6
|
134
|
+
rubygems_version: 2.7.6
|
134
135
|
signing_key:
|
135
136
|
specification_version: 4
|
136
137
|
summary: A distributed cucumber and rspec runner
|