flatware 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|