parallel_tests 4.2.0 → 4.8.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: 1bcc6e9cd8a207f7a7ec8253139040265cf1c1962e5d25629bca37e5cd2b111e
4
- data.tar.gz: 7db53c69e2048799c12504a6ce7b56b6b7b4833ef77fdc298ba50c61fe8ed742
3
+ metadata.gz: 40c390881f78ed6c146e2f11deee7e19fc5df5fa99494779a15743ce6a8463a0
4
+ data.tar.gz: 89cc457abaea5d29160f745ccc6cd57e2ba365f53f64b82248eb3033533da804
5
5
  SHA512:
6
- metadata.gz: f682ef1d3752cd3893e186879d905954456b51900ce0d4cfd2213f1c226d13fb38d1a3f251dc75bccb8da663273f59ef977e0aa92707f3ede49db381a53c40f5
7
- data.tar.gz: 7a3bad92876b0b225f381b60b89e7049f84e9069a89b710749ecad0e6beed6a60952ed3d850dbe90621a731a974b7694b9182bb02a0f5e3e5292ccb2b6226614
6
+ metadata.gz: d1cc1c8645f580ea2eb43ee650e24a1c314c1af93937a5d11a8591e405bbc429b23fcfc79ed92bc50f9d15ec3a1aa35abe1db2a718927dbab50b1077a4fa91c6
7
+ data.tar.gz: b50b820699c76f128d1bce2495d5f2363cac1a53e978713c6e3a51b5e298f841292153a898514ded1153d7c99c230e67c95f3f23c6a4416a78dc26991c875b11
data/Readme.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # parallel_tests
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/parallel_tests.svg)](https://rubygems.org/gems/parallel_tests)
4
- [![Build status](https://github.com/grosser/parallel_tests/workflows/test/badge.svg)](https://github.com/grosser/parallel_tests/actions?query=workflow%3Atest)
4
+ [![Build status](https://github.com/grosser/parallel_tests/workflows/test/badge.svg)](https://github.com/grosser/parallel_tests/actions?query=workflow%3Atest&branch=master)
5
5
 
6
- Speedup Test::Unit + RSpec + Cucumber + Spinach by running parallel on multiple CPU cores.<br/>
7
- ParallelTests splits tests into even groups (by number of lines or runtime) and runs each group in a single process with its own database.
6
+ Speedup Minitest + RSpec + Turnip + Cucumber + Spinach by running parallel on multiple CPU cores.<br/>
7
+ ParallelTests splits tests into balanced groups (by number of lines or runtime) and runs each group in a process with its own database.
8
8
 
9
9
  Setup for Rails
10
10
  ===============
@@ -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
- rake parallel:test # Test::Unit
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,13 +87,13 @@ 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
 
86
- Running things once
96
+ Running setup or teardown once
87
97
  ===================
88
98
 
89
99
  ```Ruby
@@ -106,11 +116,16 @@ at_exit do
106
116
  end
107
117
  ```
108
118
 
109
- Even test group run-times
110
- =========================
119
+ Even test group runtimes
120
+ ========================
121
+
122
+ Test groups will often run for different times, making the full test run as slow as the slowest group.
123
+
124
+ **Step 1**: Use these loggers (see below) to record test runtime
111
125
 
112
- Test groups are often not balanced and will run for different times, making everything wait for the slowest group.
113
- Use these loggers to record test runtime and then use the recorded runtime to balance test groups more evenly.
126
+ **Step 2**: The next test run will use the recorded test runtimes (use `--runtime-log <file>` if you picked a location different from below)
127
+
128
+ **Step 3**: Automate upload/download of test runtime from your CI system [example](https://github.com/grosser/parallel_rails_example/blob/master/.github/workflows/test.yml) (chunks need to be combined, an alternative is [amend](https://github.com/grosser/amend))
114
129
 
115
130
  ### RSpec
116
131
 
@@ -128,7 +143,7 @@ Add to your `test_helper.rb`:
128
143
  require 'parallel_tests/test/runtime_logger' if ENV['RECORD_RUNTIME']
129
144
  ```
130
145
 
131
- results will be logged to tmp/parallel_runtime_test.log when `RECORD_RUNTIME` is set,
146
+ results will be logged to `tmp/parallel_runtime_test.log` when `RECORD_RUNTIME` is set,
132
147
  so it is not always required or overwritten.
133
148
 
134
149
  Loggers
@@ -147,7 +162,7 @@ Add the following to your `.rspec_parallel` (or `.rspec`) :
147
162
  RSpec: FailuresLogger
148
163
  -----------------------
149
164
 
150
- Produce pastable command-line snippets for each failed example. For example:
165
+ Produce pasteable command-line snippets for each failed example. For example:
151
166
 
152
167
  ```bash
153
168
  rspec /path/to/my_spec.rb:123 # should do something
@@ -160,6 +175,24 @@ Add to `.rspec_parallel` or use as CLI flag:
160
175
 
161
176
  (Not needed to retry failures, for that pass [--only-failures](https://relishapp.com/rspec/rspec-core/docs/command-line/only-failures) to rspec)
162
177
 
178
+
179
+ RSpec: VerboseLogger
180
+ -----------------------
181
+
182
+ Prints a single line for starting and finishing each example, to see what is currently running in each process.
183
+
184
+ ```
185
+ # PID, parallel process number, spec status, example description
186
+ [14403] [2] [STARTED] Foo foo
187
+ [14402] [1] [STARTED] Bar bar
188
+ [14402] [1] [PASSED] Bar bar
189
+ ```
190
+
191
+ Add to `.rspec_parallel` or use as CLI flag:
192
+
193
+ --format ParallelTests::RSpec::VerboseLogger
194
+
195
+
163
196
  Cucumber: FailuresLogger
164
197
  -----------------------
165
198
 
@@ -205,22 +238,23 @@ Setup for non-rails
205
238
 
206
239
  Options are:
207
240
  <!-- copy output from bundle exec ./bin/parallel_test -h -->
208
- -n [PROCESSES] How many processes to use, default: available CPUs
209
- -p, --pattern [PATTERN] run tests matching this regex pattern
210
- --exclude-pattern [PATTERN] exclude tests matching this regex pattern
211
- --group-by [TYPE] group tests by:
241
+ -n PROCESSES How many processes to use, default: available CPUs
242
+ -p, --pattern PATTERN run tests matching this regex pattern
243
+ --exclude-pattern PATTERN exclude tests matching this regex pattern
244
+ --group-by TYPE group tests by:
212
245
  found - order of finding files
213
246
  steps - number of cucumber/spinach steps
214
247
  scenarios - individual cucumber scenarios
215
248
  filesize - by size of the file
216
249
  runtime - info from runtime log
217
250
  default - runtime when runtime log is filled otherwise filesize
218
- -m, --multiply-processes [FLOAT] use given number as a multiplier of processes to run
219
- -s, --single [PATTERN] Run all matching files in the same process
251
+ -m, --multiply-processes COUNT use given number as a multiplier of processes to run
252
+ -s, --single PATTERN Run all matching files in the same process
220
253
  -i, --isolate Do not run any other tests in the group used by --single(-s)
221
- --isolate-n [PROCESSES] Use 'isolate' singles with number of processes, default: 1.
254
+ --isolate-n PROCESSES Use 'isolate' singles with number of processes, default: 1
222
255
  --highest-exit-status Exit with the highest exit status provided by test run(s)
223
- --specify-groups [SPECS] Use 'specify-groups' if you want to specify multiple specs running in multiple
256
+ --failure-exit-code INT Specify the exit code to use when tests fail
257
+ --specify-groups SPECS Use 'specify-groups' if you want to specify multiple specs running in multiple
224
258
  processes in a specific formation. Commas indicate specs in the same process,
225
259
  pipes indicate specs in a new process. Cannot use with --single, --isolate, or
226
260
  --isolate-n. Ex.
@@ -228,11 +262,13 @@ Options are:
228
262
  Process 1 will contain 1_spec.rb and 2_spec.rb
229
263
  Process 2 will contain 3_spec.rb
230
264
  Process 3 will contain all other specs
231
- --only-group INT[,INT]
232
- -e, --exec [COMMAND] execute this code parallel and with ENV['TEST_ENV_NUMBER']
233
- -o, --test-options '[OPTIONS]' execute test commands with those options
234
- -t, --type [TYPE] test(default) / rspec / cucumber / spinach
235
- --suffix [PATTERN] override built in test file pattern (should match suffix):
265
+ --only-group GROUP_INDEX[,GROUP_INDEX]
266
+ Only run the given group numbers.
267
+ Changes `--group-by` default to 'filesize'.
268
+ -e, --exec COMMAND execute this code parallel and with ENV['TEST_ENV_NUMBER']
269
+ -o, --test-options 'OPTIONS' execute test commands with those options
270
+ -t, --type TYPE test(default) / rspec / cucumber / spinach
271
+ --suffix PATTERN override built in test file pattern (should match suffix):
236
272
  '_spec.rb$' - matches rspec files
237
273
  '_(test|spec).rb$' - matches test or spec files
238
274
  --serialize-stdout Serialize stdout output, nothing will be written until everything is done
@@ -241,16 +277,19 @@ Options are:
241
277
  --combine-stderr Combine stderr into stdout, useful in conjunction with --serialize-stdout
242
278
  --non-parallel execute same commands but do not in parallel, needs --exec
243
279
  --no-symlinks Do not traverse symbolic links to find test files
244
- --ignore-tags [PATTERN] When counting steps ignore scenarios with tags that match this pattern
280
+ --ignore-tags PATTERN When counting steps ignore scenarios with tags that match this pattern
245
281
  --nice execute test commands with low priority.
246
- --runtime-log [PATH] Location of previously recorded test runtimes
247
- --allowed-missing [INT] Allowed percentage of missing runtimes (default = 50)
248
- --unknown-runtime [FLOAT] Use given number as unknown runtime (otherwise use average time)
282
+ --runtime-log PATH Location of previously recorded test runtimes
283
+ --allowed-missing COUNT Allowed percentage of missing runtimes (default = 50)
284
+ --allow-duplicates When detecting files to run, allow duplicates
285
+ --unknown-runtime SECONDS Use given number as unknown runtime (otherwise use average time)
249
286
  --first-is-1 Use "1" as TEST_ENV_NUMBER to not reuse the default test environment
250
287
  --fail-fast Stop all groups when one group fails (best used with --test-options '--fail-fast' if supported
288
+ --test-file-limit LIMIT Limit to this number of files per test run by batching (for windows set to ~100 to stay below 8192 max command limit, might have bugs from reusing test-env-number and summarizing partial results)
251
289
  --verbose Print debug output
252
- --verbose-process-command Displays only the command that will be executed by each process
253
- --verbose-rerun-command When there are failures, displays the command executed by each process that failed
290
+ --verbose-command Combines options --verbose-process-command and --verbose-rerun-command
291
+ --verbose-process-command Print the command that will be executed by each process before it begins
292
+ --verbose-rerun-command After a process fails, print the command executed by that process
254
293
  --quiet Print only tests output
255
294
  -v, --version Show Version
256
295
  -h, --help Show this.
@@ -404,6 +443,9 @@ inspired by [pivotal labs](https://blog.pivotal.io/labs/labs/parallelize-your-rs
404
443
  - [Jon Dufresne](https://github.com/jdufresne)
405
444
  - [Eric Kessler](https://github.com/enkessler)
406
445
  - [Adis Osmonov](https://github.com/adis-io)
446
+ - [Josh Westbrook](https://github.com/joshwestbrook)
447
+ - [Jay Dorsey](https://github.com/jaydorsey)
448
+ - [hatsu](https://github.com/hatsu38)
407
449
 
408
450
  [Michael Grosser](http://grosser.it)<br/>
409
451
  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
@@ -108,8 +109,19 @@ module ParallelTests
108
109
  end
109
110
 
110
111
  def run_tests(group, process_number, num_processes, options)
111
- if group.empty?
112
- { stdout: '', exit_status: 0, command: nil, seed: nil }
112
+ if (limit = options[:test_file_limit])
113
+ # TODO: will have some bugs with summarizing results and last process
114
+ results = group.each_slice(limit).map do |slice|
115
+ @runner.run_tests(slice, process_number, num_processes, options)
116
+ end
117
+ result = results[0]
118
+ results[1..].each do |res|
119
+ result[:stdout] = result[:stdout].to_s + res[:stdout].to_s
120
+ result[:exit_status] = [res[:exit_status], result[:exit_status]].max
121
+ # adding all files back in, not using original cmd to show what was actually run
122
+ result[:command] |= res[:command]
123
+ end
124
+ result
113
125
  else
114
126
  @runner.run_tests(group, process_number, num_processes, options)
115
127
  end
@@ -145,7 +157,7 @@ module ParallelTests
145
157
  failing_sets = test_results.reject { |r| r[:exit_status] == 0 }
146
158
  return if failing_sets.none?
147
159
 
148
- if options[:verbose] || options[:verbose_command]
160
+ if options[:verbose] || options[:verbose_rerun_command]
149
161
  puts "\n\nTests have failed for a parallel_test group. Use the following command to run the group again:\n\n"
150
162
  failing_sets.each do |failing_set|
151
163
  command = failing_set[:command]
@@ -193,11 +205,11 @@ module ParallelTests
193
205
 
194
206
  Options are:
195
207
  BANNER
196
- opts.on("-n [PROCESSES]", Integer, "How many processes to use, default: available CPUs") { |n| options[:count] = n }
197
- opts.on("-p", "--pattern [PATTERN]", "run tests matching this regex pattern") { |pattern| options[:pattern] = /#{pattern}/ }
198
- opts.on("--exclude-pattern", "--exclude-pattern [PATTERN]", "exclude tests matching this regex pattern") { |pattern| options[:exclude_pattern] = /#{pattern}/ }
208
+ opts.on("-n PROCESSES", Integer, "How many processes to use, default: available CPUs") { |n| options[:count] = n }
209
+ opts.on("-p", "--pattern PATTERN", "run tests matching this regex pattern") { |pattern| options[:pattern] = /#{pattern}/ }
210
+ opts.on("--exclude-pattern", "--exclude-pattern PATTERN", "exclude tests matching this regex pattern") { |pattern| options[:exclude_pattern] = /#{pattern}/ }
199
211
  opts.on(
200
- "--group-by [TYPE]",
212
+ "--group-by TYPE",
201
213
  <<~TEXT.rstrip.split("\n").join("\n#{newline_padding}")
202
214
  group tests by:
203
215
  found - order of finding files
@@ -208,11 +220,11 @@ module ParallelTests
208
220
  default - runtime when runtime log is filled otherwise filesize
209
221
  TEXT
210
222
  ) { |type| options[:group_by] = type.to_sym }
211
- opts.on("-m [FLOAT]", "--multiply-processes [FLOAT]", Float, "use given number as a multiplier of processes to run") do |multiply|
223
+ opts.on("-m COUNT", "--multiply-processes COUNT", Float, "use given number as a multiplier of processes to run") do |multiply|
212
224
  options[:multiply] = multiply
213
225
  end
214
226
 
215
- opts.on("-s [PATTERN]", "--single [PATTERN]", "Run all matching files in the same process") do |pattern|
227
+ opts.on("-s PATTERN", "--single PATTERN", "Run all matching files in the same process") do |pattern|
216
228
  (options[:single_process] ||= []) << /#{pattern}/
217
229
  end
218
230
 
@@ -221,44 +233,58 @@ module ParallelTests
221
233
  end
222
234
 
223
235
  opts.on(
224
- "--isolate-n [PROCESSES]",
236
+ "--isolate-n PROCESSES",
225
237
  Integer,
226
- "Use 'isolate' singles with number of processes, default: 1."
238
+ "Use 'isolate' singles with number of processes, default: 1"
227
239
  ) { |n| options[:isolate_count] = n }
228
240
 
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
241
+ opts.on(
242
+ "--highest-exit-status",
243
+ "Exit with the highest exit status provided by test run(s)"
244
+ ) { options[:highest_exit_status] = true }
245
+
246
+ opts.on(
247
+ "--failure-exit-code INT",
248
+ Integer,
249
+ "Specify the exit code to use when tests fail"
250
+ ) { |code| options[:failure_exit_code] = code }
232
251
 
233
252
  opts.on(
234
- "--specify-groups [SPECS]",
253
+ "--specify-groups SPECS",
235
254
  <<~TEXT.rstrip.split("\n").join("\n#{newline_padding}")
236
255
  Use 'specify-groups' if you want to specify multiple specs running in multiple
237
256
  processes in a specific formation. Commas indicate specs in the same process,
238
257
  pipes indicate specs in a new process. Cannot use with --single, --isolate, or
239
258
  --isolate-n. Ex.
240
- $ parallel_test -n 3 . --specify-groups '1_spec.rb,2_spec.rb|3_spec.rb'
259
+ $ parallel_tests -n 3 . --specify-groups '1_spec.rb,2_spec.rb|3_spec.rb'
241
260
  Process 1 will contain 1_spec.rb and 2_spec.rb
242
261
  Process 2 will contain 3_spec.rb
243
262
  Process 3 will contain all other specs
244
263
  TEXT
245
264
  ) { |groups| options[:specify_groups] = groups }
246
265
 
247
- opts.on("--only-group INT[,INT]", Array) { |groups| options[:only_group] = groups.map(&:to_i) }
266
+ opts.on(
267
+ "--only-group GROUP_INDEX[,GROUP_INDEX]",
268
+ Array,
269
+ <<~TEXT.rstrip.split("\n").join("\n#{newline_padding}")
270
+ Only run the given group numbers.
271
+ Changes `--group-by` default to 'filesize'.
272
+ TEXT
273
+ ) { |groups| options[:only_group] = groups.map(&:to_i) }
248
274
 
249
- opts.on("-e", "--exec [COMMAND]", "execute this code parallel and with ENV['TEST_ENV_NUMBER']") { |arg| options[:execute] = Shellwords.shellsplit(arg) }
250
- opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options") { |arg| options[:test_options] = Shellwords.shellsplit(arg) }
251
- opts.on("-t", "--type [TYPE]", "test(default) / rspec / cucumber / spinach") do |type|
275
+ opts.on("-e", "--exec COMMAND", "execute this code parallel and with ENV['TEST_ENV_NUMBER']") { |arg| options[:execute] = Shellwords.shellsplit(arg) }
276
+ opts.on("-o", "--test-options 'OPTIONS'", "execute test commands with those options") { |arg| options[:test_options] = Shellwords.shellsplit(arg) }
277
+ opts.on("-t", "--type TYPE", "test(default) / rspec / cucumber / spinach") do |type|
252
278
  @runner = load_runner(type)
253
279
  rescue NameError, LoadError => e
254
280
  puts "Runner for `#{type}` type has not been found! (#{e})"
255
281
  abort
256
282
  end
257
283
  opts.on(
258
- "--suffix [PATTERN]",
284
+ "--suffix PATTERN",
259
285
  <<~TEXT.rstrip.split("\n").join("\n#{newline_padding}")
260
286
  override built in test file pattern (should match suffix):
261
- '_spec\.rb$' - matches rspec files
287
+ '_spec.rb$' - matches rspec files
262
288
  '_(test|spec).rb$' - matches test or spec files
263
289
  TEXT
264
290
  ) { |pattern| options[:suffix] = /#{pattern}/ }
@@ -267,15 +293,19 @@ module ParallelTests
267
293
  opts.on("--combine-stderr", "Combine stderr into stdout, useful in conjunction with --serialize-stdout") { options[:combine_stderr] = true }
268
294
  opts.on("--non-parallel", "execute same commands but do not in parallel, needs --exec") { options[:non_parallel] = true }
269
295
  opts.on("--no-symlinks", "Do not traverse symbolic links to find test files") { options[:symlinks] = false }
270
- opts.on('--ignore-tags [PATTERN]', 'When counting steps ignore scenarios with tags that match this pattern') { |arg| options[:ignore_tag_pattern] = arg }
296
+ opts.on('--ignore-tags PATTERN', 'When counting steps ignore scenarios with tags that match this pattern') { |arg| options[:ignore_tag_pattern] = arg }
271
297
  opts.on("--nice", "execute test commands with low priority.") { options[:nice] = true }
272
- opts.on("--runtime-log [PATH]", "Location of previously recorded test runtimes") { |path| options[:runtime_log] = path }
273
- opts.on("--allowed-missing [INT]", Integer, "Allowed percentage of missing runtimes (default = 50)") { |percent| options[:allowed_missing_percent] = percent }
274
- opts.on("--unknown-runtime [FLOAT]", Float, "Use given number as unknown runtime (otherwise use average time)") { |time| options[:unknown_runtime] = time }
298
+ opts.on("--runtime-log PATH", "Location of previously recorded test runtimes") { |path| options[:runtime_log] = path }
299
+ opts.on("--allowed-missing COUNT", Integer, "Allowed percentage of missing runtimes (default = 50)") { |percent| options[:allowed_missing_percent] = percent }
300
+ opts.on('--allow-duplicates', 'When detecting files to run, allow duplicates') { options[:allow_duplicates] = true }
301
+ opts.on("--unknown-runtime SECONDS", Float, "Use given number as unknown runtime (otherwise use average time)") { |time| options[:unknown_runtime] = time }
275
302
  opts.on("--first-is-1", "Use \"1\" as TEST_ENV_NUMBER to not reuse the default test environment") { options[:first_is_1] = true }
276
303
  opts.on("--fail-fast", "Stop all groups when one group fails (best used with --test-options '--fail-fast' if supported") { options[:fail_fast] = true }
304
+ opts.on("--test-file-limit LIMIT", Integer, "Limit to this number of files per test run by batching (for windows set to ~100 to stay below 8192 max command limit, might have bugs from reusing test-env-number and summarizing partial results)") { |limit| options[:test_file_limit] = limit }
277
305
  opts.on("--verbose", "Print debug output") { options[:verbose] = true }
278
- 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 }
306
+ opts.on("--verbose-command", "Combines options --verbose-process-command and --verbose-rerun-command") { options.merge! verbose_process_command: true, verbose_rerun_command: true }
307
+ opts.on("--verbose-process-command", "Print the command that will be executed by each process before it begins") { options[:verbose_process_command] = true }
308
+ opts.on("--verbose-rerun-command", "After a process fails, print the command executed by that process") { options[:verbose_rerun_command] = true }
279
309
  opts.on("--quiet", "Print only tests output") { options[:quiet] = true }
280
310
  opts.on("-v", "--version", "Show Version") do
281
311
  puts ParallelTests::VERSION
@@ -323,6 +353,10 @@ module ParallelTests
323
353
  raise "Can't pass --specify-groups with any of these keys: --single, --isolate, or --isolate-n"
324
354
  end
325
355
 
356
+ if options[:failure_exit_code] && options[:highest_exit_status]
357
+ raise "Can't pass --failure-exit-code and --highest-exit-status"
358
+ end
359
+
326
360
  options
327
361
  end
328
362
 
@@ -4,8 +4,8 @@ require "parallel_tests/gherkin/runner"
4
4
  module ParallelTests
5
5
  module Cucumber
6
6
  class Runner < ParallelTests::Gherkin::Runner
7
- SCENARIOS_RESULTS_BOUNDARY_REGEX = /^(Failing|Flaky) Scenarios:$/.freeze
8
- SCENARIO_REGEX = %r{^cucumber features/.+:\d+}.freeze
7
+ SCENARIOS_RESULTS_BOUNDARY_REGEX = /^(Failing|Flaky) Scenarios:$/
8
+ SCENARIO_REGEX = %r{^cucumber features/.+:\d+}
9
9
 
10
10
  class << self
11
11
  def name
@@ -26,10 +26,6 @@ module ParallelTests
26
26
 
27
27
  isolate_count = isolate_count(options)
28
28
 
29
- if isolate_count >= num_groups
30
- raise 'Number of isolated processes must be less than total the number of processes'
31
- end
32
-
33
29
  if isolate_count >= num_groups
34
30
  raise 'Number of isolated processes must be >= total number of processes'
35
31
  end
@@ -3,22 +3,14 @@ require 'parallel_tests/rspec/logger_base'
3
3
  require 'parallel_tests/rspec/runner'
4
4
 
5
5
  class ParallelTests::RSpec::FailuresLogger < ParallelTests::RSpec::LoggerBase
6
- if RSPEC_2
7
- def dump_failures(*args); end
8
- else
9
- RSpec::Core::Formatters.register self, :dump_summary
10
- end
6
+ RSpec::Core::Formatters.register(self, :dump_summary)
11
7
 
12
8
  def dump_summary(*args)
13
9
  lock_output do
14
- if RSPEC_2
15
- dump_commands_to_rerun_failed_examples
16
- else
17
- notification = args.first
18
- unless notification.failed_examples.empty?
19
- colorizer = ::RSpec::Core::Formatters::ConsoleCodes
20
- output.puts notification.colorized_rerun_commands(colorizer)
21
- end
10
+ notification = args.first
11
+ unless notification.failed_examples.empty?
12
+ colorizer = ::RSpec::Core::Formatters::ConsoleCodes
13
+ output.puts notification.colorized_rerun_commands(colorizer)
22
14
  end
23
15
  end
24
16
  @output.flush
@@ -7,8 +7,6 @@ end
7
7
  require 'rspec/core/formatters/base_text_formatter'
8
8
 
9
9
  class ParallelTests::RSpec::LoggerBase < RSpec::Core::Formatters::BaseTextFormatter
10
- RSPEC_2 = RSpec::Core::Version::STRING.start_with?('2')
11
-
12
10
  def initialize(*args)
13
11
  super
14
12
 
@@ -33,8 +33,10 @@ module ParallelTests
33
33
  "spec"
34
34
  end
35
35
 
36
+ # used to find all _spec.rb files
37
+ # supports also feature files used by rspec turnip extension
36
38
  def test_suffix
37
- /_spec\.rb$/
39
+ /(_spec\.rb|\.feature)$/
38
40
  end
39
41
 
40
42
  def line_is_result?(line)
@@ -9,7 +9,7 @@ class ParallelTests::RSpec::RuntimeLogger < ParallelTests::RSpec::LoggerBase
9
9
  @group_nesting = 0
10
10
  end
11
11
 
12
- RSpec::Core::Formatters.register self, :example_group_started, :example_group_finished, :start_dump unless RSPEC_2
12
+ RSpec::Core::Formatters.register(self, :example_group_started, :example_group_finished, :start_dump)
13
13
 
14
14
  def example_group_started(example_group)
15
15
  @time = ParallelTests.now if @group_nesting == 0
@@ -20,12 +20,13 @@ class ParallelTests::RSpec::RuntimeLogger < ParallelTests::RSpec::LoggerBase
20
20
  def example_group_finished(notification)
21
21
  @group_nesting -= 1
22
22
  if @group_nesting == 0
23
- path = (RSPEC_2 ? notification.file_path : notification.group.file_path)
24
- @example_times[path] += ParallelTests.now - @time
23
+ @example_times[notification.group.file_path] += ParallelTests.now - @time
25
24
  end
26
25
  super if defined?(super)
27
26
  end
28
27
 
28
+ def seed(*); end
29
+
29
30
  def dump_summary(*); end
30
31
 
31
32
  def dump_failures(*); end
@@ -37,9 +38,11 @@ class ParallelTests::RSpec::RuntimeLogger < ParallelTests::RSpec::LoggerBase
37
38
  def start_dump(*)
38
39
  return unless ENV['TEST_ENV_NUMBER'] # only record when running in parallel
39
40
  lock_output do
41
+ # Order the output from slowest to fastest
42
+ @example_times = @example_times.sort_by(&:last).reverse
40
43
  @example_times.each do |file, time|
41
44
  relative_path = file.sub(%r{^#{Regexp.escape Dir.pwd}/}, '').sub(%r{^\./}, "")
42
- @output.puts "#{relative_path}:#{time > 0 ? time : 0}"
45
+ @output.puts "#{relative_path}:#{[time, 0].max}"
43
46
  end
44
47
  end
45
48
  @output.flush
@@ -2,7 +2,7 @@
2
2
  require 'parallel_tests/rspec/failures_logger'
3
3
 
4
4
  class ParallelTests::RSpec::SummaryLogger < ParallelTests::RSpec::LoggerBase
5
- RSpec::Core::Formatters.register self, :dump_failures unless RSPEC_2
5
+ RSpec::Core::Formatters.register(self, :dump_failures)
6
6
 
7
7
  def dump_failures(*args)
8
8
  lock_output { super }
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core/formatters/base_text_formatter'
4
+ require 'parallel_tests/rspec/runner'
5
+
6
+ class ParallelTests::RSpec::VerboseLogger < RSpec::Core::Formatters::BaseTextFormatter
7
+ RSpec::Core::Formatters.register(
8
+ self,
9
+ :example_group_started,
10
+ :example_group_finished,
11
+ :example_started,
12
+ :example_passed,
13
+ :example_pending,
14
+ :example_failed
15
+ )
16
+
17
+ def initialize(output)
18
+ super
19
+ @line = []
20
+ end
21
+
22
+ def example_group_started(notification)
23
+ @line.push(notification.group.description)
24
+ end
25
+
26
+ def example_group_finished(_notification)
27
+ @line.pop
28
+ end
29
+
30
+ def example_started(notification)
31
+ @line.push(notification.example.description)
32
+ output_formatted_line('STARTED', :yellow)
33
+ end
34
+
35
+ def example_passed(_passed)
36
+ output_formatted_line('PASSED', :success)
37
+ @line.pop
38
+ end
39
+
40
+ def example_pending(_pending)
41
+ output_formatted_line('PENDING', :pending)
42
+ @line.pop
43
+ end
44
+
45
+ def example_failed(_failure)
46
+ output_formatted_line('FAILED', :failure)
47
+ @line.pop
48
+ end
49
+
50
+ private
51
+
52
+ def output_formatted_line(status, console_code)
53
+ prefix = ["[#{Process.pid}]"]
54
+ if ENV.include?('TEST_ENV_NUMBER')
55
+ test_env_number = ENV['TEST_ENV_NUMBER'] == '' ? 1 : Integer(ENV['TEST_ENV_NUMBER'])
56
+ prefix << "[#{test_env_number}]"
57
+ end
58
+ prefix << RSpec::Core::Formatters::ConsoleCodes.wrap("[#{status}]", console_code)
59
+
60
+ output.puts [*prefix, *@line].join(' ')
61
+ end
62
+ end
@@ -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
@@ -95,7 +95,7 @@ module ParallelTests
95
95
  cmd = ["nice", *cmd] if options[:nice]
96
96
 
97
97
  # being able to run with for example `-output foo-$TEST_ENV_NUMBER` worked originally and is convenient
98
- cmd.map! { |c| c.gsub("$TEST_ENV_NUMBER", number).gsub("${TEST_ENV_NUMBER}", number) }
98
+ cmd = cmd.map { |c| c.gsub("$TEST_ENV_NUMBER", number).gsub("${TEST_ENV_NUMBER}", number) }
99
99
 
100
100
  print_command(cmd, env) if report_process_command?(options) && !options[:serialize_stdout]
101
101
 
@@ -158,7 +158,7 @@ module ParallelTests
158
158
 
159
159
  def executable
160
160
  if (executable = ENV['PARALLEL_TESTS_EXECUTABLE'])
161
- [executable]
161
+ Shellwords.shellsplit(executable)
162
162
  else
163
163
  determine_executable
164
164
  end
@@ -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.2.0'
3
+ VERSION = '4.8.0'
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, Errno::EPERM
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.2.0
4
+ version: 4.8.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: 2023-02-06 00:00:00.000000000 Z
11
+ date: 2025-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parallel
@@ -58,6 +58,7 @@ files:
58
58
  - lib/parallel_tests/rspec/runner.rb
59
59
  - lib/parallel_tests/rspec/runtime_logger.rb
60
60
  - lib/parallel_tests/rspec/summary_logger.rb
61
+ - lib/parallel_tests/rspec/verbose_logger.rb
61
62
  - lib/parallel_tests/spinach/runner.rb
62
63
  - lib/parallel_tests/tasks.rb
63
64
  - lib/parallel_tests/test/runner.rb
@@ -68,9 +69,11 @@ licenses:
68
69
  - MIT
69
70
  metadata:
70
71
  bug_tracker_uri: https://github.com/grosser/parallel_tests/issues
71
- documentation_uri: https://github.com/grosser/parallel_tests/blob/v4.2.0/Readme.md
72
- source_code_uri: https://github.com/grosser/parallel_tests/tree/v4.2.0
72
+ changelog_uri: https://github.com/grosser/parallel_tests/blob/v4.8.0/CHANGELOG.md
73
+ documentation_uri: https://github.com/grosser/parallel_tests/blob/v4.8.0/Readme.md
74
+ source_code_uri: https://github.com/grosser/parallel_tests/tree/v4.8.0
73
75
  wiki_uri: https://github.com/grosser/parallel_tests/wiki
76
+ rubygems_mfa_required: 'true'
74
77
  post_install_message:
75
78
  rdoc_options: []
76
79
  require_paths:
@@ -79,14 +82,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
79
82
  requirements:
80
83
  - - ">="
81
84
  - !ruby/object:Gem::Version
82
- version: 2.7.0
85
+ version: 3.0.0
83
86
  required_rubygems_version: !ruby/object:Gem::Requirement
84
87
  requirements:
85
88
  - - ">="
86
89
  - !ruby/object:Gem::Version
87
90
  version: '0'
88
91
  requirements: []
89
- rubygems_version: 3.3.3
92
+ rubygems_version: 3.4.10
90
93
  signing_key:
91
94
  specification_version: 4
92
95
  summary: Run Test::Unit / RSpec / Cucumber / Spinach in parallel