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 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