parallel_tests 4.4.0 → 4.7.1

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: cf9a4f09819ff1193b0f02d9148f72d7b12e4c2efc2ca4e5e7981eb6959ebfef
4
- data.tar.gz: f86bdeb95cb93b8d8d79a81727e719014a481c18520da973480ff44d6828ffdb
3
+ metadata.gz: b091de564b1cd9f2c57b94dd973bceea01f7722e4c6bafbe71e6b75c7a265421
4
+ data.tar.gz: bf14a02a03bef8046e2973a904701f03b10bdae1ef8a84654511c18631efe179
5
5
  SHA512:
6
- metadata.gz: 549401be10742522594fbd9f9ba1eb657c7689c13be3f9034bb7ee78225a7b86cf127b83d1f16cac932b639cfec92ecac9644e8aff9aa488b39e3980eb23ff6c
7
- data.tar.gz: ad909f442e744a869bc4db59b9bf2bc9b3559e888f2d017cee58fa51b4d8563f5dc9123d6ce48317fe65f56e7d6f4f4d89c56274960d14ec1befbe5e3cabcc2d
6
+ metadata.gz: d4d7df8aed6d01ba8a9b98064a4c88ad7b88270884d9b5f24c34c2094772974c81fef357c9534e97265e57603b780c9b61af4b1a70a32ae6891ba87256633dfd
7
+ data.tar.gz: a0f0e19e7d02d3046f41ebe8c51a72056bd840d25b54947564b5b015df8946f0e4b0c86920b3cabca7ff1a82f29dcbb44e53daeaf92ae3721de29b6388c92fe0
data/Readme.md CHANGED
@@ -33,35 +33,45 @@ test:
33
33
  ### Create additional database(s)
34
34
  rake parallel:create
35
35
 
36
+ ### (Multi-DB) Create individual database
37
+ rake parallel:create:<database>
38
+ rake parallel:create:secondary
39
+
36
40
  ### Copy development schema (repeat after migrations)
37
41
  rake parallel:prepare
38
42
 
39
43
  ### Run migrations in additional database(s) (repeat after migrations)
40
44
  rake parallel:migrate
41
45
 
46
+ ### (Multi-DB) Run migrations in individual database
47
+ rake parallel:migrate:<database>
48
+
42
49
  ### Setup environment from scratch (create db and loads schema, useful for CI)
43
50
  rake parallel:setup
44
51
 
45
52
  ### Drop all test databases
46
53
  rake parallel:drop
47
54
 
55
+ ### (Multi-DB) Drop individual test database
56
+ rake parallel:drop:<database>
57
+
48
58
  ### Run!
49
59
  rake parallel:test # Minitest
50
60
  rake parallel:spec # RSpec
51
61
  rake parallel:features # Cucumber
52
62
  rake parallel:features-spinach # Spinach
53
63
 
54
- rake parallel:test[1] --> force 1 CPU --> 86 seconds
64
+ rake "parallel:test[1]" --> force 1 CPU --> 86 seconds
55
65
  rake parallel:test --> got 2 CPUs? --> 47 seconds
56
66
  rake parallel:test --> got 4 CPUs? --> 26 seconds
57
67
  ...
58
68
 
59
69
  Test by pattern with Regex (e.g. use one integration server per subfolder / see if you broke any 'user'-related tests)
60
70
 
61
- rake parallel:test[^test/unit] # every test file in test/unit folder
62
- rake parallel:test[user] # run users_controller + user_helper + user tests
63
- rake parallel:test['user|product'] # run user and product related tests
64
- rake parallel:spec['spec\/(?!features)'] # run RSpec tests except the tests in spec/features
71
+ rake "parallel:test[^test/unit]" # every test file in test/unit folder
72
+ rake "parallel:test[user]" # run users_controller + user_helper + user tests
73
+ rake "parallel:test['user|product']" # run user and product related tests
74
+ rake "parallel:spec['spec\/(?!features)']" # run RSpec tests except the tests in spec/features
65
75
 
66
76
 
67
77
  ### Example output
@@ -77,9 +87,9 @@ Test by pattern with Regex (e.g. use one integration server per subfolder / see
77
87
  ```Bash
78
88
  RAILS_ENV=test parallel_test -e "rake my:custom:task"
79
89
  # or
80
- rake parallel:rake[my:custom:task]
90
+ rake "parallel:rake[my:custom:task]"
81
91
  # limited parallelism
82
- rake parallel:rake[my:custom:task,2]
92
+ rake "parallel:rake[my:custom:task,2]"
83
93
  ```
84
94
 
85
95
 
@@ -240,8 +250,9 @@ Options are:
240
250
  -m, --multiply-processes [FLOAT] use given number as a multiplier of processes to run
241
251
  -s, --single [PATTERN] Run all matching files in the same process
242
252
  -i, --isolate Do not run any other tests in the group used by --single(-s)
243
- --isolate-n [PROCESSES] Use 'isolate' singles with number of processes, default: 1.
253
+ --isolate-n [PROCESSES] Use 'isolate' singles with number of processes, default: 1
244
254
  --highest-exit-status Exit with the highest exit status provided by test run(s)
255
+ --failure-exit-code [INT] Specify the exit code to use when tests fail
245
256
  --specify-groups [SPECS] Use 'specify-groups' if you want to specify multiple specs running in multiple
246
257
  processes in a specific formation. Commas indicate specs in the same process,
247
258
  pipes indicate specs in a new process. Cannot use with --single, --isolate, or
@@ -250,9 +261,8 @@ Options are:
250
261
  Process 1 will contain 1_spec.rb and 2_spec.rb
251
262
  Process 2 will contain 3_spec.rb
252
263
  Process 3 will contain all other specs
253
- --only-group INT[,INT] Only run the given group numbers. Note that this will force the 'filesize'
254
- grouping strategy (even when the runtime log is present) unless you explicitly
255
- set it otherwise via the '-group-by' flag.
264
+ --only-group INT[,INT] Only run the given group numbers.
265
+ Changes `--group-by` default to 'filesize'.
256
266
  -e, --exec [COMMAND] execute this code parallel and with ENV['TEST_ENV_NUMBER']
257
267
  -o, --test-options '[OPTIONS]' execute test commands with those options
258
268
  -t, --type [TYPE] test(default) / rspec / cucumber / spinach
@@ -269,11 +279,14 @@ Options are:
269
279
  --nice execute test commands with low priority.
270
280
  --runtime-log [PATH] Location of previously recorded test runtimes
271
281
  --allowed-missing [INT] Allowed percentage of missing runtimes (default = 50)
282
+ --allow-duplicates When detecting files to run, allow duplicates
272
283
  --unknown-runtime [FLOAT] Use given number as unknown runtime (otherwise use average time)
273
284
  --first-is-1 Use "1" as TEST_ENV_NUMBER to not reuse the default test environment
274
285
  --fail-fast Stop all groups when one group fails (best used with --test-options '--fail-fast' if supported
275
286
  --verbose Print debug output
276
- --verbose-command Displays the command that will be executed by each process and when there are failures displays the command executed by each process that failed
287
+ --verbose-command Combines options --verbose-process-command and --verbose-rerun-command
288
+ --verbose-process-command Print the command that will be executed by each process before it begins
289
+ --verbose-rerun-command After a process fails, print the command executed by that process
277
290
  --quiet Print only tests output
278
291
  -v, --version Show Version
279
292
  -h, --help Show this.
@@ -427,6 +440,9 @@ inspired by [pivotal labs](https://blog.pivotal.io/labs/labs/parallelize-your-rs
427
440
  - [Jon Dufresne](https://github.com/jdufresne)
428
441
  - [Eric Kessler](https://github.com/enkessler)
429
442
  - [Adis Osmonov](https://github.com/adis-io)
443
+ - [Josh Westbrook](https://github.com/joshwestbrook)
444
+ - [Jay Dorsey](https://github.com/jaydorsey)
445
+ - [hatsu](https://github.com/hatsu38)
430
446
 
431
447
  [Michael Grosser](http://grosser.it)<br/>
432
448
  michael@grosser.it<br/>
@@ -57,8 +57,8 @@ module ParallelTests
57
57
  Tempfile.open 'parallel_tests-lock' do |lock|
58
58
  ParallelTests.with_pid_file do
59
59
  simulate_output_for_ci options[:serialize_stdout] do
60
- Parallel.map(items, in_threads: num_processes) do |item|
61
- result = yield(item)
60
+ Parallel.map_with_index(items, in_threads: num_processes) do |item, index|
61
+ result = yield(item, index)
62
62
  reprint_output(result, lock.path) if options[:serialize_stdout]
63
63
  ParallelTests.stop_all_processes if options[:fail_fast] && result[:exit_status] != 0
64
64
  result
@@ -81,8 +81,8 @@ module ParallelTests
81
81
  end
82
82
 
83
83
  report_number_of_tests(groups) unless options[:quiet]
84
- test_results = execute_in_parallel(groups, groups.size, options) do |group|
85
- run_tests(group, groups.index(group), num_processes, options)
84
+ test_results = execute_in_parallel(groups, groups.size, options) do |group, index|
85
+ run_tests(group, index, num_processes, options)
86
86
  end
87
87
  report_results(test_results, options) unless options[:quiet]
88
88
  end
@@ -96,8 +96,9 @@ module ParallelTests
96
96
  if any_test_failed?(test_results)
97
97
  warn final_fail_message
98
98
 
99
- # return the highest exit status to allow sub-processes to send things other than 1
100
- exit_status = if options[:highest_exit_status]
99
+ exit_status = if options[:failure_exit_code]
100
+ options[:failure_exit_code]
101
+ elsif options[:highest_exit_status]
101
102
  test_results.map { |data| data.fetch(:exit_status) }.max
102
103
  else
103
104
  1
@@ -145,7 +146,7 @@ module ParallelTests
145
146
  failing_sets = test_results.reject { |r| r[:exit_status] == 0 }
146
147
  return if failing_sets.none?
147
148
 
148
- if options[:verbose] || options[:verbose_command]
149
+ if options[:verbose] || options[:verbose_rerun_command]
149
150
  puts "\n\nTests have failed for a parallel_test group. Use the following command to run the group again:\n\n"
150
151
  failing_sets.each do |failing_set|
151
152
  command = failing_set[:command]
@@ -223,12 +224,19 @@ module ParallelTests
223
224
  opts.on(
224
225
  "--isolate-n [PROCESSES]",
225
226
  Integer,
226
- "Use 'isolate' singles with number of processes, default: 1."
227
+ "Use 'isolate' singles with number of processes, default: 1"
227
228
  ) { |n| options[:isolate_count] = n }
228
229
 
229
- opts.on("--highest-exit-status", "Exit with the highest exit status provided by test run(s)") do
230
- options[:highest_exit_status] = true
231
- end
230
+ opts.on(
231
+ "--highest-exit-status",
232
+ "Exit with the highest exit status provided by test run(s)"
233
+ ) { options[:highest_exit_status] = true }
234
+
235
+ opts.on(
236
+ "--failure-exit-code [INT]",
237
+ Integer,
238
+ "Specify the exit code to use when tests fail"
239
+ ) { |code| options[:failure_exit_code] = code }
232
240
 
233
241
  opts.on(
234
242
  "--specify-groups [SPECS]",
@@ -237,7 +245,7 @@ module ParallelTests
237
245
  processes in a specific formation. Commas indicate specs in the same process,
238
246
  pipes indicate specs in a new process. Cannot use with --single, --isolate, or
239
247
  --isolate-n. Ex.
240
- $ parallel_test -n 3 . --specify-groups '1_spec.rb,2_spec.rb|3_spec.rb'
248
+ $ parallel_tests -n 3 . --specify-groups '1_spec.rb,2_spec.rb|3_spec.rb'
241
249
  Process 1 will contain 1_spec.rb and 2_spec.rb
242
250
  Process 2 will contain 3_spec.rb
243
251
  Process 3 will contain all other specs
@@ -278,11 +286,14 @@ module ParallelTests
278
286
  opts.on("--nice", "execute test commands with low priority.") { options[:nice] = true }
279
287
  opts.on("--runtime-log [PATH]", "Location of previously recorded test runtimes") { |path| options[:runtime_log] = path }
280
288
  opts.on("--allowed-missing [INT]", Integer, "Allowed percentage of missing runtimes (default = 50)") { |percent| options[:allowed_missing_percent] = percent }
289
+ opts.on('--allow-duplicates', 'When detecting files to run, allow duplicates') { options[:allow_duplicates] = true }
281
290
  opts.on("--unknown-runtime [FLOAT]", Float, "Use given number as unknown runtime (otherwise use average time)") { |time| options[:unknown_runtime] = time }
282
291
  opts.on("--first-is-1", "Use \"1\" as TEST_ENV_NUMBER to not reuse the default test environment") { options[:first_is_1] = true }
283
292
  opts.on("--fail-fast", "Stop all groups when one group fails (best used with --test-options '--fail-fast' if supported") { options[:fail_fast] = true }
284
293
  opts.on("--verbose", "Print debug output") { options[:verbose] = true }
285
- opts.on("--verbose-command", "Displays the command that will be executed by each process and when there are failures displays the command executed by each process that failed") { options[:verbose_command] = true }
294
+ opts.on("--verbose-command", "Combines options --verbose-process-command and --verbose-rerun-command") { options.merge! verbose_process_command: true, verbose_rerun_command: true }
295
+ opts.on("--verbose-process-command", "Print the command that will be executed by each process before it begins") { options[:verbose_process_command] = true }
296
+ opts.on("--verbose-rerun-command", "After a process fails, print the command executed by that process") { options[:verbose_rerun_command] = true }
286
297
  opts.on("--quiet", "Print only tests output") { options[:quiet] = true }
287
298
  opts.on("-v", "--version", "Show Version") do
288
299
  puts ParallelTests::VERSION
@@ -330,6 +341,10 @@ module ParallelTests
330
341
  raise "Can't pass --specify-groups with any of these keys: --single, --isolate, or --isolate-n"
331
342
  end
332
343
 
344
+ if options[:failure_exit_code] && options[:highest_exit_status]
345
+ raise "Can't pass --failure-exit-code and --highest-exit-status"
346
+ end
347
+
333
348
  options
334
349
  end
335
350
 
@@ -15,7 +15,7 @@ module ParallelTests
15
15
 
16
16
  def runtime_logging
17
17
  # Not Yet Supported
18
- ""
18
+ []
19
19
  end
20
20
  end
21
21
  end
@@ -48,6 +48,9 @@ module ParallelTests
48
48
  activate_pipefail = "set -o pipefail"
49
49
  remove_ignored_lines = %{(grep -v #{Shellwords.escape(ignore_regex)} || true)}
50
50
 
51
+ # remove nil values (ex: #purge_before_load returns nil)
52
+ command.compact!
53
+
51
54
  if system('/bin/bash', '-c', "#{activate_pipefail} 2>/dev/null")
52
55
  shell_command = "#{activate_pipefail} && (#{Shellwords.shelljoin(command)}) | #{remove_ignored_lines}"
53
56
  ['/bin/bash', '-c', shell_command]
@@ -125,6 +128,24 @@ module ParallelTests
125
128
  command
126
129
  end
127
130
 
131
+ def configured_databases
132
+ return [] unless defined?(ActiveRecord) && rails_61_or_greater?
133
+
134
+ @@configured_databases ||= ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
135
+ end
136
+
137
+ def for_each_database(&block)
138
+ # Use nil to represent all databases
139
+ block&.call(nil)
140
+
141
+ # skip if not rails or old rails version
142
+ return if !defined?(ActiveRecord::Tasks::DatabaseTasks) || !ActiveRecord::Tasks::DatabaseTasks.respond_to?(:for_each)
143
+
144
+ ActiveRecord::Tasks::DatabaseTasks.for_each(configured_databases) do |name|
145
+ block&.call(name)
146
+ end
147
+ end
148
+
128
149
  private
129
150
 
130
151
  def rails_7_or_greater?
@@ -145,25 +166,33 @@ namespace :parallel do
145
166
  ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
146
167
  end
147
168
 
148
- desc "Create test databases via db:create --> parallel:create[num_cpus]"
149
- task :create, :count do |_, args|
150
- ParallelTests::Tasks.run_in_parallel(
151
- [$0, "db:create", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
152
- args
153
- )
169
+ ParallelTests::Tasks.for_each_database do |name|
170
+ task_name = 'create'
171
+ task_name += ":#{name}" if name
172
+ desc "Create test#{" #{name}" if name} database via db:#{task_name} --> parallel:#{task_name}[num_cpus]"
173
+ task task_name.to_sym, :count do |_, args|
174
+ ParallelTests::Tasks.run_in_parallel(
175
+ [$0, "db:#{task_name}", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
176
+ args
177
+ )
178
+ end
154
179
  end
155
180
 
156
- desc "Drop test databases via db:drop --> parallel:drop[num_cpus]"
157
- task :drop, :count do |_, args|
158
- ParallelTests::Tasks.run_in_parallel(
159
- [
160
- $0,
161
- "db:drop",
162
- "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
163
- "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
164
- ],
165
- args
166
- )
181
+ ParallelTests::Tasks.for_each_database do |name|
182
+ task_name = 'drop'
183
+ task_name += ":#{name}" if name
184
+ desc "Drop test#{" #{name}" if name} database via db:#{task_name} --> parallel:#{task_name}[num_cpus]"
185
+ task task_name.to_sym, :count do |_, args|
186
+ ParallelTests::Tasks.run_in_parallel(
187
+ [
188
+ $0,
189
+ "db:#{task_name}",
190
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
191
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
192
+ ],
193
+ args
194
+ )
195
+ end
167
196
  end
168
197
 
169
198
  desc "Update test databases by dumping and loading --> parallel:prepare[num_cpus]"
@@ -190,12 +219,16 @@ namespace :parallel do
190
219
  end
191
220
 
192
221
  # when dumping/resetting takes too long
193
- desc "Update test databases via db:migrate --> parallel:migrate[num_cpus]"
194
- task :migrate, :count do |_, args|
195
- ParallelTests::Tasks.run_in_parallel(
196
- [$0, "db:migrate", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
197
- args
198
- )
222
+ ParallelTests::Tasks.for_each_database do |name|
223
+ task_name = 'migrate'
224
+ task_name += ":#{name}" if name
225
+ desc "Update test#{" #{name}" if name} database via db:#{task_name} --> parallel:#{task_name}[num_cpus]"
226
+ task task_name.to_sym, :count do |_, args|
227
+ ParallelTests::Tasks.run_in_parallel(
228
+ [$0, "db:#{task_name}", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
229
+ args
230
+ )
231
+ end
199
232
  end
200
233
 
201
234
  desc "Rollback test databases via db:rollback --> parallel:rollback[num_cpus]"
@@ -207,16 +240,24 @@ namespace :parallel do
207
240
  end
208
241
 
209
242
  # just load the schema (good for integration server <-> no development db)
210
- desc "Load dumped schema for test databases via db:schema:load --> parallel:load_schema[num_cpus]"
211
- task :load_schema, :count do |_, args|
212
- command = [
213
- $0,
214
- ParallelTests::Tasks.purge_before_load,
215
- "db:schema:load",
216
- "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
217
- "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
218
- ]
219
- ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
243
+ ParallelTests::Tasks.for_each_database do |name|
244
+ rails_task = 'db:schema:load'
245
+ rails_task += ":#{name}" if name
246
+
247
+ task_name = 'load_schema'
248
+ task_name += ":#{name}" if name
249
+
250
+ desc "Load dumped schema for test#{" #{name}" if name} database via #{rails_task} --> parallel:#{task_name}[num_cpus]"
251
+ task task_name.to_sym, :count do |_, args|
252
+ command = [
253
+ $0,
254
+ ParallelTests::Tasks.purge_before_load,
255
+ rails_task,
256
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
257
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
258
+ ]
259
+ ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
260
+ end
220
261
  end
221
262
 
222
263
  # load the structure from the structure.sql file
@@ -238,8 +238,9 @@ module ParallelTests
238
238
  suffix_pattern = options[:suffix] || test_suffix
239
239
  include_pattern = options[:pattern] || //
240
240
  exclude_pattern = options[:exclude_pattern]
241
+ allow_duplicates = options[:allow_duplicates]
241
242
 
242
- (tests || []).flat_map do |file_or_folder|
243
+ files = (tests || []).flat_map do |file_or_folder|
243
244
  if File.directory?(file_or_folder)
244
245
  files = files_in_folder(file_or_folder, options)
245
246
  files = files.grep(suffix_pattern).grep(include_pattern)
@@ -248,7 +249,9 @@ module ParallelTests
248
249
  else
249
250
  file_or_folder
250
251
  end
251
- end.uniq
252
+ end
253
+
254
+ allow_duplicates ? files : files.uniq
252
255
  end
253
256
 
254
257
  def files_in_folder(folder, options = {})
@@ -290,7 +293,7 @@ module ParallelTests
290
293
  end
291
294
 
292
295
  def report_process_command?(options)
293
- options[:verbose] || options[:verbose_command]
296
+ options[:verbose] || options[:verbose_process_command]
294
297
  end
295
298
  end
296
299
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ParallelTests
3
- VERSION = '4.4.0'
3
+ VERSION = '4.7.1'
4
4
  end
@@ -44,6 +44,8 @@ module ParallelTests
44
44
 
45
45
  def stop_all_processes
46
46
  pids.all.each { |pid| Process.kill(:INT, pid) }
47
+ rescue Errno::ESRCH
48
+ # Process already terminated, do nothing
47
49
  end
48
50
 
49
51
  # copied from http://github.com/carlhuda/bundler Bundler::SharedHelpers#find_gemfile
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: 4.4.0
4
+ version: 4.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-25 00:00:00.000000000 Z
11
+ date: 2024-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parallel
@@ -69,8 +69,8 @@ licenses:
69
69
  - MIT
70
70
  metadata:
71
71
  bug_tracker_uri: https://github.com/grosser/parallel_tests/issues
72
- documentation_uri: https://github.com/grosser/parallel_tests/blob/v4.4.0/Readme.md
73
- source_code_uri: https://github.com/grosser/parallel_tests/tree/v4.4.0
72
+ documentation_uri: https://github.com/grosser/parallel_tests/blob/v4.7.1/Readme.md
73
+ source_code_uri: https://github.com/grosser/parallel_tests/tree/v4.7.1
74
74
  wiki_uri: https://github.com/grosser/parallel_tests/wiki
75
75
  post_install_message:
76
76
  rdoc_options: []
@@ -87,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
87
  - !ruby/object:Gem::Version
88
88
  version: '0'
89
89
  requirements: []
90
- rubygems_version: 3.3.3
90
+ rubygems_version: 3.4.10
91
91
  signing_key:
92
92
  specification_version: 4
93
93
  summary: Run Test::Unit / RSpec / Cucumber / Spinach in parallel