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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6a3302bc606b106b21ad18230f4c44c0981890cf5e603ecd7a85f4a800766f6
4
- data.tar.gz: c1269f9f6a3faba3d8220a360d2b050d49ccef83c14c83cf6a243a4dca7d245c
3
+ metadata.gz: 52c2ebdfc604b9e238867ea3d7d78cb919d60c951ab45bd2ead93db8893352f4
4
+ data.tar.gz: 441cf2b41a814746e1704fd794cd147638dbd261f1b294fd74b49adb9bc6d0da
5
5
  SHA512:
6
- metadata.gz: a1e9ea15748222c5acc25f8a2fee413bed29e7dff207ff968b1ffe2cbe54bb221acca26c5c83abe5f0188a8f41226392fdf356be75a2e47573c18cc0c477b406
7
- data.tar.gz: ea67c57a7037e57936ca818e79239bb42a8c18cdecd26cb6ac7496672073c3cca652e9f814227139a816676d721e5cbf8fb68fbb77da49138a5ce8426cbffce0
6
+ metadata.gz: a20853e2eac4f7b4260ea3ac3a9515b3d83fa3b47ff411a5f9567fcde257044825b2e9a81047e01f4d72722c02a6fdd2dbffba026dafb236cff8c2be2b712134
7
+ data.tar.gz: 4483fac06b11b0615b2bd39dcc5487713a02105cac547cf50177ec30805a8b17ebdbbac679e88a84f57da702235998cc0f67b80e382ea0bd27e784f4e89639a0
data/Readme.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/parallel_tests.svg)](https://rubygems.org/gems/parallel_tests)
4
4
  [![Build Status](https://travis-ci.org/grosser/parallel_tests.svg)](https://travis-ci.org/grosser/parallel_tests/builds)
5
- [![Build status](https://ci.appveyor.com/api/projects/status/708b1up4pqc34x3y?svg=true)](https://ci.appveyor.com/project/grosser/parallel-tests)
5
+ [![Build status](https://github.com/grosser/parallel_tests/workflows/windows/badge.svg)](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 pasteable command-line snippets for each failed example.
148
+ Produce pastable command-line snippets for each failed example. For example:
145
149
 
146
- E.g.
147
-
148
- rspec /path/to/my_spec.rb:123 # should do something
150
+ ```bash
151
+ rspec /path/to/my_spec.rb:123 # should do something
152
+ ```
149
153
 
150
- Add the following to your `.rspec_parallel` (or `.rspec`) :
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
- found - order of finding files
201
- steps - number of cucumber/spinach steps
202
- scenarios - individual cucumber scenarios
203
- filesize - by size of the file
204
- runtime - info from runtime log
205
- default - runtime when runtime log is filled otherwise filesize
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
- --only-group INT[, INT]
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
- '_spec.rb$' - matches rspec files
215
- '_(test|spec).rb$' - matches test or spec files
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 Allowed percentage of missing runtimes (default = 50)
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
- --verbose Print more output
228
- --quiet Do not print anything, apart from test output
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] to use spring you have to [patch it](https://github.com/grosser/parallel_tests/wiki/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 parallel test (e.g. Travis Support)](https://github.com/grosser/parallel_tests/wiki/Distributed-Parallel-Tests-and-Travis-Support)
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/>
@@ -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("../..", __FILE__)
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("../..", __FILE__)
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("../..", __FILE__)
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("../..", __FILE__)
5
+ root = File.expand_path('..', __dir__)
5
6
  $LOAD_PATH << "#{root}/lib" if File.exist?("#{root}/Gemfile")
6
7
 
7
8
  require "parallel_tests"
@@ -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| not c.to_s.strip.empty? }.to_i
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
- begin
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
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
- current, previous = File.expand_path("..", current), current
59
+ previous = current
60
+ current = File.expand_path("..", current)
61
61
  end
62
62
 
63
63
  false
@@ -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 = num_processes * (options[:multiply] || 1)
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, :in_threads => num_processes) do |item|
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! &:empty?
62
+ groups.reject!(&:empty?)
60
63
 
61
64
  test_results = if options[:only_group]
62
- groups_to_run = options[:only_group].collect{|i| groups[i - 1]}.compact
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
- abort final_fail_message if any_test_failed?(test_results)
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
- {:stdout => '', :exit_status => 0, :command => '', :seed => nil}
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
- begin
106
- lock.flock File::LOCK_EX
107
- yield
108
- ensure
109
- # This shouldn't be necessary, but appears to be
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).inject(0, :+)
153
+ num_tests = groups.map(&:size).sum
142
154
  tests_per_process = (num_processes == 0 ? 0 : num_tests / num_processes)
143
- puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{tests_per_process} #{name}s per process"
155
+ puts "#{pluralize(num_processes, 'process')} for #{pluralize(num_tests, name)}, ~ #{pluralize(tests_per_process, name)} per process"
144
156
  end
145
157
 
146
- #exit with correct status code so rake parallel:test && echo 123 works
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 = <<-BANNER.gsub(/^ /, '')
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("--group-by [TYPE]", <<-TEXT.gsub(/^ /, '')
169
- group tests by:
170
- found - order of finding files
171
- steps - number of cucumber/spinach steps
172
- scenarios - individual cucumber scenarios
173
- filesize - by size of the file
174
- runtime - info from runtime log
175
- default - runtime when runtime log is filled otherwise filesize
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
- ) { |type| options[:group_by] = type.to_sym }
178
- opts.on("-m [FLOAT]", "--multiply-processes [FLOAT]", Float, "use given number as a multiplier of processes to run") { |multiply| options[:multiply] = multiply }
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
- "Run all matching files in the same process") do |pattern|
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
- options[:single_process] ||= []
184
- options[:single_process] << /#{pattern}/
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("-i", "--isolate",
188
- "Do not run any other tests in the group used by --single(-s)") do |pattern|
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
- options[:isolate] = true
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("--only-group INT[, INT]", Array) { |groups| options[:only_group] = groups.map(&:to_i) }
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
- begin
199
- @runner = load_runner(type)
200
- rescue NameError, LoadError => e
201
- puts "Runner for `#{type}` type has not been found! (#{e})"
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("--suffix [PATTERN]", <<-TEXT.gsub(/^ /, '')
206
- override built in test file pattern (should match suffix):
207
- '_spec\.rb$' - matches rspec files
208
- '_(test|spec).rb$' - matches test or spec files
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
- ) { |pattern| options[:suffix] = /#{pattern}/ }
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') { |arg| options[:ignore_tag_pattern] = arg }
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("--verbose", "Print more output (mutually exclusive with quiet)") { options[:verbose] = true }
223
- opts.on("--quiet", "Print tests output only (mutually exclusive with verbose)") { options[:quiet] = true }
224
- opts.on("-v", "--version", "Show Version") { puts ParallelTests::VERSION; exit }
225
- opts.on("-h", "--help", "Show this.") { puts opts; exit }
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
- abort "Pass files or folders to run" unless files.any?
240
- options[:files] = files
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
- raise "--group-by found and --single-process are not supported" if options[:group_by] == :found and options[:single_process]
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 { yield }.to_i
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 = [ seconds / 3600, seconds % 3600 / 60, seconds % 60 ].drop_while(&:zero?)
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 = "#{@runner.name}s Failed"
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