parallel_tests 3.7.3 → 4.7.2
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 +70 -28
- data/lib/parallel_tests/cli.rb +68 -39
- data/lib/parallel_tests/cucumber/runner.rb +4 -4
- data/lib/parallel_tests/cucumber/scenario_line_logger.rb +1 -1
- data/lib/parallel_tests/cucumber/scenarios.rb +4 -3
- data/lib/parallel_tests/gherkin/runner.rb +11 -16
- data/lib/parallel_tests/grouper.rb +1 -5
- data/lib/parallel_tests/pids.rb +2 -2
- 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 +10 -15
- data/lib/parallel_tests/rspec/runtime_logger.rb +5 -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 +161 -75
- data/lib/parallel_tests/test/runner.rb +59 -16
- data/lib/parallel_tests/version.rb +1 -1
- data/lib/parallel_tests.rb +3 -1
- metadata +12 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e432543f3ce9e19888f91f4a11e14b073305a7f3fa8fa69a31cd3db96606d187
|
4
|
+
data.tar.gz: 1c78c9c74dbbef031ca758d3bec296e4b5d74e7beb8108f604679a45899db070
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 95cf6bc8c16eca666478417a73d674b5707506c798f3418bbfee339c27de173b5284de9e53d4120ebb4be6ee559f0a042b64e515bde05e4d7e3f08ff568baf21
|
7
|
+
data.tar.gz: a02b65aa9fdd77cb9c42f6c1e96ecff0c84affa85b525dc273b020dafc73d3715b40a0b4b64059d6cbc69940a5c7e3604bb1c9f61ff243c416bfe0aaedec3dd4
|
data/Readme.md
CHANGED
@@ -1,11 +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%3Awindows)
|
4
|
+
[](https://github.com/grosser/parallel_tests/actions?query=workflow%3Atest&branch=master)
|
6
5
|
|
7
|
-
Speedup
|
8
|
-
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.
|
9
8
|
|
10
9
|
Setup for Rails
|
11
10
|
===============
|
@@ -34,35 +33,45 @@ test:
|
|
34
33
|
### Create additional database(s)
|
35
34
|
rake parallel:create
|
36
35
|
|
36
|
+
### (Multi-DB) Create individual database
|
37
|
+
rake parallel:create:<database>
|
38
|
+
rake parallel:create:secondary
|
39
|
+
|
37
40
|
### Copy development schema (repeat after migrations)
|
38
41
|
rake parallel:prepare
|
39
42
|
|
40
43
|
### Run migrations in additional database(s) (repeat after migrations)
|
41
44
|
rake parallel:migrate
|
42
45
|
|
46
|
+
### (Multi-DB) Run migrations in individual database
|
47
|
+
rake parallel:migrate:<database>
|
48
|
+
|
43
49
|
### Setup environment from scratch (create db and loads schema, useful for CI)
|
44
50
|
rake parallel:setup
|
45
|
-
|
51
|
+
|
46
52
|
### Drop all test databases
|
47
53
|
rake parallel:drop
|
48
54
|
|
55
|
+
### (Multi-DB) Drop individual test database
|
56
|
+
rake parallel:drop:<database>
|
57
|
+
|
49
58
|
### Run!
|
50
|
-
rake parallel:test #
|
59
|
+
rake parallel:test # Minitest
|
51
60
|
rake parallel:spec # RSpec
|
52
61
|
rake parallel:features # Cucumber
|
53
62
|
rake parallel:features-spinach # Spinach
|
54
63
|
|
55
|
-
rake parallel:test[1] --> force 1 CPU --> 86 seconds
|
64
|
+
rake "parallel:test[1]" --> force 1 CPU --> 86 seconds
|
56
65
|
rake parallel:test --> got 2 CPUs? --> 47 seconds
|
57
66
|
rake parallel:test --> got 4 CPUs? --> 26 seconds
|
58
67
|
...
|
59
68
|
|
60
69
|
Test by pattern with Regex (e.g. use one integration server per subfolder / see if you broke any 'user'-related tests)
|
61
70
|
|
62
|
-
rake parallel:test[^test/unit] # every test file in test/unit folder
|
63
|
-
rake parallel:test[user] # run users_controller + user_helper + user tests
|
64
|
-
rake parallel:test['user|product'] # run user and product related tests
|
65
|
-
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
|
66
75
|
|
67
76
|
|
68
77
|
### Example output
|
@@ -78,13 +87,13 @@ Test by pattern with Regex (e.g. use one integration server per subfolder / see
|
|
78
87
|
```Bash
|
79
88
|
RAILS_ENV=test parallel_test -e "rake my:custom:task"
|
80
89
|
# or
|
81
|
-
rake parallel:rake[my:custom:task]
|
90
|
+
rake "parallel:rake[my:custom:task]"
|
82
91
|
# limited parallelism
|
83
|
-
rake parallel:rake[my:custom:task,2]
|
92
|
+
rake "parallel:rake[my:custom:task,2]"
|
84
93
|
```
|
85
94
|
|
86
95
|
|
87
|
-
Running
|
96
|
+
Running setup or teardown once
|
88
97
|
===================
|
89
98
|
|
90
99
|
```Ruby
|
@@ -107,11 +116,16 @@ at_exit do
|
|
107
116
|
end
|
108
117
|
```
|
109
118
|
|
110
|
-
Even test group
|
111
|
-
|
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
|
112
125
|
|
113
|
-
|
114
|
-
|
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))
|
115
129
|
|
116
130
|
### RSpec
|
117
131
|
|
@@ -129,7 +143,7 @@ Add to your `test_helper.rb`:
|
|
129
143
|
require 'parallel_tests/test/runtime_logger' if ENV['RECORD_RUNTIME']
|
130
144
|
```
|
131
145
|
|
132
|
-
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,
|
133
147
|
so it is not always required or overwritten.
|
134
148
|
|
135
149
|
Loggers
|
@@ -148,7 +162,7 @@ Add the following to your `.rspec_parallel` (or `.rspec`) :
|
|
148
162
|
RSpec: FailuresLogger
|
149
163
|
-----------------------
|
150
164
|
|
151
|
-
Produce
|
165
|
+
Produce pasteable command-line snippets for each failed example. For example:
|
152
166
|
|
153
167
|
```bash
|
154
168
|
rspec /path/to/my_spec.rb:123 # should do something
|
@@ -161,6 +175,24 @@ Add to `.rspec_parallel` or use as CLI flag:
|
|
161
175
|
|
162
176
|
(Not needed to retry failures, for that pass [--only-failures](https://relishapp.com/rspec/rspec-core/docs/command-line/only-failures) to rspec)
|
163
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
|
+
|
164
196
|
Cucumber: FailuresLogger
|
165
197
|
-----------------------
|
166
198
|
|
@@ -193,15 +225,15 @@ Setup for non-rails
|
|
193
225
|
- use `ENV['TEST_ENV_NUMBER']` inside your tests to select separate db/memcache/etc. (docker compose: expose it)
|
194
226
|
|
195
227
|
- Only run a subset of files / folders:
|
196
|
-
|
228
|
+
|
197
229
|
`parallel_test test/bar test/baz/foo_text.rb`
|
198
230
|
|
199
231
|
- Pass test-options and files via `--`:
|
200
|
-
|
232
|
+
|
201
233
|
`parallel_rspec -- -t acceptance -f progress -- spec/foo_spec.rb spec/acceptance`
|
202
|
-
|
234
|
+
|
203
235
|
- Pass in test options, by using the -o flag (wrap everything in quotes):
|
204
|
-
|
236
|
+
|
205
237
|
`parallel_cucumber -n 2 -o '-p foo_profile --tags @only_this_tag or @only_that_tag --format summary'`
|
206
238
|
|
207
239
|
Options are:
|
@@ -219,8 +251,9 @@ Options are:
|
|
219
251
|
-m, --multiply-processes [FLOAT] use given number as a multiplier of processes to run
|
220
252
|
-s, --single [PATTERN] Run all matching files in the same process
|
221
253
|
-i, --isolate Do not run any other tests in the group used by --single(-s)
|
222
|
-
--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
|
223
255
|
--highest-exit-status Exit with the highest exit status provided by test run(s)
|
256
|
+
--failure-exit-code [INT] Specify the exit code to use when tests fail
|
224
257
|
--specify-groups [SPECS] Use 'specify-groups' if you want to specify multiple specs running in multiple
|
225
258
|
processes in a specific formation. Commas indicate specs in the same process,
|
226
259
|
pipes indicate specs in a new process. Cannot use with --single, --isolate, or
|
@@ -229,7 +262,8 @@ Options are:
|
|
229
262
|
Process 1 will contain 1_spec.rb and 2_spec.rb
|
230
263
|
Process 2 will contain 3_spec.rb
|
231
264
|
Process 3 will contain all other specs
|
232
|
-
--only-group INT[,INT]
|
265
|
+
--only-group INT[,INT] Only run the given group numbers.
|
266
|
+
Changes `--group-by` default to 'filesize'.
|
233
267
|
-e, --exec [COMMAND] execute this code parallel and with ENV['TEST_ENV_NUMBER']
|
234
268
|
-o, --test-options '[OPTIONS]' execute test commands with those options
|
235
269
|
-t, --type [TYPE] test(default) / rspec / cucumber / spinach
|
@@ -246,12 +280,14 @@ Options are:
|
|
246
280
|
--nice execute test commands with low priority.
|
247
281
|
--runtime-log [PATH] Location of previously recorded test runtimes
|
248
282
|
--allowed-missing [INT] Allowed percentage of missing runtimes (default = 50)
|
283
|
+
--allow-duplicates When detecting files to run, allow duplicates
|
249
284
|
--unknown-runtime [FLOAT] Use given number as unknown runtime (otherwise use average time)
|
250
285
|
--first-is-1 Use "1" as TEST_ENV_NUMBER to not reuse the default test environment
|
251
286
|
--fail-fast Stop all groups when one group fails (best used with --test-options '--fail-fast' if supported
|
252
287
|
--verbose Print debug output
|
253
|
-
--verbose-process-command
|
254
|
-
--verbose-
|
288
|
+
--verbose-command Combines options --verbose-process-command and --verbose-rerun-command
|
289
|
+
--verbose-process-command Print the command that will be executed by each process before it begins
|
290
|
+
--verbose-rerun-command After a process fails, print the command executed by that process
|
255
291
|
--quiet Print only tests output
|
256
292
|
-v, --version Show Version
|
257
293
|
-h, --help Show this.
|
@@ -402,6 +438,12 @@ inspired by [pivotal labs](https://blog.pivotal.io/labs/labs/parallelize-your-rs
|
|
402
438
|
- [Vikram B Kumar](https://github.com/v-kumar)
|
403
439
|
- [Joshua Pinter](https://github.com/joshuapinter)
|
404
440
|
- [Zach Dennis](https://github.com/zdennis)
|
441
|
+
- [Jon Dufresne](https://github.com/jdufresne)
|
442
|
+
- [Eric Kessler](https://github.com/enkessler)
|
443
|
+
- [Adis Osmonov](https://github.com/adis-io)
|
444
|
+
- [Josh Westbrook](https://github.com/joshwestbrook)
|
445
|
+
- [Jay Dorsey](https://github.com/jaydorsey)
|
446
|
+
- [hatsu](https://github.com/hatsu38)
|
405
447
|
|
406
448
|
[Michael Grosser](http://grosser.it)<br/>
|
407
449
|
michael@grosser.it<br/>
|
data/lib/parallel_tests/cli.rb
CHANGED
@@ -20,7 +20,7 @@ module ParallelTests
|
|
20
20
|
options[:first_is_1] ||= first_is_1?
|
21
21
|
|
22
22
|
if options[:execute]
|
23
|
-
|
23
|
+
execute_command_in_parallel(options[:execute], num_processes, options)
|
24
24
|
else
|
25
25
|
run_tests_in_parallel(num_processes, options)
|
26
26
|
end
|
@@ -32,9 +32,23 @@ module ParallelTests
|
|
32
32
|
@graceful_shutdown_attempted ||= false
|
33
33
|
Kernel.exit if @graceful_shutdown_attempted
|
34
34
|
|
35
|
-
#
|
36
|
-
#
|
37
|
-
|
35
|
+
# In a shell, all sub-processes also get an interrupt, so they shut themselves down.
|
36
|
+
# In a background process this does not happen and we need to do it ourselves.
|
37
|
+
# We cannot always send the interrupt since then the sub-processes would get interrupted twice when in foreground
|
38
|
+
# and that messes with interrupt handling.
|
39
|
+
#
|
40
|
+
# (can simulate detached with `(bundle exec parallel_rspec test/a_spec.rb -n 2 &)`)
|
41
|
+
# also the integration test "passes on int signal to child processes" is detached.
|
42
|
+
#
|
43
|
+
# On windows getpgid does not work so we resort to always killing which is the smaller bug.
|
44
|
+
#
|
45
|
+
# The ParallelTests::Pids `synchronize` method can't be called directly from a trap,
|
46
|
+
# using Thread workaround https://github.com/ddollar/foreman/issues/332
|
47
|
+
Thread.new do
|
48
|
+
if Gem.win_platform? || ((child_pid = ParallelTests.pids.all.first) && Process.getpgid(child_pid) != Process.pid)
|
49
|
+
ParallelTests.stop_all_processes
|
50
|
+
end
|
51
|
+
end
|
38
52
|
|
39
53
|
@graceful_shutdown_attempted = true
|
40
54
|
end
|
@@ -43,8 +57,8 @@ module ParallelTests
|
|
43
57
|
Tempfile.open 'parallel_tests-lock' do |lock|
|
44
58
|
ParallelTests.with_pid_file do
|
45
59
|
simulate_output_for_ci options[:serialize_stdout] do
|
46
|
-
Parallel.
|
47
|
-
result = yield(item)
|
60
|
+
Parallel.map_with_index(items, in_threads: num_processes) do |item, index|
|
61
|
+
result = yield(item, index)
|
48
62
|
reprint_output(result, lock.path) if options[:serialize_stdout]
|
49
63
|
ParallelTests.stop_all_processes if options[:fail_fast] && result[:exit_status] != 0
|
50
64
|
result
|
@@ -61,20 +75,15 @@ module ParallelTests
|
|
61
75
|
groups = @runner.tests_in_groups(options[:files], num_processes, options)
|
62
76
|
groups.reject!(&:empty?)
|
63
77
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
execute_in_parallel(groups_to_run, groups_to_run.size, options) do |group|
|
68
|
-
run_tests(group, groups_to_run.index(group), 1, options)
|
69
|
-
end
|
70
|
-
else
|
71
|
-
report_number_of_tests(groups) unless options[:quiet]
|
72
|
-
|
73
|
-
execute_in_parallel(groups, groups.size, options) do |group|
|
74
|
-
run_tests(group, groups.index(group), num_processes, options)
|
75
|
-
end
|
78
|
+
if options[:only_group]
|
79
|
+
groups = options[:only_group].map { |i| groups[i - 1] }.compact
|
80
|
+
num_processes = 1
|
76
81
|
end
|
77
82
|
|
83
|
+
report_number_of_tests(groups) unless options[:quiet]
|
84
|
+
test_results = execute_in_parallel(groups, groups.size, options) do |group, index|
|
85
|
+
run_tests(group, index, num_processes, options)
|
86
|
+
end
|
78
87
|
report_results(test_results, options) unless options[:quiet]
|
79
88
|
end
|
80
89
|
|
@@ -87,8 +96,9 @@ module ParallelTests
|
|
87
96
|
if any_test_failed?(test_results)
|
88
97
|
warn final_fail_message
|
89
98
|
|
90
|
-
|
91
|
-
|
99
|
+
exit_status = if options[:failure_exit_code]
|
100
|
+
options[:failure_exit_code]
|
101
|
+
elsif options[:highest_exit_status]
|
92
102
|
test_results.map { |data| data.fetch(:exit_status) }.max
|
93
103
|
else
|
94
104
|
1
|
@@ -100,7 +110,7 @@ module ParallelTests
|
|
100
110
|
|
101
111
|
def run_tests(group, process_number, num_processes, options)
|
102
112
|
if group.empty?
|
103
|
-
{ stdout: '', exit_status: 0, command:
|
113
|
+
{ stdout: '', exit_status: 0, command: nil, seed: nil }
|
104
114
|
else
|
105
115
|
@runner.run_tests(group, process_number, num_processes, options)
|
106
116
|
end
|
@@ -140,9 +150,8 @@ module ParallelTests
|
|
140
150
|
puts "\n\nTests have failed for a parallel_test group. Use the following command to run the group again:\n\n"
|
141
151
|
failing_sets.each do |failing_set|
|
142
152
|
command = failing_set[:command]
|
143
|
-
command = command.gsub(/;export [A-Z_]+;/, ' ') # remove ugly export statements
|
144
153
|
command = @runner.command_with_seed(command, failing_set[:seed]) if failing_set[:seed]
|
145
|
-
|
154
|
+
@runner.print_command(command, failing_set[:env] || {})
|
146
155
|
end
|
147
156
|
end
|
148
157
|
end
|
@@ -215,12 +224,19 @@ module ParallelTests
|
|
215
224
|
opts.on(
|
216
225
|
"--isolate-n [PROCESSES]",
|
217
226
|
Integer,
|
218
|
-
"Use 'isolate' singles with number of processes, default: 1
|
227
|
+
"Use 'isolate' singles with number of processes, default: 1"
|
219
228
|
) { |n| options[:isolate_count] = n }
|
220
229
|
|
221
|
-
opts.on(
|
222
|
-
|
223
|
-
|
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 }
|
224
240
|
|
225
241
|
opts.on(
|
226
242
|
"--specify-groups [SPECS]",
|
@@ -236,10 +252,17 @@ module ParallelTests
|
|
236
252
|
TEXT
|
237
253
|
) { |groups| options[:specify_groups] = groups }
|
238
254
|
|
239
|
-
opts.on(
|
255
|
+
opts.on(
|
256
|
+
"--only-group INT[,INT]",
|
257
|
+
Array,
|
258
|
+
<<~TEXT.rstrip.split("\n").join("\n#{newline_padding}")
|
259
|
+
Only run the given group numbers.
|
260
|
+
Changes `--group-by` default to 'filesize'.
|
261
|
+
TEXT
|
262
|
+
) { |groups| options[:only_group] = groups.map(&:to_i) }
|
240
263
|
|
241
|
-
opts.on("-e", "--exec [COMMAND]", "execute this code parallel and with ENV['TEST_ENV_NUMBER']") { |
|
242
|
-
opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options") { |arg| options[:test_options] = arg
|
264
|
+
opts.on("-e", "--exec [COMMAND]", "execute this code parallel and with ENV['TEST_ENV_NUMBER']") { |arg| options[:execute] = Shellwords.shellsplit(arg) }
|
265
|
+
opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options") { |arg| options[:test_options] = Shellwords.shellsplit(arg) }
|
243
266
|
opts.on("-t", "--type [TYPE]", "test(default) / rspec / cucumber / spinach") do |type|
|
244
267
|
@runner = load_runner(type)
|
245
268
|
rescue NameError, LoadError => e
|
@@ -250,7 +273,7 @@ module ParallelTests
|
|
250
273
|
"--suffix [PATTERN]",
|
251
274
|
<<~TEXT.rstrip.split("\n").join("\n#{newline_padding}")
|
252
275
|
override built in test file pattern (should match suffix):
|
253
|
-
'_spec
|
276
|
+
'_spec.rb$' - matches rspec files
|
254
277
|
'_(test|spec).rb$' - matches test or spec files
|
255
278
|
TEXT
|
256
279
|
) { |pattern| options[:suffix] = /#{pattern}/ }
|
@@ -263,12 +286,14 @@ module ParallelTests
|
|
263
286
|
opts.on("--nice", "execute test commands with low priority.") { options[:nice] = true }
|
264
287
|
opts.on("--runtime-log [PATH]", "Location of previously recorded test runtimes") { |path| options[:runtime_log] = path }
|
265
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 }
|
266
290
|
opts.on("--unknown-runtime [FLOAT]", Float, "Use given number as unknown runtime (otherwise use average time)") { |time| options[:unknown_runtime] = time }
|
267
291
|
opts.on("--first-is-1", "Use \"1\" as TEST_ENV_NUMBER to not reuse the default test environment") { options[:first_is_1] = true }
|
268
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 }
|
269
293
|
opts.on("--verbose", "Print debug output") { options[:verbose] = true }
|
270
|
-
opts.on("--verbose-
|
271
|
-
opts.on("--verbose-
|
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 }
|
272
297
|
opts.on("--quiet", "Print only tests output") { options[:quiet] = true }
|
273
298
|
opts.on("-v", "--version", "Show Version") do
|
274
299
|
puts ParallelTests::VERSION
|
@@ -316,26 +341,30 @@ module ParallelTests
|
|
316
341
|
raise "Can't pass --specify-groups with any of these keys: --single, --isolate, or --isolate-n"
|
317
342
|
end
|
318
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
|
+
|
319
348
|
options
|
320
349
|
end
|
321
350
|
|
322
351
|
def extract_file_paths(argv)
|
323
352
|
dash_index = argv.rindex("--")
|
324
353
|
file_args_at = (dash_index || -1) + 1
|
325
|
-
[argv[file_args_at
|
354
|
+
[argv[file_args_at..], argv[0...(dash_index || 0)]]
|
326
355
|
end
|
327
356
|
|
328
357
|
def extract_test_options(argv)
|
329
358
|
dash_index = argv.index("--") || -1
|
330
|
-
argv[dash_index + 1
|
359
|
+
argv[dash_index + 1..]
|
331
360
|
end
|
332
361
|
|
333
362
|
def append_test_options(options, argv)
|
334
363
|
new_opts = extract_test_options(argv)
|
335
364
|
return if new_opts.empty?
|
336
365
|
|
337
|
-
|
338
|
-
options[:test_options]
|
366
|
+
options[:test_options] ||= []
|
367
|
+
options[:test_options] += new_opts
|
339
368
|
end
|
340
369
|
|
341
370
|
def load_runner(type)
|
@@ -345,7 +374,7 @@ module ParallelTests
|
|
345
374
|
klass_name.split('::').inject(Object) { |x, y| x.const_get(y) }
|
346
375
|
end
|
347
376
|
|
348
|
-
def
|
377
|
+
def execute_command_in_parallel(command, num_processes, options)
|
349
378
|
runs = if options[:only_group]
|
350
379
|
options[:only_group].map { |g| g - 1 }
|
351
380
|
else
|
@@ -397,7 +426,7 @@ module ParallelTests
|
|
397
426
|
def simulate_output_for_ci(simulate)
|
398
427
|
if simulate
|
399
428
|
progress_indicator = Thread.new do
|
400
|
-
interval = Float(ENV
|
429
|
+
interval = Float(ENV['PARALLEL_TEST_HEARTBEAT_INTERVAL'] || 60)
|
401
430
|
loop do
|
402
431
|
sleep interval
|
403
432
|
print '.'
|
@@ -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
|
@@ -35,8 +35,8 @@ module ParallelTests
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def command_with_seed(cmd, seed)
|
38
|
-
clean = cmd
|
39
|
-
|
38
|
+
clean = remove_command_arguments(cmd, '--order')
|
39
|
+
[*clean, '--order', "random:#{seed}"]
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
@@ -27,7 +27,7 @@ module ParallelTests
|
|
27
27
|
example_tags = example.tags.map(&:name)
|
28
28
|
example_tags = scenario_tags + example_tags
|
29
29
|
next unless matches_tags?(example_tags)
|
30
|
-
example.rows[1
|
30
|
+
example.rows[1..].each do |row|
|
31
31
|
test_line = row.source_line
|
32
32
|
next if line_numbers.any? && !line_numbers.include?(test_line)
|
33
33
|
|
@@ -4,7 +4,6 @@ require 'cucumber/runtime'
|
|
4
4
|
require 'cucumber'
|
5
5
|
require 'parallel_tests/cucumber/scenario_line_logger'
|
6
6
|
require 'parallel_tests/gherkin/listener'
|
7
|
-
require 'shellwords'
|
8
7
|
|
9
8
|
begin
|
10
9
|
gem "cuke_modeler", "~> 3.0"
|
@@ -20,7 +19,7 @@ module ParallelTests
|
|
20
19
|
def all(files, options = {})
|
21
20
|
# Parse tag expression from given test options and ignore tag pattern. Refer here to understand how new tag expression syntax works - https://github.com/cucumber/cucumber/tree/master/tag-expressions
|
22
21
|
tags = []
|
23
|
-
words = options[:test_options]
|
22
|
+
words = options[:test_options] || []
|
24
23
|
words.each_with_index { |w, i| tags << words[i + 1] if ["-t", "--tags"].include?(w) }
|
25
24
|
if ignore = options[:ignore_tag_pattern]
|
26
25
|
tags << "not (#{ignore})"
|
@@ -53,7 +52,9 @@ module ParallelTests
|
|
53
52
|
feature_tags = feature.tags.map(&:name)
|
54
53
|
|
55
54
|
# We loop on each children of the feature
|
56
|
-
feature.tests
|
55
|
+
test_models = feature.tests
|
56
|
+
test_models += feature.rules.flat_map(&:tests) if feature.respond_to?(:rules) # cuke_modeler >= 3.2 supports rules
|
57
|
+
test_models.each do |test|
|
57
58
|
# It's a scenario, we add it to the scenario_line_logger
|
58
59
|
scenario_line_logger.visit_feature_element(document.path, test, feature_tags, line_numbers: test_lines)
|
59
60
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "parallel_tests/test/runner"
|
3
|
-
require 'shellwords'
|
4
3
|
|
5
4
|
module ParallelTests
|
6
5
|
module Gherkin
|
@@ -16,17 +15,13 @@ module ParallelTests
|
|
16
15
|
end
|
17
16
|
end
|
18
17
|
|
19
|
-
sanitized_test_files = combined_scenarios.map { |val| WINDOWS ? "\"#{val}\"" : Shellwords.escape(val) }
|
20
|
-
|
21
18
|
options[:env] ||= {}
|
22
19
|
options[:env] = options[:env].merge({ 'AUTOTEST' => '1' }) if $stdout.tty?
|
23
20
|
|
24
|
-
cmd =
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
cucumber_opts(options[:test_options])
|
29
|
-
].compact.reject(&:empty?).join(' ')
|
21
|
+
cmd = executable
|
22
|
+
cmd += runtime_logging if File.directory?(File.dirname(runtime_log))
|
23
|
+
cmd += combined_scenarios
|
24
|
+
cmd += cucumber_opts(options[:test_options])
|
30
25
|
execute_command(cmd, process_number, num_processes, options)
|
31
26
|
end
|
32
27
|
|
@@ -62,22 +57,22 @@ module ParallelTests
|
|
62
57
|
plural = "s" if (word == group) && (number != 1)
|
63
58
|
"#{number} #{word}#{plural}"
|
64
59
|
end
|
65
|
-
"#{sums[0]} (#{sums[1
|
60
|
+
"#{sums[0]} (#{sums[1..].join(", ")})"
|
66
61
|
end.compact.join("\n")
|
67
62
|
end
|
68
63
|
|
69
64
|
def cucumber_opts(given)
|
70
|
-
if given
|
65
|
+
if given&.include?('--profile') || given&.include?('-p')
|
71
66
|
given
|
72
67
|
else
|
73
|
-
[given, profile_from_config]
|
68
|
+
[*given, *profile_from_config]
|
74
69
|
end
|
75
70
|
end
|
76
71
|
|
77
72
|
def profile_from_config
|
78
73
|
# copied from https://github.com/cucumber/cucumber/blob/master/lib/cucumber/cli/profile_loader.rb#L85
|
79
74
|
config = Dir.glob("{,.config/,config/}#{name}{.yml,.yaml}").first
|
80
|
-
|
75
|
+
['--profile', 'parallel'] if config && File.read(config) =~ /^parallel:/
|
81
76
|
end
|
82
77
|
|
83
78
|
def tests_in_groups(tests, num_groups, options = {})
|
@@ -91,7 +86,7 @@ module ParallelTests
|
|
91
86
|
end
|
92
87
|
|
93
88
|
def runtime_logging
|
94
|
-
|
89
|
+
['--format', 'ParallelTests::Gherkin::RuntimeLogger', '--out', runtime_log]
|
95
90
|
end
|
96
91
|
|
97
92
|
def runtime_log
|
@@ -102,11 +97,11 @@ module ParallelTests
|
|
102
97
|
if File.exist?("bin/#{name}")
|
103
98
|
ParallelTests.with_ruby_binary("bin/#{name}")
|
104
99
|
elsif ParallelTests.bundler_enabled?
|
105
|
-
"bundle exec
|
100
|
+
["bundle", "exec", name]
|
106
101
|
elsif File.file?("script/#{name}")
|
107
102
|
ParallelTests.with_ruby_binary("script/#{name}")
|
108
103
|
else
|
109
|
-
name.to_s
|
104
|
+
[name.to_s]
|
110
105
|
end
|
111
106
|
end
|
112
107
|
end
|
@@ -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
|
@@ -38,7 +34,7 @@ module ParallelTests
|
|
38
34
|
# add all files that should run in a multiple isolated processes to their own groups
|
39
35
|
group_features_by_size(items_to_group(single_items), groups[0..(isolate_count - 1)])
|
40
36
|
# group the non-isolated by size
|
41
|
-
group_features_by_size(items_to_group(items), groups[isolate_count
|
37
|
+
group_features_by_size(items_to_group(items), groups[isolate_count..])
|
42
38
|
else
|
43
39
|
# add all files that should run in a single non-isolated process to first group
|
44
40
|
single_items.each { |item, size| add_to_group(groups.first, item, size) }
|
data/lib/parallel_tests/pids.rb
CHANGED
@@ -43,14 +43,14 @@ module ParallelTests
|
|
43
43
|
|
44
44
|
def read
|
45
45
|
sync do
|
46
|
-
contents =
|
46
|
+
contents = File.read(file_path)
|
47
47
|
return if contents.empty?
|
48
48
|
@pids = JSON.parse(contents)
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
52
|
def save
|
53
|
-
sync {
|
53
|
+
sync { File.write(file_path, pids.to_json) }
|
54
54
|
end
|
55
55
|
|
56
56
|
def sync(&block)
|