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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0ff7cc506cc64f0d64a8892435f946ec19551461
4
- data.tar.gz: e27ffb2ff50e1547a03e2c8f24c8a2c1f6220abe
2
+ SHA256:
3
+ metadata.gz: 81d5bd7d6b9f6e71cf86030bdb1e10e9f699b8e63053b7795573438df8aa1ba9
4
+ data.tar.gz: 141903472d1d3d2998f1a19fec837fc79dfca9a966996c91ac5b16986e1c4906
5
5
  SHA512:
6
- metadata.gz: df21484a242be9f3e979c53bd38830c1e824e045a966d2e66023bd9f56c43ea64ed7a1c6e365206ea4bfb59e2440ce6654520eaf1be55f25f463119e89306b2d
7
- data.tar.gz: c6ce36dc2f5472e7abc59eee306cc1afc82dcae1a6c4fba5844a82a1501f3f3d036d1bb8ba2aba99d0faa7d5eb07f2280deb1bd8097f0953a574c874371581ad
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:
@@ -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
- options = ::RSpec::Core::ConfigurationOptions.new(args)
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
- runner.run(%w[--format Flatware::RSpec::Formatter] + Array(job), $stderr, $stdout)
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
@@ -14,7 +14,7 @@ module Flatware
14
14
  end
15
15
 
16
16
  def failures?
17
- summary.failure_count > 0
17
+ summary.failure_count > 0 || summary.errors_outside_of_examples_count > 0
18
18
  end
19
19
 
20
20
  def failure_notifications
@@ -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
@@ -98,7 +98,7 @@ module Flatware
98
98
  jobs.group_by.with_index do |_,i|
99
99
  i % worker_count
100
100
  end.values.map do |jobs|
101
- Job.new(jobs.map(&:id), jobs.first.args)
101
+ Job.new(jobs.map(&:id).flatten, jobs.first.args)
102
102
  end
103
103
  end
104
104
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Flatware
2
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
3
5
  end
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.0.0
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: 2018-08-28 00:00:00.000000000 Z
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.12
134
+ rubygems_version: 2.7.6
134
135
  signing_key:
135
136
  specification_version: 4
136
137
  summary: A distributed cucumber and rspec runner