parallel_tests 2.32.0 → 3.4.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
2
  SHA256:
3
- metadata.gz: f9f48375f06f2320fdda57c5c1340b8aedf1761541d4c802c5a17b6f21cbf858
4
- data.tar.gz: 8e91a9f26aa2710aa023b6d779d7fdf0e768213262d75b7d9969751a3e825089
3
+ metadata.gz: fd8878310c757d2a906bbf5ca62b68e21941cc79472a9833944f88087449c229
4
+ data.tar.gz: d55a5ac186f1c16d9568b8f6bb145e0016bafc1c2fb9748593e96997eaf2034c
5
5
  SHA512:
6
- metadata.gz: 6468e8bbb055db1eaee1a8512cb4226ceeb7acc2ba880d8be30117d863f8b870629088835d2628d28981e9b193a40239646d8a4de8e61467bd0bc48fae50e92e
7
- data.tar.gz: 0dcd9f3aee1aead8329b76e770eb814ef62f465803629fe236d764b6a874eb827458b008aea240689ecadafa02388d49e71291d2c3cca5e74bc24d66137960ce
6
+ metadata.gz: f01821e38506525feaafb8a9ff5a480ab1f6f2b0bed849088d72682a699f82ae50bb2a78b94526f243a65c2474ff8f2d3a06c204b44431a7687d877cbbc91dd8
7
+ data.tar.gz: 2823702fc7e5d047a69c57d3ee4c0b72afbb51603d58e34a3656899d0026a4b749e94494b9edf234fdcc5fd81bae59dfba55f920d777f5f7d1f7c21fb5219552
data/Readme.md CHANGED
@@ -82,6 +82,8 @@ Running things once
82
82
  ===================
83
83
 
84
84
  ```Ruby
85
+ require "parallel_tests"
86
+
85
87
  # preparation:
86
88
  # affected by race-condition: first process may boot slower than the second
87
89
  # either sleep a bit or use a lock for example File.lock
@@ -97,7 +99,6 @@ at_exit do
97
99
  undo_something
98
100
  end
99
101
  end
100
-
101
102
  ```
102
103
 
103
104
  Even test group run-times
@@ -192,7 +193,6 @@ Setup for non-rails
192
193
 
193
194
  Options are:
194
195
  <!-- copy output from bundle exec ./bin/parallel_test -h -->
195
-
196
196
  -n [PROCESSES] How many processes to use, default: available CPUs
197
197
  -p, --pattern [PATTERN] run tests matching this regex pattern
198
198
  --exclude-pattern [PATTERN] exclude tests matching this regex pattern
@@ -205,7 +205,9 @@ Options are:
205
205
  default - runtime when runtime log is filled otherwise filesize
206
206
  -m, --multiply-processes [FLOAT] use given number as a multiplier of processes to run
207
207
  -s, --single [PATTERN] Run all matching files in the same process
208
- -i, --isolate Do not run any other tests in the group used by --single(-s)
208
+ -i, --isolate Do not run any other tests in the group used by --single(-s).
209
+ Automatically turned on if --isolate-n is set above 0.
210
+ --isolate-n Number of processes for isolated groups. Default to 1 when --isolate is on.
209
211
  --only-group INT[, INT]
210
212
  -e, --exec [COMMAND] execute this code parallel and with ENV['TEST_ENV_NUMBER']
211
213
  -o, --test-options '[OPTIONS]' execute test commands with those options
@@ -222,12 +224,14 @@ Options are:
222
224
  --ignore-tags [PATTERN] When counting steps ignore scenarios with tags that match this pattern
223
225
  --nice execute test commands with low priority.
224
226
  --runtime-log [PATH] Location of previously recorded test runtimes
225
- --allowed-missing Allowed percentage of missing runtimes (default = 50)
227
+ --allowed-missing [INT] Allowed percentage of missing runtimes (default = 50)
226
228
  --unknown-runtime [FLOAT] Use given number as unknown runtime (otherwise use average time)
229
+ --first-is-1 Use "1" as TEST_ENV_NUMBER to not reuse the default test environment
230
+ --fail-fast Stop all groups when one group fails (best used with --test-options '--fail-fast' if supported
227
231
  --verbose Print debug output
228
- --verbose-process-command Print the command that will be executed by each process before it begins
229
- --verbose-rerun-command After a process fails, print the command executed by that process
230
- --quiet Print only test output
232
+ --verbose-process-command Displays only the command that will be executed by each process
233
+ --verbose-rerun-command When there are failures, displays the command executed by each process that failed
234
+ --quiet Print only tests output
231
235
  -v, --version Show Version
232
236
  -h, --help Show this.
233
237
 
@@ -257,7 +261,7 @@ TIPS
257
261
  - Use [rspec-retry](https://github.com/NoRedInk/rspec-retry) (not rspec-rerun) to rerun failed tests.
258
262
  - [JUnit formatter configuration](https://github.com/grosser/parallel_tests/wiki#with-rspec_junit_formatter----by-jgarber)
259
263
  - Use [parallel_split_test](https://github.com/grosser/parallel_split_test) to run multiple scenarios in a single spec file, concurrently. (`parallel_tests` [works at the file-level and intends to stay that way](https://github.com/grosser/parallel_tests/issues/747#issuecomment-580216980))
260
-
264
+
261
265
  ### Cucumber
262
266
 
263
267
  - Add a `parallel: foo` profile to your `config/cucumber.yml` and it will be used to run parallel tests
@@ -374,6 +378,7 @@ inspired by [pivotal labs](https://blog.pivotal.io/labs/labs/parallelize-your-rs
374
378
  - [Calaway](https://github.com/calaway)
375
379
  - [alboyadjian](https://github.com/alboyadjian)
376
380
  - [Nathan Broadbent](https://github.com/ndbroadbent)
381
+ - [Vikram B Kumar](https://github.com/v-kumar)
377
382
 
378
383
  [Michael Grosser](http://grosser.it)<br/>
379
384
  michael@grosser.it<br/>
@@ -42,9 +42,10 @@ module ParallelTests
42
42
  Tempfile.open 'parallel_tests-lock' do |lock|
43
43
  ParallelTests.with_pid_file do
44
44
  simulate_output_for_ci options[:serialize_stdout] do
45
- Parallel.map(items, :in_threads => num_processes) do |item|
45
+ Parallel.map(items, in_threads: num_processes) do |item|
46
46
  result = yield(item)
47
47
  reprint_output(result, lock.path) if options[:serialize_stdout]
48
+ ParallelTests.stop_all_processes if options[:fail_fast] && result[:exit_status] != 0
48
49
  result
49
50
  end
50
51
  end
@@ -191,6 +192,12 @@ module ParallelTests
191
192
  options[:isolate] = true
192
193
  end
193
194
 
195
+ opts.on("--isolate-n [PROCESSES]",
196
+ Integer,
197
+ "Use 'isolate' singles with number of processes, default: 1.") do |n|
198
+ options[:isolate_count] = n
199
+ end
200
+
194
201
  opts.on("--only-group INT[, INT]", Array) { |groups| options[:only_group] = groups.map(&:to_i) }
195
202
 
196
203
  opts.on("-e", "--exec [COMMAND]", "execute this code parallel and with ENV['TEST_ENV_NUMBER']") { |path| options[:execute] = path }
@@ -220,6 +227,7 @@ module ParallelTests
220
227
  opts.on("--allowed-missing [INT]", Integer, "Allowed percentage of missing runtimes (default = 50)") { |percent| options[:allowed_missing_percent] = percent }
221
228
  opts.on("--unknown-runtime [FLOAT]", Float, "Use given number as unknown runtime (otherwise use average time)") { |time| options[:unknown_runtime] = time }
222
229
  opts.on("--first-is-1", "Use \"1\" as TEST_ENV_NUMBER to not reuse the default test environment") { options[:first_is_1] = true }
230
+ opts.on("--fail-fast", "Stop all groups when one group fails (best used with --test-options '--fail-fast' if supported") { options[:fail_fast] = true }
223
231
  opts.on("--verbose", "Print debug output") { options[:verbose] = true }
224
232
  opts.on("--verbose-process-command", "Displays only the command that will be executed by each process") { options[:verbose_process_command] = true }
225
233
  opts.on("--verbose-rerun-command", "When there are failures, displays the command executed by each process that failed") { options[:verbose_rerun_command] = true }
@@ -316,9 +324,8 @@ module ParallelTests
316
324
  end
317
325
 
318
326
  def final_fail_message
319
- fail_message = "#{@runner.name}s Failed"
327
+ fail_message = "Tests Failed"
320
328
  fail_message = "\e[31m#{fail_message}\e[0m" if use_colors?
321
-
322
329
  fail_message
323
330
  end
324
331
 
@@ -0,0 +1,31 @@
1
+ begin
2
+ gem "cuke_modeler", "~> 3.0"
3
+ require 'cuke_modeler'
4
+ rescue LoadError
5
+ raise 'Grouping by number of cucumber steps requires the `cuke_modeler` modeler gem with requirement `~> 3.0`. Add `gem "cuke_modeler", "~> 3.0"` to your `Gemfile`, run `bundle install` and try again.'
6
+ end
7
+
8
+ module ParallelTests
9
+ module Cucumber
10
+ class FeaturesWithSteps
11
+ class << self
12
+ def all(tests, options)
13
+ ignore_tag_pattern = options[:ignore_tag_pattern].nil? ? nil : Regexp.compile(options[:ignore_tag_pattern])
14
+ # format of hash will be FILENAME => NUM_STEPS
15
+ steps_per_file = tests.each_with_object({}) do |file,steps|
16
+ feature = ::CukeModeler::FeatureFile.new(file).feature
17
+
18
+ # skip feature if it matches tag regex
19
+ next if feature.tags.grep(ignore_tag_pattern).any?
20
+
21
+ # count the number of steps in the file
22
+ # will only include a feature if the regex does not match
23
+ all_steps = feature.scenarios.map{|a| a.steps.count if a.tags.grep(ignore_tag_pattern).empty? }.compact
24
+ steps[file] = all_steps.inject(0,:+)
25
+ end
26
+ steps_per_file.sort_by { |_, value| -value }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,37 +1,33 @@
1
- require 'cucumber/tag_expressions/parser'
2
- require 'cucumber/core/gherkin/tag_expression'
3
-
4
1
  module ParallelTests
5
2
  module Cucumber
6
3
  module Formatters
7
4
  class ScenarioLineLogger
8
5
  attr_reader :scenarios
9
6
 
10
- def initialize(tag_expression = ::Cucumber::Core::Gherkin::TagExpression.new([]))
7
+ def initialize(tag_expression = nil)
11
8
  @scenarios = []
12
9
  @tag_expression = tag_expression
13
10
  end
14
11
 
15
12
  def visit_feature_element(uri, feature_element, feature_tags, line_numbers: [])
16
- scenario_tags = feature_element[:tags].map { |tag| tag[:name] }
13
+ scenario_tags = feature_element.tags.map { |tag| tag.name }
17
14
  scenario_tags = feature_tags + scenario_tags
18
- if feature_element[:examples].nil? # :Scenario
19
- test_line = feature_element[:location][:line]
15
+ if feature_element.is_a?(CukeModeler::Scenario) # :Scenario
16
+ test_line = feature_element.source_line
20
17
 
21
18
  # We don't accept the feature_element if the current tags are not valid
22
- return unless @tag_expression.evaluate(scenario_tags)
19
+ return unless matches_tags?(scenario_tags)
23
20
  # or if it is not at the correct location
24
21
  return if line_numbers.any? && !line_numbers.include?(test_line)
25
22
 
26
- @scenarios << [uri, feature_element[:location][:line]].join(":")
23
+ @scenarios << [uri, feature_element.source_line].join(":")
27
24
  else # :ScenarioOutline
28
- feature_element[:examples].each do |example|
29
- example_tags = example[:tags].map { |tag| tag[:name] }
25
+ feature_element.examples.each do |example|
26
+ example_tags = example.tags.map(&:name)
30
27
  example_tags = scenario_tags + example_tags
31
- next unless @tag_expression.evaluate(example_tags)
32
- rows = example[:tableBody].select { |body| body[:type] == :TableRow }
33
- rows.each do |row|
34
- test_line = row[:location][:line]
28
+ next unless matches_tags?(example_tags)
29
+ example.rows[1..-1].each do |row|
30
+ test_line = row.source_line
35
31
  next if line_numbers.any? && !line_numbers.include?(test_line)
36
32
 
37
33
  @scenarios << [uri, test_line].join(':')
@@ -42,6 +38,12 @@ module ParallelTests
42
38
 
43
39
  def method_missing(*args)
44
40
  end
41
+
42
+ private
43
+
44
+ def matches_tags?(tags)
45
+ @tag_expression.nil? || @tag_expression.evaluate(tags)
46
+ end
45
47
  end
46
48
  end
47
49
  end
@@ -1,12 +1,17 @@
1
1
  require 'cucumber/tag_expressions/parser'
2
- require 'cucumber/core/gherkin/tag_expression'
3
2
  require 'cucumber/runtime'
4
3
  require 'cucumber'
5
4
  require 'parallel_tests/cucumber/scenario_line_logger'
6
5
  require 'parallel_tests/gherkin/listener'
7
- require 'gherkin/errors'
8
6
  require 'shellwords'
9
7
 
8
+ begin
9
+ gem "cuke_modeler", "~> 3.0"
10
+ require 'cuke_modeler'
11
+ rescue LoadError
12
+ raise 'Grouping by individual cucumber scenarios requires the `cuke_modeler` modeler gem with requirement `~> 3.0`. Add `gem "cuke_modeler", "~> 3.0"` to your `Gemfile`, run `bundle install` and try again.'
13
+ end
14
+
10
15
  module ParallelTests
11
16
  module Cucumber
12
17
  class Scenarios
@@ -40,32 +45,17 @@ module ParallelTests
40
45
  path, *test_lines = path.split(/:(?=\d+)/)
41
46
  test_lines.map!(&:to_i)
42
47
 
43
- # We encode the file and get the content of it
44
- source = ::Cucumber::Runtime::NormalisedEncodingFile.read(path)
45
48
  # We create a Gherkin document, this will be used to decode the details of each scenario
46
- document = ::Cucumber::Core::Gherkin::Document.new(path, source)
47
-
48
- # We create a parser for the gherkin document
49
- parser = ::Gherkin::Parser.new()
50
- scanner = ::Gherkin::TokenScanner.new(document.body)
51
-
52
- begin
53
- # We make an attempt to parse the gherkin document, this could be failed if the document is not well formatted
54
- result = parser.parse(scanner)
55
- feature_tags = result[:feature][:tags].map { |tag| tag[:name] }
56
-
57
- # We loop on each children of the feature
58
- result[:feature][:children].each do |feature_element|
59
- # If the type of the child is not a scenario or scenario outline, we continue, we are only interested by the name of the scenario here
60
- next unless /Scenario/.match(feature_element[:type])
49
+ document = ::CukeModeler::FeatureFile.new(path)
50
+ feature = document.feature
61
51
 
62
- # It's a scenario, we add it to the scenario_line_logger
63
- scenario_line_logger.visit_feature_element(document.uri, feature_element, feature_tags, line_numbers: test_lines)
64
- end
52
+ # We make an attempt to parse the gherkin document, this could be failed if the document is not well formatted
53
+ feature_tags = feature.tags.map(&:name)
65
54
 
66
- rescue StandardError => e
67
- # Exception if the document is no well formated or error in the tags
68
- raise ::Cucumber::Core::Gherkin::ParseError.new("#{document.uri}: #{e.message}")
55
+ # We loop on each children of the feature
56
+ feature.tests.each do |test|
57
+ # It's a scenario, we add it to the scenario_line_logger
58
+ scenario_line_logger.visit_feature_element(document.path, test, feature_tags, line_numbers: test_lines)
69
59
  end
70
60
  end
71
61
 
@@ -1,5 +1,3 @@
1
- require 'gherkin/parser'
2
-
3
1
  module ParallelTests
4
2
  module Gherkin
5
3
  class Listener
@@ -14,7 +14,7 @@ module ParallelTests
14
14
  end
15
15
 
16
16
  config.on_event :test_case_finished do |event|
17
- @example_times[event.test_case.feature.file] += ParallelTests.now.to_f - @start_at
17
+ @example_times[event.test_case.location.file] += ParallelTests.now.to_f - @start_at
18
18
  end
19
19
 
20
20
  config.on_event :test_run_finished do |_|
@@ -2,7 +2,7 @@ module ParallelTests
2
2
  class Grouper
3
3
  class << self
4
4
  def by_steps(tests, num_groups, options)
5
- features_with_steps = build_features_with_steps(tests, options)
5
+ features_with_steps = group_by_features_with_steps(tests, options)
6
6
  in_even_groups_by_size(features_with_steps, num_groups)
7
7
  end
8
8
 
@@ -15,19 +15,46 @@ module ParallelTests
15
15
  groups = Array.new(num_groups) { {:items => [], :size => 0} }
16
16
 
17
17
  # add all files that should run in a single process to one group
18
- (options[:single_process] || []).each do |pattern|
19
- matched, items = items.partition { |item, _size| item =~ pattern }
20
- matched.each { |item, size| add_to_group(groups.first, item, size) }
18
+ single_process_patterns = options[:single_process] || []
19
+
20
+ single_items, items = items.partition do |item, _size|
21
+ single_process_patterns.any? { |pattern| item =~ pattern }
21
22
  end
22
23
 
23
- groups_to_fill = (options[:isolate] ? groups[1..-1] : groups)
24
- group_features_by_size(items_to_group(items), groups_to_fill)
24
+ isolate_count = isolate_count(options)
25
+
26
+ if isolate_count >= num_groups
27
+ raise 'Number of isolated processes must be less than total the number of processes'
28
+ end
29
+
30
+ if isolate_count >= 1
31
+ # add all files that should run in a multiple isolated processes to their own groups
32
+ group_features_by_size(items_to_group(single_items), groups[0..(isolate_count - 1)])
33
+ # group the non-isolated by size
34
+ group_features_by_size(items_to_group(items), groups[isolate_count..-1])
35
+ else
36
+ # add all files that should run in a single non-isolated process to first group
37
+ single_items.each { |item, size| add_to_group(groups.first, item, size) }
38
+
39
+ # group all by size
40
+ group_features_by_size(items_to_group(items), groups)
41
+ end
25
42
 
26
43
  groups.map! { |g| g[:items].sort }
27
44
  end
28
45
 
29
46
  private
30
47
 
48
+ def isolate_count(options)
49
+ if options[:isolate_count] && options[:isolate_count] > 1
50
+ options[:isolate_count]
51
+ elsif options[:isolate]
52
+ 1
53
+ else
54
+ 0
55
+ end
56
+ end
57
+
31
58
  def largest_first(files)
32
59
  files.sort_by{|_item, size| size }.reverse
33
60
  end
@@ -41,23 +68,9 @@ module ParallelTests
41
68
  group[:size] += size
42
69
  end
43
70
 
44
- def build_features_with_steps(tests, options)
45
- require 'gherkin/parser'
46
- ignore_tag_pattern = options[:ignore_tag_pattern].nil? ? nil : Regexp.compile(options[:ignore_tag_pattern])
47
- parser = ::Gherkin::Parser.new
48
- # format of hash will be FILENAME => NUM_STEPS
49
- steps_per_file = tests.each_with_object({}) do |file,steps|
50
- feature = parser.parse(File.read(file)).fetch(:feature)
51
-
52
- # skip feature if it matches tag regex
53
- next if feature[:tags].grep(ignore_tag_pattern).any?
54
-
55
- # count the number of steps in the file
56
- # will only include a feature if the regex does not match
57
- all_steps = feature[:children].map{|a| a[:steps].count if a[:tags].grep(ignore_tag_pattern).empty? }.compact
58
- steps[file] = all_steps.inject(0,:+)
59
- end
60
- steps_per_file.sort_by { |_, value| -value }
71
+ def group_by_features_with_steps(tests, options)
72
+ require 'parallel_tests/cucumber/features_with_steps'
73
+ ParallelTests::Cucumber::FeaturesWithSteps.all(tests, options)
61
74
  end
62
75
 
63
76
  def group_by_scenarios(tests, options={})
@@ -4,8 +4,6 @@ module ParallelTests
4
4
  module RSpec
5
5
  class Runner < ParallelTests::Test::Runner
6
6
  DEV_NULL = (WINDOWS ? "NUL" : "/dev/null")
7
- NAME = 'RSpec'
8
-
9
7
  class << self
10
8
  def run_tests(test_files, process_number, num_processes, options)
11
9
  exe = executable # expensive, so we cache
@@ -50,6 +48,22 @@ module ParallelTests
50
48
  "#{clean} --seed #{seed}"
51
49
  end
52
50
 
51
+ # Summarize results from threads and colorize results based on failure and pending counts.
52
+ #
53
+ def summarize_results(results)
54
+ text = super
55
+ return text unless $stdout.tty?
56
+ sums = sum_up_results(results)
57
+ color =
58
+ if sums['failure'].positive?
59
+ 31 # red
60
+ elsif sums['pending'].positive?
61
+ 33 # yellow
62
+ else
63
+ 32 # green
64
+ end
65
+ "\e[#{color}m#{text}\e[0m"
66
+ end
53
67
 
54
68
  private
55
69
 
@@ -5,7 +5,7 @@ module ParallelTests
5
5
  module Tasks
6
6
  class << self
7
7
  def rails_env
8
- ENV['RAILS_ENV'] || 'test'
8
+ 'test'
9
9
  end
10
10
 
11
11
  def rake_bin
@@ -3,15 +3,9 @@ require 'parallel_tests'
3
3
  module ParallelTests
4
4
  module Test
5
5
  class Runner
6
- NAME = 'Test'
7
-
8
6
  class << self
9
7
  # --- usually overwritten by other runners
10
8
 
11
- def name
12
- NAME
13
- end
14
-
15
9
  def runtime_log
16
10
  'tmp/parallel_runtime_test.log'
17
11
  end
@@ -179,7 +173,7 @@ module ParallelTests
179
173
  allowed_missing -= 1 unless time = runtimes[test]
180
174
  if allowed_missing < 0
181
175
  log = options[:runtime_log] || runtime_log
182
- raise "Runtime log file '#{log}' does not contain sufficient data to sort #{tests.size} test files, please update it."
176
+ raise "Runtime log file '#{log}' does not contain sufficient data to sort #{tests.size} test files, please update or remove it."
183
177
  end
184
178
  [test, time]
185
179
  end
@@ -1,3 +1,3 @@
1
1
  module ParallelTests
2
- VERSION = Version = '2.32.0'
2
+ VERSION = Version = '3.4.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parallel_tests
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.32.0
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-15 00:00:00.000000000 Z
11
+ date: 2020-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parallel
@@ -42,6 +42,7 @@ files:
42
42
  - lib/parallel_tests.rb
43
43
  - lib/parallel_tests/cli.rb
44
44
  - lib/parallel_tests/cucumber/failures_logger.rb
45
+ - lib/parallel_tests/cucumber/features_with_steps.rb
45
46
  - lib/parallel_tests/cucumber/runner.rb
46
47
  - lib/parallel_tests/cucumber/scenario_line_logger.rb
47
48
  - lib/parallel_tests/cucumber/scenarios.rb
@@ -67,8 +68,8 @@ licenses:
67
68
  - MIT
68
69
  metadata:
69
70
  bug_tracker_uri: https://github.com/grosser/parallel_tests/issues
70
- documentation_uri: https://github.com/grosser/parallel_tests/blob/v2.32.0/Readme.md
71
- source_code_uri: https://github.com/grosser/parallel_tests/tree/v2.32.0
71
+ documentation_uri: https://github.com/grosser/parallel_tests/blob/v3.4.0/Readme.md
72
+ source_code_uri: https://github.com/grosser/parallel_tests/tree/v3.4.0
72
73
  wiki_uri: https://github.com/grosser/parallel_tests/wiki
73
74
  post_install_message:
74
75
  rdoc_options: []
@@ -78,14 +79,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
78
79
  requirements:
79
80
  - - ">="
80
81
  - !ruby/object:Gem::Version
81
- version: 2.2.0
82
+ version: 2.4.0
82
83
  required_rubygems_version: !ruby/object:Gem::Requirement
83
84
  requirements:
84
85
  - - ">="
85
86
  - !ruby/object:Gem::Version
86
87
  version: '0'
87
88
  requirements: []
88
- rubygems_version: 3.0.3
89
+ rubygems_version: 3.1.3
89
90
  signing_key:
90
91
  specification_version: 4
91
92
  summary: Run Test::Unit / RSpec / Cucumber / Spinach in parallel