parallel_tests 2.28.0 → 3.7.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 +46 -28
- data/bin/parallel_cucumber +2 -1
- data/bin/parallel_rspec +2 -1
- data/bin/parallel_spinach +2 -1
- data/bin/parallel_test +2 -1
- data/lib/parallel_tests.rb +12 -12
- data/lib/parallel_tests/cli.rb +133 -68
- data/lib/parallel_tests/cucumber/failures_logger.rb +1 -1
- data/lib/parallel_tests/cucumber/features_with_steps.rb +32 -0
- data/lib/parallel_tests/cucumber/runner.rb +8 -5
- data/lib/parallel_tests/cucumber/scenario_line_logger.rb +18 -16
- data/lib/parallel_tests/cucumber/scenarios.rb +20 -30
- data/lib/parallel_tests/gherkin/io.rb +2 -3
- data/lib/parallel_tests/gherkin/listener.rb +10 -12
- data/lib/parallel_tests/gherkin/runner.rb +20 -21
- data/lib/parallel_tests/gherkin/runtime_logger.rb +3 -2
- data/lib/parallel_tests/grouper.rb +92 -28
- data/lib/parallel_tests/pids.rb +4 -3
- data/lib/parallel_tests/railtie.rb +1 -0
- data/lib/parallel_tests/rspec/failures_logger.rb +2 -2
- data/lib/parallel_tests/rspec/logger_base.rb +9 -7
- data/lib/parallel_tests/rspec/runner.rb +27 -12
- data/lib/parallel_tests/rspec/runtime_logger.rb +12 -10
- data/lib/parallel_tests/rspec/summary_logger.rb +2 -3
- data/lib/parallel_tests/spinach/runner.rb +6 -2
- data/lib/parallel_tests/tasks.rb +81 -40
- data/lib/parallel_tests/test/runner.rb +54 -37
- data/lib/parallel_tests/test/runtime_logger.rb +19 -14
- data/lib/parallel_tests/version.rb +2 -1
- metadata +11 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 52c2ebdfc604b9e238867ea3d7d78cb919d60c951ab45bd2ead93db8893352f4
|
|
4
|
+
data.tar.gz: 441cf2b41a814746e1704fd794cd147638dbd261f1b294fd74b49adb9bc6d0da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a20853e2eac4f7b4260ea3ac3a9515b3d83fa3b47ff411a5f9567fcde257044825b2e9a81047e01f4d72722c02a6fdd2dbffba026dafb236cff8c2be2b712134
|
|
7
|
+
data.tar.gz: 4483fac06b11b0615b2bd39dcc5487713a02105cac547cf50177ec30805a8b17ebdbbac679e88a84f57da702235998cc0f67b80e382ea0bd27e784f4e89639a0
|
data/Readme.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/parallel_tests)
|
|
4
4
|
[](https://travis-ci.org/grosser/parallel_tests/builds)
|
|
5
|
-
[](https://github.com/grosser/parallel_tests/actions?query=workflow%3Awindows)
|
|
6
6
|
|
|
7
7
|
Speedup Test::Unit + RSpec + Cucumber + Spinach by running parallel on multiple CPU cores.<br/>
|
|
8
8
|
ParallelTests splits tests into even groups (by number of lines or runtime) and runs each group in a single process with its own database.
|
|
@@ -37,6 +37,9 @@ test:
|
|
|
37
37
|
### Copy development schema (repeat after migrations)
|
|
38
38
|
rake parallel:prepare
|
|
39
39
|
|
|
40
|
+
### Run migrations in additional database(s) (repeat after migrations)
|
|
41
|
+
rake parallel:migrate
|
|
42
|
+
|
|
40
43
|
### Setup environment from scratch (create db and loads schema, useful for CI)
|
|
41
44
|
rake parallel:setup
|
|
42
45
|
|
|
@@ -82,6 +85,8 @@ Running things once
|
|
|
82
85
|
===================
|
|
83
86
|
|
|
84
87
|
```Ruby
|
|
88
|
+
require "parallel_tests"
|
|
89
|
+
|
|
85
90
|
# preparation:
|
|
86
91
|
# affected by race-condition: first process may boot slower than the second
|
|
87
92
|
# either sleep a bit or use a lock for example File.lock
|
|
@@ -97,7 +102,6 @@ at_exit do
|
|
|
97
102
|
undo_something
|
|
98
103
|
end
|
|
99
104
|
end
|
|
100
|
-
|
|
101
105
|
```
|
|
102
106
|
|
|
103
107
|
Even test group run-times
|
|
@@ -141,17 +145,19 @@ Add the following to your `.rspec_parallel` (or `.rspec`) :
|
|
|
141
145
|
RSpec: FailuresLogger
|
|
142
146
|
-----------------------
|
|
143
147
|
|
|
144
|
-
Produce
|
|
148
|
+
Produce pastable command-line snippets for each failed example. For example:
|
|
145
149
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
```bash
|
|
151
|
+
rspec /path/to/my_spec.rb:123 # should do something
|
|
152
|
+
```
|
|
149
153
|
|
|
150
|
-
Add
|
|
154
|
+
Add to `.rspec_parallel` or use as CLI flag:
|
|
151
155
|
|
|
152
156
|
--format progress
|
|
153
157
|
--format ParallelTests::RSpec::FailuresLogger --out tmp/failing_specs.log
|
|
154
158
|
|
|
159
|
+
(Not needed to retry failures, for that pass [--only-failures](https://relishapp.com/rspec/rspec-core/docs/command-line/only-failures) to rspec)
|
|
160
|
+
|
|
155
161
|
Cucumber: FailuresLogger
|
|
156
162
|
-----------------------
|
|
157
163
|
|
|
@@ -192,27 +198,36 @@ Setup for non-rails
|
|
|
192
198
|
|
|
193
199
|
Options are:
|
|
194
200
|
<!-- copy output from bundle exec ./bin/parallel_test -h -->
|
|
195
|
-
|
|
196
201
|
-n [PROCESSES] How many processes to use, default: available CPUs
|
|
197
202
|
-p, --pattern [PATTERN] run tests matching this regex pattern
|
|
198
203
|
--exclude-pattern [PATTERN] exclude tests matching this regex pattern
|
|
199
204
|
--group-by [TYPE] group tests by:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
205
|
+
found - order of finding files
|
|
206
|
+
steps - number of cucumber/spinach steps
|
|
207
|
+
scenarios - individual cucumber scenarios
|
|
208
|
+
filesize - by size of the file
|
|
209
|
+
runtime - info from runtime log
|
|
210
|
+
default - runtime when runtime log is filled otherwise filesize
|
|
206
211
|
-m, --multiply-processes [FLOAT] use given number as a multiplier of processes to run
|
|
207
212
|
-s, --single [PATTERN] Run all matching files in the same process
|
|
208
213
|
-i, --isolate Do not run any other tests in the group used by --single(-s)
|
|
209
|
-
--
|
|
214
|
+
--isolate-n [PROCESSES] Use 'isolate' singles with number of processes, default: 1.
|
|
215
|
+
--highest-exit-status Exit with the highest exit status provided by test run(s)
|
|
216
|
+
--specify-groups [SPECS] Use 'specify-groups' if you want to specify multiple specs running in multiple
|
|
217
|
+
processes in a specific formation. Commas indicate specs in the same process,
|
|
218
|
+
pipes indicate specs in a new process. Cannot use with --single, --isolate, or
|
|
219
|
+
--isolate-n. Ex.
|
|
220
|
+
$ parallel_tests -n 3 . --specify-groups '1_spec.rb,2_spec.rb|3_spec.rb'
|
|
221
|
+
Process 1 will contain 1_spec.rb and 2_spec.rb
|
|
222
|
+
Process 2 will contain 3_spec.rb
|
|
223
|
+
Process 3 will contain all other specs
|
|
224
|
+
--only-group INT[,INT]
|
|
210
225
|
-e, --exec [COMMAND] execute this code parallel and with ENV['TEST_ENV_NUMBER']
|
|
211
226
|
-o, --test-options '[OPTIONS]' execute test commands with those options
|
|
212
227
|
-t, --type [TYPE] test(default) / rspec / cucumber / spinach
|
|
213
228
|
--suffix [PATTERN] override built in test file pattern (should match suffix):
|
|
214
|
-
|
|
215
|
-
|
|
229
|
+
'_spec.rb$' - matches rspec files
|
|
230
|
+
'_(test|spec).rb$' - matches test or spec files
|
|
216
231
|
--serialize-stdout Serialize stdout output, nothing will be written until everything is done
|
|
217
232
|
--prefix-output-with-test-env-number
|
|
218
233
|
Prefixes test env number to the output when not using --serialize-stdout
|
|
@@ -222,10 +237,14 @@ Options are:
|
|
|
222
237
|
--ignore-tags [PATTERN] When counting steps ignore scenarios with tags that match this pattern
|
|
223
238
|
--nice execute test commands with low priority.
|
|
224
239
|
--runtime-log [PATH] Location of previously recorded test runtimes
|
|
225
|
-
--allowed-missing
|
|
240
|
+
--allowed-missing [INT] Allowed percentage of missing runtimes (default = 50)
|
|
226
241
|
--unknown-runtime [FLOAT] Use given number as unknown runtime (otherwise use average time)
|
|
227
|
-
--
|
|
228
|
-
--
|
|
242
|
+
--first-is-1 Use "1" as TEST_ENV_NUMBER to not reuse the default test environment
|
|
243
|
+
--fail-fast Stop all groups when one group fails (best used with --test-options '--fail-fast' if supported
|
|
244
|
+
--verbose Print debug output
|
|
245
|
+
--verbose-process-command Displays only the command that will be executed by each process
|
|
246
|
+
--verbose-rerun-command When there are failures, displays the command executed by each process that failed
|
|
247
|
+
--quiet Print only tests output
|
|
229
248
|
-v, --version Show Version
|
|
230
249
|
-h, --help Show this.
|
|
231
250
|
|
|
@@ -254,6 +273,7 @@ TIPS
|
|
|
254
273
|
- Instantly see failures (instead of just a red F) with [rspec-instafail](https://github.com/grosser/rspec-instafail)
|
|
255
274
|
- Use [rspec-retry](https://github.com/NoRedInk/rspec-retry) (not rspec-rerun) to rerun failed tests.
|
|
256
275
|
- [JUnit formatter configuration](https://github.com/grosser/parallel_tests/wiki#with-rspec_junit_formatter----by-jgarber)
|
|
276
|
+
- Use [parallel_split_test](https://github.com/grosser/parallel_split_test) to run multiple scenarios in a single spec file, concurrently. (`parallel_tests` [works at the file-level and intends to stay that way](https://github.com/grosser/parallel_tests/issues/747#issuecomment-580216980))
|
|
257
277
|
|
|
258
278
|
### Cucumber
|
|
259
279
|
|
|
@@ -264,30 +284,24 @@ TIPS
|
|
|
264
284
|
- Builds a HTML report from JSON with support for debug msgs & embedded Base64 images.
|
|
265
285
|
|
|
266
286
|
### General
|
|
267
|
-
- [SQL schema format] use :ruby schema format to get faster parallel:prepare`
|
|
268
287
|
- [ZSH] use quotes to use rake arguments `rake "parallel:prepare[3]"`
|
|
269
288
|
- [Memcached] use different namespaces<br/>
|
|
270
289
|
e.g. `config.cache_store = ..., namespace: "test_#{ENV['TEST_ENV_NUMBER']}"`
|
|
271
290
|
- Debug errors that only happen with multiple files using `--verbose` and [cleanser](https://github.com/grosser/cleanser)
|
|
272
291
|
- `export PARALLEL_TEST_PROCESSORS=13` to override default processor count
|
|
273
292
|
- Shell alias: `alias prspec='parallel_rspec -m 2 --'`
|
|
274
|
-
- [Spring]
|
|
293
|
+
- [Spring] Add the [spring-commands-parallel-tests](https://github.com/DocSpring/spring-commands-parallel-tests) gem to your `Gemfile` to get `parallel_tests` working with Spring.
|
|
275
294
|
- `--first-is-1` will make the first environment be `1`, so you can test while running your full suite.<br/>
|
|
276
295
|
`export PARALLEL_TEST_FIRST_IS_1=true` will provide the same result
|
|
277
296
|
- [email_spec and/or action_mailer_cache_delivery](https://github.com/grosser/parallel_tests/wiki)
|
|
278
297
|
- [zeus-parallel_tests](https://github.com/sevos/zeus-parallel_tests)
|
|
279
|
-
- [Distributed
|
|
298
|
+
- [Distributed Parallel Tests on CI systems)](https://github.com/grosser/parallel_tests/wiki/Distributed-Parallel-Tests-on-CI-systems) learn how `parallel_tests` can run on distributed servers such as Travis and GitLab-CI. Also shows you how to use parallel_tests without adding `TEST_ENV_NUMBER`-backends
|
|
280
299
|
- [Capybara setup](https://github.com/grosser/parallel_tests/wiki)
|
|
281
300
|
- [Sphinx setup](https://github.com/grosser/parallel_tests/wiki)
|
|
282
301
|
- [Capistrano setup](https://github.com/grosser/parallel_tests/wiki/Remotely-with-capistrano) let your tests run on a big box instead of your laptop
|
|
283
302
|
|
|
284
303
|
Contribute your own gotchas to the [Wiki](https://github.com/grosser/parallel_tests/wiki) or even better open a PR :)
|
|
285
304
|
|
|
286
|
-
TODO
|
|
287
|
-
====
|
|
288
|
-
- fix tests vs cucumber >= 1.2 `unknown option --format`
|
|
289
|
-
- add unit tests for cucumber runtime formatter
|
|
290
|
-
|
|
291
305
|
Authors
|
|
292
306
|
====
|
|
293
307
|
inspired by [pivotal labs](https://blog.pivotal.io/labs/labs/parallelize-your-rspec-suite)
|
|
@@ -375,6 +389,10 @@ inspired by [pivotal labs](https://blog.pivotal.io/labs/labs/parallelize-your-rs
|
|
|
375
389
|
- [Justin Doody](https://github.com/justindoody)
|
|
376
390
|
- [Sandeep Singh](https://github.com/sandeepnagra)
|
|
377
391
|
- [Calaway](https://github.com/calaway)
|
|
392
|
+
- [alboyadjian](https://github.com/alboyadjian)
|
|
393
|
+
- [Nathan Broadbent](https://github.com/ndbroadbent)
|
|
394
|
+
- [Vikram B Kumar](https://github.com/v-kumar)
|
|
395
|
+
- [Joshua Pinter](https://github.com/joshuapinter)
|
|
378
396
|
|
|
379
397
|
[Michael Grosser](http://grosser.it)<br/>
|
|
380
398
|
michael@grosser.it<br/>
|
data/bin/parallel_cucumber
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
# enable local usage from cloned repo
|
|
4
|
-
root = File.expand_path(
|
|
5
|
+
root = File.expand_path('..', __dir__)
|
|
5
6
|
$LOAD_PATH << "#{root}/lib" if File.exist?("#{root}/Gemfile")
|
|
6
7
|
|
|
7
8
|
require "parallel_tests"
|
data/bin/parallel_rspec
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
# enable local usage from cloned repo
|
|
4
|
-
root = File.expand_path(
|
|
5
|
+
root = File.expand_path('..', __dir__)
|
|
5
6
|
$LOAD_PATH << "#{root}/lib" if File.exist?("#{root}/Gemfile")
|
|
6
7
|
|
|
7
8
|
require "parallel_tests"
|
data/bin/parallel_spinach
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
# enable local usage from cloned repo
|
|
4
|
-
root = File.expand_path(
|
|
5
|
+
root = File.expand_path('..', __dir__)
|
|
5
6
|
$LOAD_PATH << "#{root}/lib" if File.exist?("#{root}/Gemfile")
|
|
6
7
|
|
|
7
8
|
require "parallel_tests"
|
data/bin/parallel_test
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
# enable local usage from cloned repo
|
|
4
|
-
root = File.expand_path(
|
|
5
|
+
root = File.expand_path('..', __dir__)
|
|
5
6
|
$LOAD_PATH << "#{root}/lib" if File.exist?("#{root}/Gemfile")
|
|
6
7
|
|
|
7
8
|
require "parallel_tests"
|
data/lib/parallel_tests.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require "parallel"
|
|
2
3
|
require "parallel_tests/railtie" if defined? Rails::Railtie
|
|
3
4
|
require "rbconfig"
|
|
@@ -17,21 +18,19 @@ module ParallelTests
|
|
|
17
18
|
count,
|
|
18
19
|
ENV["PARALLEL_TEST_PROCESSORS"],
|
|
19
20
|
Parallel.processor_count
|
|
20
|
-
].detect{|c|
|
|
21
|
+
].detect { |c| !c.to_s.strip.empty? }.to_i
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def with_pid_file
|
|
24
25
|
Tempfile.open('parallel_tests-pidfile') do |f|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@pids = nil
|
|
34
|
-
end
|
|
26
|
+
ENV['PARALLEL_PID_FILE'] = f.path
|
|
27
|
+
# Pids object should be created before threads will start adding pids to it
|
|
28
|
+
# Otherwise we would have to use Mutex to prevent creation of several instances
|
|
29
|
+
@pids = pids
|
|
30
|
+
yield
|
|
31
|
+
ensure
|
|
32
|
+
ENV['PARALLEL_PID_FILE'] = nil
|
|
33
|
+
@pids = nil
|
|
35
34
|
end
|
|
36
35
|
end
|
|
37
36
|
|
|
@@ -57,7 +56,8 @@ module ParallelTests
|
|
|
57
56
|
until !File.directory?(current) || current == previous
|
|
58
57
|
filename = File.join(current, "Gemfile")
|
|
59
58
|
return true if File.exist?(filename)
|
|
60
|
-
|
|
59
|
+
previous = current
|
|
60
|
+
current = File.expand_path("..", current)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
false
|
data/lib/parallel_tests/cli.rb
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'optparse'
|
|
2
3
|
require 'tempfile'
|
|
3
4
|
require 'parallel_tests'
|
|
4
5
|
require 'shellwords'
|
|
6
|
+
require 'pathname'
|
|
5
7
|
|
|
6
8
|
module ParallelTests
|
|
7
9
|
class CLI
|
|
@@ -13,7 +15,7 @@ module ParallelTests
|
|
|
13
15
|
ENV['DISABLE_SPRING'] ||= '1'
|
|
14
16
|
|
|
15
17
|
num_processes = ParallelTests.determine_number_of_processes(options[:count])
|
|
16
|
-
num_processes
|
|
18
|
+
num_processes *= (options[:multiply] || 1)
|
|
17
19
|
|
|
18
20
|
options[:first_is_1] ||= first_is_1?
|
|
19
21
|
|
|
@@ -41,9 +43,10 @@ module ParallelTests
|
|
|
41
43
|
Tempfile.open 'parallel_tests-lock' do |lock|
|
|
42
44
|
ParallelTests.with_pid_file do
|
|
43
45
|
simulate_output_for_ci options[:serialize_stdout] do
|
|
44
|
-
Parallel.map(items, :
|
|
46
|
+
Parallel.map(items, in_threads: num_processes) do |item|
|
|
45
47
|
result = yield(item)
|
|
46
48
|
reprint_output(result, lock.path) if options[:serialize_stdout]
|
|
49
|
+
ParallelTests.stop_all_processes if options[:fail_fast] && result[:exit_status] != 0
|
|
47
50
|
result
|
|
48
51
|
end
|
|
49
52
|
end
|
|
@@ -54,12 +57,12 @@ module ParallelTests
|
|
|
54
57
|
def run_tests_in_parallel(num_processes, options)
|
|
55
58
|
test_results = nil
|
|
56
59
|
|
|
57
|
-
run_tests_proc = ->
|
|
60
|
+
run_tests_proc = -> do
|
|
58
61
|
groups = @runner.tests_in_groups(options[:files], num_processes, options)
|
|
59
|
-
groups.reject!
|
|
62
|
+
groups.reject!(&:empty?)
|
|
60
63
|
|
|
61
64
|
test_results = if options[:only_group]
|
|
62
|
-
groups_to_run = options[:only_group].
|
|
65
|
+
groups_to_run = options[:only_group].map { |i| groups[i - 1] }.compact
|
|
63
66
|
report_number_of_tests(groups_to_run) unless options[:quiet]
|
|
64
67
|
execute_in_parallel(groups_to_run, groups_to_run.size, options) do |group|
|
|
65
68
|
run_tests(group, groups_to_run.index(group), 1, options)
|
|
@@ -73,7 +76,7 @@ module ParallelTests
|
|
|
73
76
|
end
|
|
74
77
|
|
|
75
78
|
report_results(test_results, options) unless options[:quiet]
|
|
76
|
-
|
|
79
|
+
end
|
|
77
80
|
|
|
78
81
|
if options[:quiet]
|
|
79
82
|
run_tests_proc.call
|
|
@@ -81,12 +84,23 @@ module ParallelTests
|
|
|
81
84
|
report_time_taken(&run_tests_proc)
|
|
82
85
|
end
|
|
83
86
|
|
|
84
|
-
|
|
87
|
+
if any_test_failed?(test_results)
|
|
88
|
+
warn final_fail_message
|
|
89
|
+
|
|
90
|
+
# return the highest exit status to allow sub-processes to send things other than 1
|
|
91
|
+
exit_status = if options[:highest_exit_status]
|
|
92
|
+
test_results.map { |data| data.fetch(:exit_status) }.max
|
|
93
|
+
else
|
|
94
|
+
1
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
exit exit_status
|
|
98
|
+
end
|
|
85
99
|
end
|
|
86
100
|
|
|
87
101
|
def run_tests(group, process_number, num_processes, options)
|
|
88
102
|
if group.empty?
|
|
89
|
-
{:
|
|
103
|
+
{ stdout: '', exit_status: 0, command: '', seed: nil }
|
|
90
104
|
else
|
|
91
105
|
@runner.run_tests(group, process_number, num_processes, options)
|
|
92
106
|
end
|
|
@@ -102,18 +116,16 @@ module ParallelTests
|
|
|
102
116
|
|
|
103
117
|
def lock(lockfile)
|
|
104
118
|
File.open(lockfile) do |lock|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
lock.flock File::LOCK_UN
|
|
111
|
-
end
|
|
119
|
+
lock.flock File::LOCK_EX
|
|
120
|
+
yield
|
|
121
|
+
ensure
|
|
122
|
+
# This shouldn't be necessary, but appears to be
|
|
123
|
+
lock.flock File::LOCK_UN
|
|
112
124
|
end
|
|
113
125
|
end
|
|
114
126
|
|
|
115
127
|
def report_results(test_results, options)
|
|
116
|
-
results = @runner.find_results(test_results.map { |result| result[:stdout] }*"")
|
|
128
|
+
results = @runner.find_results(test_results.map { |result| result[:stdout] } * "")
|
|
117
129
|
puts ""
|
|
118
130
|
puts @runner.summarize_results(results)
|
|
119
131
|
|
|
@@ -124,7 +136,7 @@ module ParallelTests
|
|
|
124
136
|
failing_sets = test_results.reject { |r| r[:exit_status] == 0 }
|
|
125
137
|
return if failing_sets.none?
|
|
126
138
|
|
|
127
|
-
if options[:verbose]
|
|
139
|
+
if options[:verbose] || options[:verbose_rerun_command]
|
|
128
140
|
puts "\n\nTests have failed for a parallel_test group. Use the following command to run the group again:\n\n"
|
|
129
141
|
failing_sets.each do |failing_set|
|
|
130
142
|
command = failing_set[:command]
|
|
@@ -138,20 +150,31 @@ module ParallelTests
|
|
|
138
150
|
def report_number_of_tests(groups)
|
|
139
151
|
name = @runner.test_file_name
|
|
140
152
|
num_processes = groups.size
|
|
141
|
-
num_tests = groups.map(&:size).
|
|
153
|
+
num_tests = groups.map(&:size).sum
|
|
142
154
|
tests_per_process = (num_processes == 0 ? 0 : num_tests / num_processes)
|
|
143
|
-
puts "#{num_processes}
|
|
155
|
+
puts "#{pluralize(num_processes, 'process')} for #{pluralize(num_tests, name)}, ~ #{pluralize(tests_per_process, name)} per process"
|
|
144
156
|
end
|
|
145
157
|
|
|
146
|
-
|
|
158
|
+
def pluralize(n, singular)
|
|
159
|
+
if n == 1
|
|
160
|
+
"1 #{singular}"
|
|
161
|
+
elsif singular.end_with?('s', 'sh', 'ch', 'x', 'z')
|
|
162
|
+
"#{n} #{singular}es"
|
|
163
|
+
else
|
|
164
|
+
"#{n} #{singular}s"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# exit with correct status code so rake parallel:test && echo 123 works
|
|
147
169
|
def any_test_failed?(test_results)
|
|
148
170
|
test_results.any? { |result| result[:exit_status] != 0 }
|
|
149
171
|
end
|
|
150
172
|
|
|
151
173
|
def parse_options!(argv)
|
|
174
|
+
newline_padding = " " * 37
|
|
152
175
|
options = {}
|
|
153
176
|
OptionParser.new do |opts|
|
|
154
|
-
opts.banner =
|
|
177
|
+
opts.banner = <<~BANNER
|
|
155
178
|
Run all tests in parallel, giving each process ENV['TEST_ENV_NUMBER'] ('', '2', '3', ...)
|
|
156
179
|
|
|
157
180
|
[optional] Only selected files & folders:
|
|
@@ -165,69 +188,99 @@ module ParallelTests
|
|
|
165
188
|
opts.on("-n [PROCESSES]", Integer, "How many processes to use, default: available CPUs") { |n| options[:count] = n }
|
|
166
189
|
opts.on("-p", "--pattern [PATTERN]", "run tests matching this regex pattern") { |pattern| options[:pattern] = /#{pattern}/ }
|
|
167
190
|
opts.on("--exclude-pattern", "--exclude-pattern [PATTERN]", "exclude tests matching this regex pattern") { |pattern| options[:exclude_pattern] = /#{pattern}/ }
|
|
168
|
-
opts.on(
|
|
169
|
-
group
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
191
|
+
opts.on(
|
|
192
|
+
"--group-by [TYPE]",
|
|
193
|
+
<<~TEXT.rstrip.split("\n").join("\n#{newline_padding}")
|
|
194
|
+
group tests by:
|
|
195
|
+
found - order of finding files
|
|
196
|
+
steps - number of cucumber/spinach steps
|
|
197
|
+
scenarios - individual cucumber scenarios
|
|
198
|
+
filesize - by size of the file
|
|
199
|
+
runtime - info from runtime log
|
|
200
|
+
default - runtime when runtime log is filled otherwise filesize
|
|
176
201
|
TEXT
|
|
177
|
-
|
|
178
|
-
opts.on("-m [FLOAT]", "--multiply-processes [FLOAT]", Float, "use given number as a multiplier of processes to run")
|
|
202
|
+
) { |type| options[:group_by] = type.to_sym }
|
|
203
|
+
opts.on("-m [FLOAT]", "--multiply-processes [FLOAT]", Float, "use given number as a multiplier of processes to run") do |multiply|
|
|
204
|
+
options[:multiply] = multiply
|
|
205
|
+
end
|
|
179
206
|
|
|
180
|
-
opts.on("-s [PATTERN]", "--single [PATTERN]",
|
|
181
|
-
|
|
207
|
+
opts.on("-s [PATTERN]", "--single [PATTERN]", "Run all matching files in the same process") do |pattern|
|
|
208
|
+
(options[:single_process] ||= []) << /#{pattern}/
|
|
209
|
+
end
|
|
182
210
|
|
|
183
|
-
|
|
184
|
-
options[:
|
|
211
|
+
opts.on("-i", "--isolate", "Do not run any other tests in the group used by --single(-s)") do
|
|
212
|
+
options[:isolate] = true
|
|
185
213
|
end
|
|
186
214
|
|
|
187
|
-
opts.on(
|
|
188
|
-
"
|
|
215
|
+
opts.on(
|
|
216
|
+
"--isolate-n [PROCESSES]",
|
|
217
|
+
Integer,
|
|
218
|
+
"Use 'isolate' singles with number of processes, default: 1."
|
|
219
|
+
) { |n| options[:isolate_count] = n }
|
|
189
220
|
|
|
190
|
-
|
|
221
|
+
opts.on("--highest-exit-status", "Exit with the highest exit status provided by test run(s)") do
|
|
222
|
+
options[:highest_exit_status] = true
|
|
191
223
|
end
|
|
192
224
|
|
|
193
|
-
opts.on(
|
|
225
|
+
opts.on(
|
|
226
|
+
"--specify-groups [SPECS]",
|
|
227
|
+
<<~TEXT.rstrip.split("\n").join("\n#{newline_padding}")
|
|
228
|
+
Use 'specify-groups' if you want to specify multiple specs running in multiple
|
|
229
|
+
processes in a specific formation. Commas indicate specs in the same process,
|
|
230
|
+
pipes indicate specs in a new process. Cannot use with --single, --isolate, or
|
|
231
|
+
--isolate-n. Ex.
|
|
232
|
+
$ parallel_tests -n 3 . --specify-groups '1_spec.rb,2_spec.rb|3_spec.rb'
|
|
233
|
+
Process 1 will contain 1_spec.rb and 2_spec.rb
|
|
234
|
+
Process 2 will contain 3_spec.rb
|
|
235
|
+
Process 3 will contain all other specs
|
|
236
|
+
TEXT
|
|
237
|
+
) { |groups| options[:specify_groups] = groups }
|
|
238
|
+
|
|
239
|
+
opts.on("--only-group INT[,INT]", Array) { |groups| options[:only_group] = groups.map(&:to_i) }
|
|
194
240
|
|
|
195
241
|
opts.on("-e", "--exec [COMMAND]", "execute this code parallel and with ENV['TEST_ENV_NUMBER']") { |path| options[:execute] = path }
|
|
196
242
|
opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options") { |arg| options[:test_options] = arg.lstrip }
|
|
197
243
|
opts.on("-t", "--type [TYPE]", "test(default) / rspec / cucumber / spinach") do |type|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
abort
|
|
203
|
-
end
|
|
244
|
+
@runner = load_runner(type)
|
|
245
|
+
rescue NameError, LoadError => e
|
|
246
|
+
puts "Runner for `#{type}` type has not been found! (#{e})"
|
|
247
|
+
abort
|
|
204
248
|
end
|
|
205
|
-
opts.on(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
249
|
+
opts.on(
|
|
250
|
+
"--suffix [PATTERN]",
|
|
251
|
+
<<~TEXT.rstrip.split("\n").join("\n#{newline_padding}")
|
|
252
|
+
override built in test file pattern (should match suffix):
|
|
253
|
+
'_spec\.rb$' - matches rspec files
|
|
254
|
+
'_(test|spec).rb$' - matches test or spec files
|
|
209
255
|
TEXT
|
|
210
|
-
|
|
256
|
+
) { |pattern| options[:suffix] = /#{pattern}/ }
|
|
211
257
|
opts.on("--serialize-stdout", "Serialize stdout output, nothing will be written until everything is done") { options[:serialize_stdout] = true }
|
|
212
258
|
opts.on("--prefix-output-with-test-env-number", "Prefixes test env number to the output when not using --serialize-stdout") { options[:prefix_output_with_test_env_number] = true }
|
|
213
259
|
opts.on("--combine-stderr", "Combine stderr into stdout, useful in conjunction with --serialize-stdout") { options[:combine_stderr] = true }
|
|
214
260
|
opts.on("--non-parallel", "execute same commands but do not in parallel, needs --exec") { options[:non_parallel] = true }
|
|
215
261
|
opts.on("--no-symlinks", "Do not traverse symbolic links to find test files") { options[:symlinks] = false }
|
|
216
|
-
opts.on('--ignore-tags [PATTERN]', 'When counting steps ignore scenarios with tags that match this pattern')
|
|
262
|
+
opts.on('--ignore-tags [PATTERN]', 'When counting steps ignore scenarios with tags that match this pattern') { |arg| options[:ignore_tag_pattern] = arg }
|
|
217
263
|
opts.on("--nice", "execute test commands with low priority.") { options[:nice] = true }
|
|
218
264
|
opts.on("--runtime-log [PATH]", "Location of previously recorded test runtimes") { |path| options[:runtime_log] = path }
|
|
219
265
|
opts.on("--allowed-missing [INT]", Integer, "Allowed percentage of missing runtimes (default = 50)") { |percent| options[:allowed_missing_percent] = percent }
|
|
220
266
|
opts.on("--unknown-runtime [FLOAT]", Float, "Use given number as unknown runtime (otherwise use average time)") { |time| options[:unknown_runtime] = time }
|
|
221
267
|
opts.on("--first-is-1", "Use \"1\" as TEST_ENV_NUMBER to not reuse the default test environment") { options[:first_is_1] = true }
|
|
222
|
-
opts.on("--
|
|
223
|
-
opts.on("--
|
|
224
|
-
opts.on("-
|
|
225
|
-
opts.on("-
|
|
268
|
+
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
|
+
opts.on("--verbose", "Print debug output") { options[:verbose] = true }
|
|
270
|
+
opts.on("--verbose-process-command", "Displays only the command that will be executed by each process") { options[:verbose_process_command] = true }
|
|
271
|
+
opts.on("--verbose-rerun-command", "When there are failures, displays the command executed by each process that failed") { options[:verbose_rerun_command] = true }
|
|
272
|
+
opts.on("--quiet", "Print only tests output") { options[:quiet] = true }
|
|
273
|
+
opts.on("-v", "--version", "Show Version") do
|
|
274
|
+
puts ParallelTests::VERSION
|
|
275
|
+
exit 0
|
|
276
|
+
end
|
|
277
|
+
opts.on("-h", "--help", "Show this.") do
|
|
278
|
+
puts opts
|
|
279
|
+
exit 0
|
|
280
|
+
end
|
|
226
281
|
end.parse!(argv)
|
|
227
282
|
|
|
228
|
-
if options[:verbose] && options[:quiet]
|
|
229
|
-
raise "Both options are mutually exclusive: verbose & quiet"
|
|
230
|
-
end
|
|
283
|
+
raise "Both options are mutually exclusive: verbose & quiet" if options[:verbose] && options[:quiet]
|
|
231
284
|
|
|
232
285
|
if options[:count] == 0
|
|
233
286
|
options.delete(:count)
|
|
@@ -236,20 +289,33 @@ module ParallelTests
|
|
|
236
289
|
|
|
237
290
|
files, remaining = extract_file_paths(argv)
|
|
238
291
|
unless options[:execute]
|
|
239
|
-
|
|
240
|
-
|
|
292
|
+
if files.empty?
|
|
293
|
+
default_test_folder = @runner.default_test_folder
|
|
294
|
+
if File.directory?(default_test_folder)
|
|
295
|
+
files = [default_test_folder]
|
|
296
|
+
else
|
|
297
|
+
abort "Pass files or folders to run"
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
options[:files] = files.map { |file_path| Pathname.new(file_path).cleanpath.to_s }
|
|
241
301
|
end
|
|
242
302
|
|
|
243
303
|
append_test_options(options, remaining)
|
|
244
304
|
|
|
245
305
|
options[:group_by] ||= :filesize if options[:only_group]
|
|
246
306
|
|
|
247
|
-
|
|
307
|
+
if options[:group_by] == :found && options[:single_process]
|
|
308
|
+
raise "--group-by found and --single-process are not supported"
|
|
309
|
+
end
|
|
248
310
|
allowed = [:filesize, :runtime, :found]
|
|
249
311
|
if !allowed.include?(options[:group_by]) && options[:only_group]
|
|
250
312
|
raise "--group-by #{allowed.join(" or ")} is required for --only-group"
|
|
251
313
|
end
|
|
252
314
|
|
|
315
|
+
if options[:specify_groups] && (options.keys & [:single_process, :isolate, :isolate_count]).any?
|
|
316
|
+
raise "Can't pass --specify-groups with any of these keys: --single, --isolate, or --isolate-n"
|
|
317
|
+
end
|
|
318
|
+
|
|
253
319
|
options
|
|
254
320
|
end
|
|
255
321
|
|
|
@@ -261,7 +327,7 @@ module ParallelTests
|
|
|
261
327
|
|
|
262
328
|
def extract_test_options(argv)
|
|
263
329
|
dash_index = argv.index("--") || -1
|
|
264
|
-
argv[dash_index+1..-1]
|
|
330
|
+
argv[dash_index + 1..-1]
|
|
265
331
|
end
|
|
266
332
|
|
|
267
333
|
def append_test_options(options, argv)
|
|
@@ -281,7 +347,7 @@ module ParallelTests
|
|
|
281
347
|
|
|
282
348
|
def execute_shell_command_in_parallel(command, num_processes, options)
|
|
283
349
|
runs = if options[:only_group]
|
|
284
|
-
options[:only_group].map{|g| g - 1}
|
|
350
|
+
options[:only_group].map { |g| g - 1 }
|
|
285
351
|
else
|
|
286
352
|
(0...num_processes).to_a
|
|
287
353
|
end
|
|
@@ -300,22 +366,21 @@ module ParallelTests
|
|
|
300
366
|
abort if results.any? { |r| r[:exit_status] != 0 }
|
|
301
367
|
end
|
|
302
368
|
|
|
303
|
-
def report_time_taken
|
|
304
|
-
seconds = ParallelTests.delta
|
|
369
|
+
def report_time_taken(&block)
|
|
370
|
+
seconds = ParallelTests.delta(&block).to_i
|
|
305
371
|
puts "\nTook #{seconds} seconds#{detailed_duration(seconds)}"
|
|
306
372
|
end
|
|
307
373
|
|
|
308
374
|
def detailed_duration(seconds)
|
|
309
|
-
parts = [
|
|
375
|
+
parts = [seconds / 3600, seconds % 3600 / 60, seconds % 60].drop_while(&:zero?)
|
|
310
376
|
return if parts.size < 2
|
|
311
377
|
parts = parts.map { |i| "%02d" % i }.join(':').sub(/^0/, '')
|
|
312
378
|
" (#{parts})"
|
|
313
379
|
end
|
|
314
380
|
|
|
315
381
|
def final_fail_message
|
|
316
|
-
fail_message = "
|
|
382
|
+
fail_message = "Tests Failed"
|
|
317
383
|
fail_message = "\e[31m#{fail_message}\e[0m" if use_colors?
|
|
318
|
-
|
|
319
384
|
fail_message
|
|
320
385
|
end
|
|
321
386
|
|