parallel_tests 3.8.1 → 3.10.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: e4a7071d3e5afb39f01dd57304c8fab20d985f25c2f3699a7f6e69074715e576
4
- data.tar.gz: 64b9cb4fb05ed5cfc04554bc6fb04960fbefdf0ac756062fe01e7e15383fb1bc
3
+ metadata.gz: 02e1b418b273cae6dc5c01e23a772e096ab58ff438b48cc6931a9f786f760edc
4
+ data.tar.gz: adbe80da32bcf51282bfb8dc7027083aaa764824ba17276e93b399b4c5444a13
5
5
  SHA512:
6
- metadata.gz: 61639ad8b786efee4bd6e868d244531008d7eade53e3fb419f26ce7688a0b10d8609624f078e48562b3ac5ffb4560515e2ac869ebb6ddcb7616c10e869d22f70
7
- data.tar.gz: 2f15bd174e7e5064efa7186cdb604620cdd8aa0fb11d7702c4db3f8f558ca972385d65af51dade0ee8adaf80229673785f57c9160e3d38046c56a0ce539217c3
6
+ metadata.gz: 188deeb8e49d97dce2fb367948d02531a54377528338ae3cee13e3ab5dd414b2956b1103a3dd617797761c14a933597b905942823a0ab011d49a9ea5e5b0d51a
7
+ data.tar.gz: f397c05454ab591adff4012214a2e50c37f5b562bc6824833be5ca2c6bb8280f48bab781f27304c25f952f19f5322e5afa2f7f25a2083880eba09035d81b4da3
data/Readme.md CHANGED
@@ -42,7 +42,7 @@ test:
42
42
 
43
43
  ### Setup environment from scratch (create db and loads schema, useful for CI)
44
44
  rake parallel:setup
45
-
45
+
46
46
  ### Drop all test databases
47
47
  rake parallel:drop
48
48
 
@@ -193,15 +193,15 @@ Setup for non-rails
193
193
  - use `ENV['TEST_ENV_NUMBER']` inside your tests to select separate db/memcache/etc. (docker compose: expose it)
194
194
 
195
195
  - Only run a subset of files / folders:
196
-
196
+
197
197
  `parallel_test test/bar test/baz/foo_text.rb`
198
198
 
199
199
  - Pass test-options and files via `--`:
200
-
200
+
201
201
  `parallel_rspec -- -t acceptance -f progress -- spec/foo_spec.rb spec/acceptance`
202
-
202
+
203
203
  - Pass in test options, by using the -o flag (wrap everything in quotes):
204
-
204
+
205
205
  `parallel_cucumber -n 2 -o '-p foo_profile --tags @only_this_tag or @only_that_tag --format summary'`
206
206
 
207
207
  Options are:
@@ -402,6 +402,7 @@ inspired by [pivotal labs](https://blog.pivotal.io/labs/labs/parallelize-your-rs
402
402
  - [Vikram B Kumar](https://github.com/v-kumar)
403
403
  - [Joshua Pinter](https://github.com/joshuapinter)
404
404
  - [Zach Dennis](https://github.com/zdennis)
405
+ - [Jon Dufresne](https://github.com/jdufresne)
405
406
 
406
407
  [Michael Grosser](http://grosser.it)<br/>
407
408
  michael@grosser.it<br/>
@@ -20,7 +20,7 @@ module ParallelTests
20
20
  options[:first_is_1] ||= first_is_1?
21
21
 
22
22
  if options[:execute]
23
- execute_shell_command_in_parallel(options[:execute], num_processes, options)
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
@@ -100,7 +100,7 @@ module ParallelTests
100
100
 
101
101
  def run_tests(group, process_number, num_processes, options)
102
102
  if group.empty?
103
- { stdout: '', exit_status: 0, command: '', seed: nil }
103
+ { stdout: '', exit_status: 0, command: nil, seed: nil }
104
104
  else
105
105
  @runner.run_tests(group, process_number, num_processes, options)
106
106
  end
@@ -140,9 +140,8 @@ module ParallelTests
140
140
  puts "\n\nTests have failed for a parallel_test group. Use the following command to run the group again:\n\n"
141
141
  failing_sets.each do |failing_set|
142
142
  command = failing_set[:command]
143
- command = command.gsub(/;export [A-Z_]+;/, ' ') # remove ugly export statements
144
143
  command = @runner.command_with_seed(command, failing_set[:seed]) if failing_set[:seed]
145
- puts command
144
+ puts Shellwords.shelljoin(command)
146
145
  end
147
146
  end
148
147
  end
@@ -238,8 +237,8 @@ module ParallelTests
238
237
 
239
238
  opts.on("--only-group INT[,INT]", Array) { |groups| options[:only_group] = groups.map(&:to_i) }
240
239
 
241
- opts.on("-e", "--exec [COMMAND]", "execute this code parallel and with ENV['TEST_ENV_NUMBER']") { |path| options[:execute] = path }
242
- opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options") { |arg| options[:test_options] = arg.lstrip }
240
+ opts.on("-e", "--exec [COMMAND]", "execute this code parallel and with ENV['TEST_ENV_NUMBER']") { |arg| options[:execute] = Shellwords.shellsplit(arg) }
241
+ opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options") { |arg| options[:test_options] = Shellwords.shellsplit(arg) }
243
242
  opts.on("-t", "--type [TYPE]", "test(default) / rspec / cucumber / spinach") do |type|
244
243
  @runner = load_runner(type)
245
244
  rescue NameError, LoadError => e
@@ -334,8 +333,8 @@ module ParallelTests
334
333
  new_opts = extract_test_options(argv)
335
334
  return if new_opts.empty?
336
335
 
337
- prev_and_new = [options[:test_options], new_opts.shelljoin]
338
- options[:test_options] = prev_and_new.compact.join(' ')
336
+ options[:test_options] ||= []
337
+ options[:test_options] += new_opts
339
338
  end
340
339
 
341
340
  def load_runner(type)
@@ -345,7 +344,7 @@ module ParallelTests
345
344
  klass_name.split('::').inject(Object) { |x, y| x.const_get(y) }
346
345
  end
347
346
 
348
- def execute_shell_command_in_parallel(command, num_processes, options)
347
+ def execute_command_in_parallel(command, num_processes, options)
349
348
  runs = if options[:only_group]
350
349
  options[:only_group].map { |g| g - 1 }
351
350
  else
@@ -35,8 +35,8 @@ module ParallelTests
35
35
  end
36
36
 
37
37
  def command_with_seed(cmd, seed)
38
- clean = cmd.sub(/\s--order\s+random(:\d+)?\b/, '')
39
- "#{clean} --order random:#{seed}"
38
+ clean = remove_command_arguments(cmd, '--order')
39
+ [*clean, '--order', "random:#{seed}"]
40
40
  end
41
41
  end
42
42
  end
@@ -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].to_s.shellsplit
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})"
@@ -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
- executable,
26
- (runtime_logging if File.directory?(File.dirname(runtime_log))),
27
- *sanitized_test_files,
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
 
@@ -67,17 +62,17 @@ module ParallelTests
67
62
  end
68
63
 
69
64
  def cucumber_opts(given)
70
- if given =~ (/--profile/) || given =~ (/(^|\s)-p /)
65
+ if given&.include?('--profile') || given&.include?('-p')
71
66
  given
72
67
  else
73
- [given, profile_from_config].compact.join(" ")
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
- "--profile parallel" if config && File.read(config) =~ /^parallel:/
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
- "--format ParallelTests::Gherkin::RuntimeLogger --out #{runtime_log}"
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 #{name}"
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
@@ -7,8 +7,7 @@ module ParallelTests
7
7
  DEV_NULL = (WINDOWS ? "NUL" : "/dev/null")
8
8
  class << self
9
9
  def run_tests(test_files, process_number, num_processes, options)
10
- exe = executable # expensive, so we cache
11
- cmd = [exe, options[:test_options], color, spec_opts, *test_files].compact.join(" ")
10
+ cmd = [*executable, *options[:test_options], *color, *spec_opts, *test_files]
12
11
  execute_command(cmd, process_number, num_processes, options)
13
12
  end
14
13
 
@@ -16,9 +15,9 @@ module ParallelTests
16
15
  if File.exist?("bin/rspec")
17
16
  ParallelTests.with_ruby_binary("bin/rspec")
18
17
  elsif ParallelTests.bundler_enabled?
19
- "bundle exec rspec"
18
+ ["bundle", "exec", "rspec"]
20
19
  else
21
- "rspec"
20
+ ["rspec"]
22
21
  end
23
22
  end
24
23
 
@@ -48,8 +47,8 @@ module ParallelTests
48
47
  # --order rand:1234
49
48
  # --order random:1234
50
49
  def command_with_seed(cmd, seed)
51
- clean = cmd.sub(/\s--(seed\s+\d+|order\s+rand(om)?(:\d+)?)\b/, '')
52
- "#{clean} --seed #{seed}"
50
+ clean = remove_command_arguments(cmd, '--seed', '--order')
51
+ [*clean, '--seed', seed]
53
52
  end
54
53
 
55
54
  # Summarize results from threads and colorize results based on failure and pending counts.
@@ -71,19 +70,13 @@ module ParallelTests
71
70
 
72
71
  private
73
72
 
74
- # so it can be stubbed....
75
- def run(cmd)
76
- `#{cmd}`
77
- end
78
-
79
73
  def color
80
- '--color --tty' if $stdout.tty?
74
+ ['--color', '--tty'] if $stdout.tty?
81
75
  end
82
76
 
83
77
  def spec_opts
84
78
  options_file = ['.rspec_parallel', 'spec/parallel_spec.opts', 'spec/spec.opts'].detect { |f| File.file?(f) }
85
- return unless options_file
86
- "-O #{options_file}"
79
+ ["-O", options_file] if options_file
87
80
  end
88
81
  end
89
82
  end
@@ -9,16 +9,8 @@ module ParallelTests
9
9
  'test'
10
10
  end
11
11
 
12
- def rake_bin
13
- # Prevent 'Exec format error' Errno::ENOEXEC on Windows
14
- return "rake" if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
15
- binstub_path = File.join('bin', 'rake')
16
- return binstub_path if File.exist?(binstub_path)
17
- "rake"
18
- end
19
-
20
12
  def load_lib
21
- $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
13
+ $LOAD_PATH << File.expand_path('..', __dir__)
22
14
  require "parallel_tests"
23
15
  end
24
16
 
@@ -30,12 +22,15 @@ module ParallelTests
30
22
 
31
23
  def run_in_parallel(cmd, options = {})
32
24
  load_lib
33
- count = " -n #{options[:count]}" unless options[:count].to_s.empty?
25
+
34
26
  # Using the relative path to find the binary allow to run a specific version of it
35
27
  executable = File.expand_path('../../bin/parallel_test', __dir__)
36
- non_parallel = (options[:non_parallel] ? ' --non-parallel' : '')
37
- command = "#{ParallelTests.with_ruby_binary(Shellwords.escape(executable))} --exec '#{cmd}'#{count}#{non_parallel}"
38
- abort unless system(command)
28
+ command = ParallelTests.with_ruby_binary(executable)
29
+ command += ['--exec', Shellwords.join(cmd)]
30
+ command += ['-n', options[:count]] unless options[:count].to_s.empty?
31
+ command << '--non-parallel' if options[:non_parallel]
32
+
33
+ abort unless system(*command)
39
34
  end
40
35
 
41
36
  # this is a crazy-complex solution for a very simple problem:
@@ -48,16 +43,14 @@ module ParallelTests
48
43
  # - pipefail makes pipe fail with exitstatus of first failed command
49
44
  # - pipefail is not supported in (zsh)
50
45
  # - defining a new rake task like silence_schema would force users to load parallel_tests in test env
51
- # - do not use ' since run_in_parallel uses them to quote stuff
52
46
  # - simple system "set -o pipefail" returns nil even though set -o pipefail exists with 0
53
47
  def suppress_output(command, ignore_regex)
54
48
  activate_pipefail = "set -o pipefail"
55
- remove_ignored_lines = %{(grep -v "#{ignore_regex}" || test 1)}
49
+ remove_ignored_lines = %{(grep -v #{Shellwords.escape(ignore_regex)} || true)}
56
50
 
57
- if File.executable?('/bin/bash') && system('/bin/bash', '-c', "#{activate_pipefail} 2>/dev/null && test 1")
58
- # We need to shell escape single quotes (' becomes '"'"') because
59
- # run_in_parallel wraps command in single quotes
60
- %{/bin/bash -c '"'"'#{activate_pipefail} && (#{command}) | #{remove_ignored_lines}'"'"'}
51
+ if system('/bin/bash', '-c', "#{activate_pipefail} 2>/dev/null")
52
+ shell_command = "#{activate_pipefail} && (#{Shellwords.shelljoin(command)}) | #{remove_ignored_lines}"
53
+ ['/bin/bash', '-c', shell_command]
61
54
  else
62
55
  command
63
56
  end
@@ -90,7 +83,7 @@ module ParallelTests
90
83
  options = args.shift
91
84
  pass_through = args.shift
92
85
 
93
- [num_processes, pattern.to_s, options.to_s, pass_through.to_s]
86
+ [num_processes, pattern, options, pass_through]
94
87
  end
95
88
 
96
89
  def schema_format_based_on_rails_version
@@ -125,22 +118,28 @@ end
125
118
  namespace :parallel do
126
119
  desc "Setup test databases via db:setup --> parallel:setup[num_cpus]"
127
120
  task :setup, :count do |_, args|
128
- command = "#{ParallelTests::Tasks.rake_bin} db:setup RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
121
+ command = [$0, "db:setup", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"]
129
122
  ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
130
123
  end
131
124
 
132
125
  desc "Create test databases via db:create --> parallel:create[num_cpus]"
133
126
  task :create, :count do |_, args|
134
127
  ParallelTests::Tasks.run_in_parallel(
135
- "#{ParallelTests::Tasks.rake_bin} db:create RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
128
+ [$0, "db:create", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
129
+ args
136
130
  )
137
131
  end
138
132
 
139
133
  desc "Drop test databases via db:drop --> parallel:drop[num_cpus]"
140
134
  task :drop, :count do |_, args|
141
135
  ParallelTests::Tasks.run_in_parallel(
142
- "#{ParallelTests::Tasks.rake_bin} db:drop RAILS_ENV=#{ParallelTests::Tasks.rails_env} " \
143
- "DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args
136
+ [
137
+ $0,
138
+ "db:drop",
139
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
140
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
141
+ ],
142
+ args
144
143
  )
145
144
  end
146
145
 
@@ -162,7 +161,7 @@ namespace :parallel do
162
161
  # slow: dump and load in in serial
163
162
  args = args.to_hash.merge(non_parallel: true) # normal merge returns nil
164
163
  task_name = Rake::Task.task_defined?('db:test:prepare') ? 'db:test:prepare' : 'app:db:test:prepare'
165
- ParallelTests::Tasks.run_in_parallel("#{ParallelTests::Tasks.rake_bin} #{task_name}", args)
164
+ ParallelTests::Tasks.run_in_parallel([$0, task_name], args)
166
165
  next
167
166
  end
168
167
  end
@@ -171,23 +170,29 @@ namespace :parallel do
171
170
  desc "Update test databases via db:migrate --> parallel:migrate[num_cpus]"
172
171
  task :migrate, :count do |_, args|
173
172
  ParallelTests::Tasks.run_in_parallel(
174
- "#{ParallelTests::Tasks.rake_bin} db:migrate RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
173
+ [$0, "db:migrate", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
174
+ args
175
175
  )
176
176
  end
177
177
 
178
178
  desc "Rollback test databases via db:rollback --> parallel:rollback[num_cpus]"
179
179
  task :rollback, :count do |_, args|
180
180
  ParallelTests::Tasks.run_in_parallel(
181
- "#{ParallelTests::Tasks.rake_bin} db:rollback RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
181
+ [$0, "db:rollback", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
182
+ args
182
183
  )
183
184
  end
184
185
 
185
186
  # just load the schema (good for integration server <-> no development db)
186
187
  desc "Load dumped schema for test databases via db:schema:load --> parallel:load_schema[num_cpus]"
187
188
  task :load_schema, :count do |_, args|
188
- command =
189
- "#{ParallelTests::Tasks.rake_bin} #{ParallelTests::Tasks.purge_before_load} " \
190
- "db:schema:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
189
+ command = [
190
+ $0,
191
+ ParallelTests::Tasks.purge_before_load,
192
+ "db:schema:load",
193
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
194
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
195
+ ]
191
196
  ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
192
197
  end
193
198
 
@@ -196,23 +201,34 @@ namespace :parallel do
196
201
  desc "Load structure for test databases via db:schema:load --> parallel:load_structure[num_cpus]"
197
202
  task :load_structure, :count do |_, args|
198
203
  ParallelTests::Tasks.run_in_parallel(
199
- "#{ParallelTests::Tasks.rake_bin} #{ParallelTests::Tasks.purge_before_load} " \
200
- "db:structure:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args
204
+ [
205
+ $0,
206
+ ParallelTests::Tasks.purge_before_load,
207
+ "db:structure:load",
208
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
209
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
210
+ ],
211
+ args
201
212
  )
202
213
  end
203
214
 
204
215
  desc "Load the seed data from db/seeds.rb via db:seed --> parallel:seed[num_cpus]"
205
216
  task :seed, :count do |_, args|
206
217
  ParallelTests::Tasks.run_in_parallel(
207
- "#{ParallelTests::Tasks.rake_bin} db:seed RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
218
+ [
219
+ $0,
220
+ "db:seed",
221
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
222
+ ],
223
+ args
208
224
  )
209
225
  end
210
226
 
211
227
  desc "Launch given rake command in parallel"
212
228
  task :rake, :command, :count do |_, args|
213
229
  ParallelTests::Tasks.run_in_parallel(
214
- "RAILS_ENV=#{ParallelTests::Tasks.rails_env} #{ParallelTests::Tasks.rake_bin} " \
215
- "#{args.command}", args
230
+ [$0, args.command, "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
231
+ args
216
232
  )
217
233
  end
218
234
 
@@ -232,16 +248,15 @@ namespace :parallel do
232
248
 
233
249
  type = 'features' if test_framework == 'spinach'
234
250
  # Using the relative path to find the binary allow to run a specific version of it
235
- executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
236
-
237
- command =
238
- "#{ParallelTests.with_ruby_binary(Shellwords.escape(executable))} #{type} " \
239
- "--type #{test_framework} " \
240
- "-n #{count} " \
241
- "--pattern '#{pattern}' " \
242
- "--test-options '#{options}' " \
243
- "#{pass_through}"
244
- abort unless system(command) # allow to chain tasks e.g. rake parallel:spec parallel:features
251
+ executable = File.expand_path('../../bin/parallel_test', __dir__)
252
+
253
+ command = [*ParallelTests.with_ruby_binary(executable), type, '--type', test_framework]
254
+ command += ['-n', count] if count
255
+ command += ['--pattern', pattern] if pattern
256
+ command += ['--test-options', options] if options
257
+ command << pass_through if pass_through
258
+
259
+ abort unless system(*command) # allow to chain tasks e.g. rake parallel:spec parallel:features
245
260
  end
246
261
  end
247
262
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'shellwords'
2
3
  require 'parallel_tests'
3
4
 
4
5
  module ParallelTests
@@ -25,7 +26,14 @@ module ParallelTests
25
26
 
26
27
  def run_tests(test_files, process_number, num_processes, options)
27
28
  require_list = test_files.map { |file| file.gsub(" ", "\\ ") }.join(" ")
28
- cmd = "#{executable} -Itest -e '%w[#{require_list}].each { |f| require %{./\#{f}} }' -- #{options[:test_options]}"
29
+ cmd = [
30
+ *executable,
31
+ '-Itest',
32
+ '-e',
33
+ "%w[#{require_list}].each { |f| require %{./\#{f}} }",
34
+ '--',
35
+ *options[:test_options]
36
+ ]
29
37
  execute_command(cmd, process_number, num_processes, options)
30
38
  end
31
39
 
@@ -81,17 +89,20 @@ module ParallelTests
81
89
  "PARALLEL_TEST_GROUPS" => num_processes.to_s,
82
90
  "PARALLEL_PID_FILE" => ParallelTests.pid_file_path
83
91
  )
84
- cmd = "nice #{cmd}" if options[:nice]
85
- cmd = "#{cmd} 2>&1" if options[:combine_stderr]
92
+ cmd = ["nice", *cmd] if options[:nice]
86
93
 
87
- puts cmd if report_process_command?(options) && !options[:serialize_stdout]
94
+ puts Shellwords.shelljoin(cmd) if report_process_command?(options) && !options[:serialize_stdout]
88
95
 
89
96
  execute_command_and_capture_output(env, cmd, options)
90
97
  end
91
98
 
92
99
  def execute_command_and_capture_output(env, cmd, options)
93
100
  pid = nil
94
- output = IO.popen(env, cmd) do |io|
101
+
102
+ popen_options = {}
103
+ popen_options[:err] = [:child, :out] if options[:combine_stderr]
104
+
105
+ output = IO.popen(env, cmd, popen_options) do |io|
95
106
  pid = io.pid
96
107
  ParallelTests.pids.add(pid)
97
108
  capture_output(io, env, options)
@@ -100,7 +111,7 @@ module ParallelTests
100
111
  exitstatus = $?.exitstatus
101
112
  seed = output[/seed (\d+)/, 1]
102
113
 
103
- output = [cmd, output].join("\n") if report_process_command?(options) && options[:serialize_stdout]
114
+ output = "#{Shellwords.shelljoin(cmd)}\n#{output}" if report_process_command?(options) && options[:serialize_stdout]
104
115
 
105
116
  { stdout: output, exit_status: exitstatus, command: cmd, seed: seed }
106
117
  end
@@ -129,18 +140,22 @@ module ParallelTests
129
140
 
130
141
  # remove old seed and add new seed
131
142
  def command_with_seed(cmd, seed)
132
- clean = cmd.sub(/\s--seed\s+\d+\b/, '')
133
- "#{clean} --seed #{seed}"
143
+ clean = remove_command_arguments(cmd, '--seed')
144
+ [*clean, '--seed', seed]
134
145
  end
135
146
 
136
147
  protected
137
148
 
138
149
  def executable
139
- ENV['PARALLEL_TESTS_EXECUTABLE'] || determine_executable
150
+ if ENV.include?('PARALLEL_TESTS_EXECUTABLE')
151
+ [ENV['PARALLEL_TESTS_EXECUTABLE']]
152
+ else
153
+ determine_executable
154
+ end
140
155
  end
141
156
 
142
157
  def determine_executable
143
- "ruby"
158
+ ["ruby"]
144
159
  end
145
160
 
146
161
  def sum_up_results(results)
@@ -237,6 +252,21 @@ module ParallelTests
237
252
  Dir[File.join(folder, pattern)].uniq.sort
238
253
  end
239
254
 
255
+ def remove_command_arguments(command, *args)
256
+ remove_next = false
257
+ command.select do |arg|
258
+ if remove_next
259
+ remove_next = false
260
+ false
261
+ elsif args.include?(arg)
262
+ remove_next = true
263
+ false
264
+ else
265
+ true
266
+ end
267
+ end
268
+ end
269
+
240
270
  private
241
271
 
242
272
  # fill gaps with unknown-runtime if given, average otherwise
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ParallelTests
3
- VERSION = '3.8.1'
3
+ VERSION = '3.10.0'
4
4
  end
@@ -76,7 +76,7 @@ module ParallelTests
76
76
  end
77
77
 
78
78
  def with_ruby_binary(command)
79
- WINDOWS ? "#{RUBY_BINARY} -- #{command}" : command
79
+ WINDOWS ? [RUBY_BINARY, '--', command] : [command]
80
80
  end
81
81
 
82
82
  def wait_for_other_processes_to_finish
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parallel_tests
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.8.1
4
+ version: 3.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-28 00:00:00.000000000 Z
11
+ date: 2022-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parallel
@@ -68,8 +68,8 @@ licenses:
68
68
  - MIT
69
69
  metadata:
70
70
  bug_tracker_uri: https://github.com/grosser/parallel_tests/issues
71
- documentation_uri: https://github.com/grosser/parallel_tests/blob/v3.8.1/Readme.md
72
- source_code_uri: https://github.com/grosser/parallel_tests/tree/v3.8.1
71
+ documentation_uri: https://github.com/grosser/parallel_tests/blob/v3.10.0/Readme.md
72
+ source_code_uri: https://github.com/grosser/parallel_tests/tree/v3.10.0
73
73
  wiki_uri: https://github.com/grosser/parallel_tests/wiki
74
74
  post_install_message:
75
75
  rdoc_options: []
@@ -86,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
86
  - !ruby/object:Gem::Version
87
87
  version: '0'
88
88
  requirements: []
89
- rubygems_version: 3.3.10
89
+ rubygems_version: 3.0.3.1
90
90
  signing_key:
91
91
  specification_version: 4
92
92
  summary: Run Test::Unit / RSpec / Cucumber / Spinach in parallel