parallel_tests 3.8.0 → 3.9.1

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: 3e3e1a135eda0167339276ebde8bbdc6c0620c83d08b36ca150cb19b2651bd18
4
- data.tar.gz: 759e3dd86a759cb2217f82b2b92bdc0e83e11d6e1d17cb7226d37eaa6c7ebade
3
+ metadata.gz: f47ccd447d00db0be7c9f7ae419b3d2083f0529624859546a6a1dee52337604f
4
+ data.tar.gz: 624a59d50ba0d167ab2bbbf5ee946fa3917ba57fb3cc256f3c2e5b4855349343
5
5
  SHA512:
6
- metadata.gz: 825d480ce774744fd4caa2fc5016be513384385ca88fc14b90552efef04041b45912246aa26557217d3f09e4810fc85ac4634381aadfcf0e0375ed4413c50557
7
- data.tar.gz: f927123ce02a41fadd99ab7045aad0e00464488c81d2cf2567e47180909c36be2d220d3c5192fbb0820f4f6c89ac29fc33f23368be5cb702d1aa55f66f419da8
6
+ metadata.gz: ac848edaee8de2a79d5cae89ddfe59df61b37d1464048f5d591d488a26db044f0fd97e2669fe6b69772ad3e449bc1cb28faa0c627d0114d57cd657a222ec39e0
7
+ data.tar.gz: 77f69505ddcbec907cdd45d955d7512b96509e003757bbc42f0ad188ee4083945f33316cf8d5a423b3dfe0e4181038e70e3cb68b8be152649cb3631d564dcb11
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
@@ -322,20 +321,20 @@ module ParallelTests
322
321
  def extract_file_paths(argv)
323
322
  dash_index = argv.rindex("--")
324
323
  file_args_at = (dash_index || -1) + 1
325
- [argv[file_args_at..], argv[0...(dash_index || 0)]]
324
+ [argv[file_args_at..-1], argv[0...(dash_index || 0)]]
326
325
  end
327
326
 
328
327
  def extract_test_options(argv)
329
328
  dash_index = argv.index("--") || -1
330
- argv[dash_index + 1..]
329
+ argv[dash_index + 1..-1]
331
330
  end
332
331
 
333
332
  def append_test_options(options, argv)
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
@@ -27,7 +27,7 @@ module ParallelTests
27
27
  example_tags = example.tags.map(&:name)
28
28
  example_tags = scenario_tags + example_tags
29
29
  next unless matches_tags?(example_tags)
30
- example.rows[1..].each do |row|
30
+ example.rows[1..-1].each do |row|
31
31
  test_line = row.source_line
32
32
  next if line_numbers.any? && !line_numbers.include?(test_line)
33
33
 
@@ -4,7 +4,6 @@ require 'cucumber/runtime'
4
4
  require 'cucumber'
5
5
  require 'parallel_tests/cucumber/scenario_line_logger'
6
6
  require 'parallel_tests/gherkin/listener'
7
- require 'shellwords'
8
7
 
9
8
  begin
10
9
  gem "cuke_modeler", "~> 3.0"
@@ -20,7 +19,7 @@ module ParallelTests
20
19
  def all(files, options = {})
21
20
  # Parse tag expression from given test options and ignore tag pattern. Refer here to understand how new tag expression syntax works - https://github.com/cucumber/cucumber/tree/master/tag-expressions
22
21
  tags = []
23
- words = options[:test_options].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
 
@@ -62,22 +57,22 @@ module ParallelTests
62
57
  plural = "s" if (word == group) && (number != 1)
63
58
  "#{number} #{word}#{plural}"
64
59
  end
65
- "#{sums[0]} (#{sums[1..].join(", ")})"
60
+ "#{sums[0]} (#{sums[1..-1].join(", ")})"
66
61
  end.compact.join("\n")
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
@@ -38,7 +38,7 @@ module ParallelTests
38
38
  # add all files that should run in a multiple isolated processes to their own groups
39
39
  group_features_by_size(items_to_group(single_items), groups[0..(isolate_count - 1)])
40
40
  # group the non-isolated by size
41
- group_features_by_size(items_to_group(items), groups[isolate_count..])
41
+ group_features_by_size(items_to_group(items), groups[isolate_count..-1])
42
42
  else
43
43
  # add all files that should run in a single non-isolated process to first group
44
44
  single_items.each { |item, size| add_to_group(groups.first, item, size) }
@@ -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
@@ -18,7 +18,7 @@ module ParallelTests
18
18
  end
19
19
 
20
20
  def load_lib
21
- $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
21
+ $LOAD_PATH << File.expand_path('..', __dir__)
22
22
  require "parallel_tests"
23
23
  end
24
24
 
@@ -30,12 +30,15 @@ module ParallelTests
30
30
 
31
31
  def run_in_parallel(cmd, options = {})
32
32
  load_lib
33
- count = " -n #{options[:count]}" unless options[:count].to_s.empty?
33
+
34
34
  # Using the relative path to find the binary allow to run a specific version of it
35
35
  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)
36
+ command = ParallelTests.with_ruby_binary(executable)
37
+ command += ['--exec', Shellwords.join(cmd)]
38
+ command += ['-n', options[:count]] unless options[:count].to_s.empty?
39
+ command << '--non-parallel' if options[:non_parallel]
40
+
41
+ abort unless system(*command)
39
42
  end
40
43
 
41
44
  # this is a crazy-complex solution for a very simple problem:
@@ -48,16 +51,14 @@ module ParallelTests
48
51
  # - pipefail makes pipe fail with exitstatus of first failed command
49
52
  # - pipefail is not supported in (zsh)
50
53
  # - 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
54
  # - simple system "set -o pipefail" returns nil even though set -o pipefail exists with 0
53
55
  def suppress_output(command, ignore_regex)
54
56
  activate_pipefail = "set -o pipefail"
55
- remove_ignored_lines = %{(grep -v "#{ignore_regex}" || test 1)}
57
+ remove_ignored_lines = %{(grep -v #{Shellwords.escape(ignore_regex)} || true)}
56
58
 
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}'"'"'}
59
+ if system('/bin/bash', '-c', "#{activate_pipefail} 2>/dev/null")
60
+ shell_command = "#{activate_pipefail} && (#{Shellwords.shelljoin(command)}) | #{remove_ignored_lines}"
61
+ ['/bin/bash', '-c', shell_command]
61
62
  else
62
63
  command
63
64
  end
@@ -90,7 +91,7 @@ module ParallelTests
90
91
  options = args.shift
91
92
  pass_through = args.shift
92
93
 
93
- [num_processes, pattern.to_s, options.to_s, pass_through.to_s]
94
+ [num_processes, pattern, options, pass_through]
94
95
  end
95
96
 
96
97
  def schema_format_based_on_rails_version
@@ -125,22 +126,28 @@ end
125
126
  namespace :parallel do
126
127
  desc "Setup test databases via db:setup --> parallel:setup[num_cpus]"
127
128
  task :setup, :count do |_, args|
128
- command = "#{ParallelTests::Tasks.rake_bin} db:setup RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
129
+ command = [ParallelTests::Tasks.rake_bin, "db:setup", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"]
129
130
  ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
130
131
  end
131
132
 
132
133
  desc "Create test databases via db:create --> parallel:create[num_cpus]"
133
134
  task :create, :count do |_, args|
134
135
  ParallelTests::Tasks.run_in_parallel(
135
- "#{ParallelTests::Tasks.rake_bin} db:create RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
136
+ [ParallelTests::Tasks.rake_bin, "db:create", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
137
+ args
136
138
  )
137
139
  end
138
140
 
139
141
  desc "Drop test databases via db:drop --> parallel:drop[num_cpus]"
140
142
  task :drop, :count do |_, args|
141
143
  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
144
+ [
145
+ ParallelTests::Tasks.rake_bin,
146
+ "db:drop",
147
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
148
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
149
+ ],
150
+ args
144
151
  )
145
152
  end
146
153
 
@@ -162,7 +169,7 @@ namespace :parallel do
162
169
  # slow: dump and load in in serial
163
170
  args = args.to_hash.merge(non_parallel: true) # normal merge returns nil
164
171
  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)
172
+ ParallelTests::Tasks.run_in_parallel([ParallelTests::Tasks.rake_bin, task_name], args)
166
173
  next
167
174
  end
168
175
  end
@@ -171,23 +178,29 @@ namespace :parallel do
171
178
  desc "Update test databases via db:migrate --> parallel:migrate[num_cpus]"
172
179
  task :migrate, :count do |_, args|
173
180
  ParallelTests::Tasks.run_in_parallel(
174
- "#{ParallelTests::Tasks.rake_bin} db:migrate RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
181
+ [ParallelTests::Tasks.rake_bin, "db:migrate", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
182
+ args
175
183
  )
176
184
  end
177
185
 
178
186
  desc "Rollback test databases via db:rollback --> parallel:rollback[num_cpus]"
179
187
  task :rollback, :count do |_, args|
180
188
  ParallelTests::Tasks.run_in_parallel(
181
- "#{ParallelTests::Tasks.rake_bin} db:rollback RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
189
+ [ParallelTests::Tasks.rake_bin, "db:rollback", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
190
+ args
182
191
  )
183
192
  end
184
193
 
185
194
  # just load the schema (good for integration server <-> no development db)
186
195
  desc "Load dumped schema for test databases via db:schema:load --> parallel:load_schema[num_cpus]"
187
196
  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"
197
+ command = [
198
+ ParallelTests::Tasks.rake_bin,
199
+ ParallelTests::Tasks.purge_before_load,
200
+ "db:schema:load",
201
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
202
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
203
+ ]
191
204
  ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
192
205
  end
193
206
 
@@ -196,23 +209,34 @@ namespace :parallel do
196
209
  desc "Load structure for test databases via db:schema:load --> parallel:load_structure[num_cpus]"
197
210
  task :load_structure, :count do |_, args|
198
211
  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
212
+ [
213
+ ParallelTests::Tasks.rake_bin,
214
+ ParallelTests::Tasks.purge_before_load,
215
+ "db:structure:load",
216
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
217
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
218
+ ],
219
+ args
201
220
  )
202
221
  end
203
222
 
204
223
  desc "Load the seed data from db/seeds.rb via db:seed --> parallel:seed[num_cpus]"
205
224
  task :seed, :count do |_, args|
206
225
  ParallelTests::Tasks.run_in_parallel(
207
- "#{ParallelTests::Tasks.rake_bin} db:seed RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
226
+ [
227
+ ParallelTests::Tasks.rake_bin,
228
+ "db:seed",
229
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
230
+ ],
231
+ args
208
232
  )
209
233
  end
210
234
 
211
235
  desc "Launch given rake command in parallel"
212
236
  task :rake, :command, :count do |_, args|
213
237
  ParallelTests::Tasks.run_in_parallel(
214
- "RAILS_ENV=#{ParallelTests::Tasks.rails_env} #{ParallelTests::Tasks.rake_bin} " \
215
- "#{args.command}", args
238
+ [ParallelTests::Tasks.rake_bin, args.command, "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
239
+ args
216
240
  )
217
241
  end
218
242
 
@@ -232,16 +256,15 @@ namespace :parallel do
232
256
 
233
257
  type = 'features' if test_framework == 'spinach'
234
258
  # 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
259
+ executable = File.expand_path('../../bin/parallel_test', __dir__)
260
+
261
+ command = [*ParallelTests.with_ruby_binary(executable), type, '--type', test_framework]
262
+ command += ['-n', count] if count
263
+ command += ['--pattern', pattern] if pattern
264
+ command += ['--test-options', options] if options
265
+ command << pass_through if pass_through
266
+
267
+ abort unless system(*command) # allow to chain tasks e.g. rake parallel:spec parallel:features
245
268
  end
246
269
  end
247
270
  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.0'
3
+ VERSION = '3.9.1'
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.0
4
+ version: 3.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-26 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
@@ -24,7 +24,7 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- description:
27
+ description:
28
28
  email: michael@grosser.it
29
29
  executables:
30
30
  - parallel_spinach
@@ -68,10 +68,10 @@ 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.0/Readme.md
72
- source_code_uri: https://github.com/grosser/parallel_tests/tree/v3.8.0
71
+ documentation_uri: https://github.com/grosser/parallel_tests/blob/v3.9.1/Readme.md
72
+ source_code_uri: https://github.com/grosser/parallel_tests/tree/v3.9.1
73
73
  wiki_uri: https://github.com/grosser/parallel_tests/wiki
74
- post_install_message:
74
+ post_install_message:
75
75
  rdoc_options: []
76
76
  require_paths:
77
77
  - lib
@@ -79,15 +79,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: 2.7.0
82
+ version: 2.5.0
83
83
  required_rubygems_version: !ruby/object:Gem::Requirement
84
84
  requirements:
85
85
  - - ">="
86
86
  - !ruby/object:Gem::Version
87
87
  version: '0'
88
88
  requirements: []
89
- rubygems_version: 3.1.6
90
- signing_key:
89
+ rubygems_version: 3.3.3
90
+ signing_key:
91
91
  specification_version: 4
92
92
  summary: Run Test::Unit / RSpec / Cucumber / Spinach in parallel
93
93
  test_files: []