parallel_tests 3.8.1 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
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