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 +4 -4
- data/Readme.md +79 -37
- data/lib/parallel_tests/cli.rb +67 -33
- data/lib/parallel_tests/cucumber/runner.rb +2 -2
- data/lib/parallel_tests/grouper.rb +0 -4
- data/lib/parallel_tests/rspec/failures_logger.rb +5 -13
- data/lib/parallel_tests/rspec/logger_base.rb +0 -2
- data/lib/parallel_tests/rspec/runner.rb +3 -1
- data/lib/parallel_tests/rspec/runtime_logger.rb +7 -4
- data/lib/parallel_tests/rspec/summary_logger.rb +1 -1
- data/lib/parallel_tests/rspec/verbose_logger.rb +62 -0
- data/lib/parallel_tests/spinach/runner.rb +1 -1
- data/lib/parallel_tests/tasks.rb +74 -33
- data/lib/parallel_tests/test/runner.rb +8 -5
- data/lib/parallel_tests/version.rb +1 -1
- data/lib/parallel_tests.rb +2 -0
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40c390881f78ed6c146e2f11deee7e19fc5df5fa99494779a15743ce6a8463a0
|
4
|
+
data.tar.gz: 89cc457abaea5d29160f745ccc6cd57e2ba365f53f64b82248eb3033533da804
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
[](https://rubygems.org/gems/parallel_tests)
|
4
|
-
[](https://github.com/grosser/parallel_tests/actions?query=workflow%3Atest)
|
4
|
+
[](https://github.com/grosser/parallel_tests/actions?query=workflow%3Atest&branch=master)
|
5
5
|
|
6
|
-
Speedup
|
7
|
-
ParallelTests splits tests into
|
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 #
|
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
|
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
|
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
|
-
|
113
|
-
|
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
|
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
|
209
|
-
-p, --pattern
|
210
|
-
--exclude-pattern
|
211
|
-
--group-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
|
219
|
-
-s, --single
|
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
|
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
|
-
--
|
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
|
232
|
-
|
233
|
-
|
234
|
-
-
|
235
|
-
|
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
|
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
|
247
|
-
--allowed-missing
|
248
|
-
--
|
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
|
253
|
-
--verbose-
|
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/>
|
data/lib/parallel_tests/cli.rb
CHANGED
@@ -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.
|
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,
|
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
|
-
|
100
|
-
|
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
|
112
|
-
|
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[:
|
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
|
197
|
-
opts.on("-p", "--pattern
|
198
|
-
opts.on("--exclude-pattern", "--exclude-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
|
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
|
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
|
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
|
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(
|
230
|
-
|
231
|
-
|
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
|
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
|
-
$
|
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(
|
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
|
250
|
-
opts.on("-o", "--test-options '
|
251
|
-
opts.on("-t", "--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
|
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
|
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
|
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
|
273
|
-
opts.on("--allowed-missing
|
274
|
-
opts.on(
|
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", "
|
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
|
8
|
-
SCENARIO_REGEX = %r{^cucumber features/.+:\d+}
|
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
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
notification
|
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
|
@@ -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
|
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
|
-
|
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
|
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
|
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
|
data/lib/parallel_tests/tasks.rb
CHANGED
@@ -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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
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
|
-
|
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
|
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[:
|
296
|
+
options[:verbose] || options[:verbose_process_command]
|
294
297
|
end
|
295
298
|
end
|
296
299
|
end
|
data/lib/parallel_tests.rb
CHANGED
@@ -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.
|
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:
|
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
|
-
|
72
|
-
|
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:
|
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.
|
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
|