parallel_tests 3.4.0 → 4.3.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.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'rake'
2
3
  require 'shellwords'
3
4
 
@@ -8,16 +9,8 @@ module ParallelTests
8
9
  'test'
9
10
  end
10
11
 
11
- def rake_bin
12
- # Prevent 'Exec format error' Errno::ENOEXEC on Windows
13
- return "rake" if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
14
- binstub_path = File.join('bin', 'rake')
15
- return binstub_path if File.exist?(binstub_path)
16
- "rake"
17
- end
18
-
19
12
  def load_lib
20
- $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
13
+ $LOAD_PATH << File.expand_path('..', __dir__)
21
14
  require "parallel_tests"
22
15
  end
23
16
 
@@ -27,13 +20,17 @@ module ParallelTests
27
20
  end
28
21
  end
29
22
 
30
- def run_in_parallel(cmd, options={})
23
+ def run_in_parallel(cmd, options = {})
31
24
  load_lib
32
- count = " -n #{options[:count]}" unless options[:count].to_s.empty?
25
+
33
26
  # Using the relative path to find the binary allow to run a specific version of it
34
- executable = File.expand_path("../../../bin/parallel_test", __FILE__)
35
- command = "#{ParallelTests.with_ruby_binary(Shellwords.escape(executable))} --exec '#{cmd}'#{count}#{' --non-parallel' if options[:non_parallel]}"
36
- abort unless system(command)
27
+ executable = File.expand_path('../../bin/parallel_test', __dir__)
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)
37
34
  end
38
35
 
39
36
  # this is a crazy-complex solution for a very simple problem:
@@ -46,16 +43,14 @@ module ParallelTests
46
43
  # - pipefail makes pipe fail with exitstatus of first failed command
47
44
  # - pipefail is not supported in (zsh)
48
45
  # - defining a new rake task like silence_schema would force users to load parallel_tests in test env
49
- # - do not use ' since run_in_parallel uses them to quote stuff
50
46
  # - simple system "set -o pipefail" returns nil even though set -o pipefail exists with 0
51
47
  def suppress_output(command, ignore_regex)
52
48
  activate_pipefail = "set -o pipefail"
53
- remove_ignored_lines = %Q{(grep -v "#{ignore_regex}" || test 1)}
49
+ remove_ignored_lines = %{(grep -v #{Shellwords.escape(ignore_regex)} || true)}
54
50
 
55
- if File.executable?('/bin/bash') && system('/bin/bash', '-c', "#{activate_pipefail} 2>/dev/null && test 1")
56
- # We need to shell escape single quotes (' becomes '"'"') because
57
- # run_in_parallel wraps command in single quotes
58
- %Q{/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]
59
54
  else
60
55
  command
61
56
  end
@@ -83,12 +78,61 @@ module ParallelTests
83
78
  # parallel:spec[2,models,options]
84
79
  # parallel:spec[,models,options]
85
80
  count = args.shift if args.first.to_s =~ /^\d*$/
86
- num_processes = count.to_i unless count.to_s.empty?
81
+ num_processes = (count.to_s.empty? ? nil : Integer(count))
87
82
  pattern = args.shift
88
83
  options = args.shift
89
84
  pass_through = args.shift
90
85
 
91
- [num_processes, pattern.to_s, options.to_s, pass_through.to_s]
86
+ [num_processes, pattern, options, pass_through]
87
+ end
88
+
89
+ def schema_format_based_on_rails_version
90
+ if rails_7_or_greater?
91
+ ActiveRecord.schema_format
92
+ else
93
+ ActiveRecord::Base.schema_format
94
+ end
95
+ end
96
+
97
+ def schema_type_based_on_rails_version
98
+ if rails_61_or_greater? || schema_format_based_on_rails_version == :ruby
99
+ "schema"
100
+ else
101
+ "structure"
102
+ end
103
+ end
104
+
105
+ def build_run_command(type, args)
106
+ count, pattern, options, pass_through = ParallelTests::Tasks.parse_args(args)
107
+ test_framework = {
108
+ 'spec' => 'rspec',
109
+ 'test' => 'test',
110
+ 'features' => 'cucumber',
111
+ 'features-spinach' => 'spinach'
112
+ }.fetch(type)
113
+
114
+ type = 'features' if test_framework == 'spinach'
115
+
116
+ # Using the relative path to find the binary allow to run a specific version of it
117
+ executable = File.expand_path('../../bin/parallel_test', __dir__)
118
+ executable = ParallelTests.with_ruby_binary(executable)
119
+
120
+ command = [*executable, type, '--type', test_framework]
121
+ command += ['-n', count.to_s] if count
122
+ command += ['--pattern', pattern] if pattern
123
+ command += ['--test-options', options] if options
124
+ command += Shellwords.shellsplit pass_through if pass_through
125
+ command
126
+ end
127
+
128
+ private
129
+
130
+ def rails_7_or_greater?
131
+ Gem::Version.new(Rails.version) >= Gem::Version.new('7.0')
132
+ end
133
+
134
+ def rails_61_or_greater?
135
+ Gem::Version.new(Rails.version) >= Gem::Version.new('6.1.0')
92
136
  end
93
137
  end
94
138
  end
@@ -96,30 +140,40 @@ end
96
140
 
97
141
  namespace :parallel do
98
142
  desc "Setup test databases via db:setup --> parallel:setup[num_cpus]"
99
- task :setup, :count do |_,args|
100
- command = "#{ParallelTests::Tasks.rake_bin} db:setup RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
143
+ task :setup, :count do |_, args|
144
+ command = [$0, "db:setup", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"]
101
145
  ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
102
146
  end
103
147
 
104
148
  desc "Create test databases via db:create --> parallel:create[num_cpus]"
105
- task :create, :count do |_,args|
149
+ task :create, :count do |_, args|
106
150
  ParallelTests::Tasks.run_in_parallel(
107
- "#{ParallelTests::Tasks.rake_bin} db:create RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
151
+ [$0, "db:create", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
152
+ args
153
+ )
108
154
  end
109
155
 
110
156
  desc "Drop test databases via db:drop --> parallel:drop[num_cpus]"
111
- task :drop, :count do |_,args|
157
+ task :drop, :count do |_, args|
112
158
  ParallelTests::Tasks.run_in_parallel(
113
- "#{ParallelTests::Tasks.rake_bin} db:drop RAILS_ENV=#{ParallelTests::Tasks.rails_env} " \
114
- "DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args)
159
+ [
160
+ $0,
161
+ "db:drop",
162
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
163
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
164
+ ],
165
+ args
166
+ )
115
167
  end
116
168
 
117
169
  desc "Update test databases by dumping and loading --> parallel:prepare[num_cpus]"
118
- task(:prepare, [:count]) do |_,args|
170
+ task(:prepare, [:count]) do |_, args|
119
171
  ParallelTests::Tasks.check_for_pending_migrations
120
- if defined?(ActiveRecord::Base) && [:ruby, :sql].include?(ActiveRecord::Base.schema_format)
172
+
173
+ if defined?(ActiveRecord) && [:ruby, :sql].include?(ParallelTests::Tasks.schema_format_based_on_rails_version)
121
174
  # fast: dump once, load in parallel
122
- type = (ActiveRecord::Base.schema_format == :ruby ? "schema" : "structure")
175
+ type = ParallelTests::Tasks.schema_type_based_on_rails_version
176
+
123
177
  Rake::Task["db:#{type}:dump"].invoke
124
178
 
125
179
  # remove database connection to prevent "database is being accessed by other users"
@@ -128,82 +182,87 @@ namespace :parallel do
128
182
  Rake::Task["parallel:load_#{type}"].invoke(args[:count])
129
183
  else
130
184
  # slow: dump and load in in serial
131
- args = args.to_hash.merge(:non_parallel => true) # normal merge returns nil
185
+ args = args.to_hash.merge(non_parallel: true) # normal merge returns nil
132
186
  task_name = Rake::Task.task_defined?('db:test:prepare') ? 'db:test:prepare' : 'app:db:test:prepare'
133
- ParallelTests::Tasks.run_in_parallel("#{ParallelTests::Tasks.rake_bin} #{task_name}", args)
187
+ ParallelTests::Tasks.run_in_parallel([$0, task_name], args)
134
188
  next
135
189
  end
136
190
  end
137
191
 
138
192
  # when dumping/resetting takes too long
139
193
  desc "Update test databases via db:migrate --> parallel:migrate[num_cpus]"
140
- task :migrate, :count do |_,args|
194
+ task :migrate, :count do |_, args|
141
195
  ParallelTests::Tasks.run_in_parallel(
142
- "#{ParallelTests::Tasks.rake_bin} db:migrate RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
196
+ [$0, "db:migrate", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
197
+ args
198
+ )
143
199
  end
144
200
 
145
201
  desc "Rollback test databases via db:rollback --> parallel:rollback[num_cpus]"
146
- task :rollback, :count do |_,args|
202
+ task :rollback, :count do |_, args|
147
203
  ParallelTests::Tasks.run_in_parallel(
148
- "#{ParallelTests::Tasks.rake_bin} db:rollback RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
204
+ [$0, "db:rollback", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
205
+ args
206
+ )
149
207
  end
150
208
 
151
209
  # just load the schema (good for integration server <-> no development db)
152
210
  desc "Load dumped schema for test databases via db:schema:load --> parallel:load_schema[num_cpus]"
153
- task :load_schema, :count do |_,args|
154
- command = "#{ParallelTests::Tasks.rake_bin} #{ParallelTests::Tasks.purge_before_load} " \
155
- "db:schema:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
211
+ task :load_schema, :count do |_, args|
212
+ command = [
213
+ $0,
214
+ ParallelTests::Tasks.purge_before_load,
215
+ "db:schema:load",
216
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
217
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
218
+ ]
156
219
  ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
157
220
  end
158
221
 
159
222
  # load the structure from the structure.sql file
160
- desc "Load structure for test databases via db:structure:load --> parallel:load_structure[num_cpus]"
161
- task :load_structure, :count do |_,args|
223
+ # (faster for rails < 6.1, deprecated after and only configured by `ActiveRecord::Base.schema_format`)
224
+ desc "Load structure for test databases via db:schema:load --> parallel:load_structure[num_cpus]"
225
+ task :load_structure, :count do |_, args|
162
226
  ParallelTests::Tasks.run_in_parallel(
163
- "#{ParallelTests::Tasks.rake_bin} #{ParallelTests::Tasks.purge_before_load} " \
164
- "db:structure:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args)
227
+ [
228
+ $0,
229
+ ParallelTests::Tasks.purge_before_load,
230
+ "db:structure:load",
231
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
232
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
233
+ ],
234
+ args
235
+ )
165
236
  end
166
237
 
167
238
  desc "Load the seed data from db/seeds.rb via db:seed --> parallel:seed[num_cpus]"
168
- task :seed, :count do |_,args|
239
+ task :seed, :count do |_, args|
169
240
  ParallelTests::Tasks.run_in_parallel(
170
- "#{ParallelTests::Tasks.rake_bin} db:seed RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
241
+ [
242
+ $0,
243
+ "db:seed",
244
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
245
+ ],
246
+ args
247
+ )
171
248
  end
172
249
 
173
250
  desc "Launch given rake command in parallel"
174
251
  task :rake, :command, :count do |_, args|
175
252
  ParallelTests::Tasks.run_in_parallel(
176
- "RAILS_ENV=#{ParallelTests::Tasks.rails_env} #{ParallelTests::Tasks.rake_bin} " \
177
- "#{args.command}", args)
253
+ [$0, args.command, "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
254
+ args
255
+ )
178
256
  end
179
257
 
180
258
  ['test', 'spec', 'features', 'features-spinach'].each do |type|
181
259
  desc "Run #{type} in parallel with parallel:#{type}[num_cpus]"
182
- task type, [:count, :pattern, :options, :pass_through] do |t, args|
260
+ task type, [:count, :pattern, :options, :pass_through] do |_t, args|
183
261
  ParallelTests::Tasks.check_for_pending_migrations
184
262
  ParallelTests::Tasks.load_lib
263
+ command = ParallelTests::Tasks.build_run_command(type, args)
185
264
 
186
- count, pattern, options, pass_through = ParallelTests::Tasks.parse_args(args)
187
- test_framework = {
188
- 'spec' => 'rspec',
189
- 'test' => 'test',
190
- 'features' => 'cucumber',
191
- 'features-spinach' => 'spinach',
192
- }[type]
193
-
194
- if test_framework == 'spinach'
195
- type = 'features'
196
- end
197
- # Using the relative path to find the binary allow to run a specific version of it
198
- executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
199
-
200
- command = "#{ParallelTests.with_ruby_binary(Shellwords.escape(executable))} #{type} " \
201
- "--type #{test_framework} " \
202
- "-n #{count} " \
203
- "--pattern '#{pattern}' " \
204
- "--test-options '#{options}' " \
205
- "#{pass_through}"
206
- abort unless system(command) # allow to chain tasks e.g. rake parallel:spec parallel:features
265
+ abort unless system(*command) # allow to chain tasks e.g. rake parallel:spec parallel:features
207
266
  end
208
267
  end
209
268
  end
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+ require 'shellwords'
1
3
  require 'parallel_tests'
2
4
 
3
5
  module ParallelTests
4
6
  module Test
5
7
  class Runner
8
+ RuntimeLogTooSmallError = Class.new(StandardError)
9
+
6
10
  class << self
7
11
  # --- usually overwritten by other runners
8
12
 
@@ -14,13 +18,24 @@ module ParallelTests
14
18
  /_(test|spec).rb$/
15
19
  end
16
20
 
21
+ def default_test_folder
22
+ "test"
23
+ end
24
+
17
25
  def test_file_name
18
26
  "test"
19
27
  end
20
28
 
21
29
  def run_tests(test_files, process_number, num_processes, options)
22
- require_list = test_files.map { |file| file.sub(" ", "\\ ") }.join(" ")
23
- cmd = "#{executable} -Itest -e '%w[#{require_list}].each { |f| require %{./\#{f}} }' -- #{options[:test_options]}"
30
+ require_list = test_files.map { |file| file.gsub(" ", "\\ ") }.join(" ")
31
+ cmd = [
32
+ *executable,
33
+ '-Itest',
34
+ '-e',
35
+ "%w[#{require_list}].each { |f| require %{./\#{f}} }",
36
+ '--',
37
+ *options[:test_options]
38
+ ]
24
39
  execute_command(cmd, process_number, num_processes, options)
25
40
  end
26
41
 
@@ -32,7 +47,7 @@ module ParallelTests
32
47
  # --- usually used by other runners
33
48
 
34
49
  # finds all tests and partitions them into groups
35
- def tests_in_groups(tests, num_groups, options={})
50
+ def tests_in_groups(tests, num_groups, options = {})
36
51
  tests = tests_with_size(tests, options)
37
52
  Grouper.in_even_groups_by_size(tests, num_groups, options)
38
53
  end
@@ -46,12 +61,19 @@ module ParallelTests
46
61
  when :filesize
47
62
  sort_by_filesize(tests)
48
63
  when :runtime
49
- sort_by_runtime(tests, runtimes(tests, options), options.merge(allowed_missing: (options[:allowed_missing_percent] || 50) / 100.0))
64
+ sort_by_runtime(
65
+ tests, runtimes(tests, options),
66
+ options.merge(allowed_missing: (options[:allowed_missing_percent] || 50) / 100.0)
67
+ )
50
68
  when nil
51
69
  # use recorded test runtime if we got enough data
52
- runtimes = runtimes(tests, options) rescue []
70
+ runtimes = begin
71
+ runtimes(tests, options)
72
+ rescue StandardError
73
+ []
74
+ end
53
75
  if runtimes.size * 1.5 > tests.size
54
- puts "Using recorded test runtime"
76
+ puts "Using recorded test runtime" unless options[:quiet]
55
77
  sort_by_runtime(tests, runtimes)
56
78
  else
57
79
  sort_by_filesize(tests)
@@ -64,35 +86,44 @@ module ParallelTests
64
86
  end
65
87
 
66
88
  def execute_command(cmd, process_number, num_processes, options)
89
+ number = test_env_number(process_number, options).to_s
67
90
  env = (options[:env] || {}).merge(
68
- "TEST_ENV_NUMBER" => test_env_number(process_number, options).to_s,
91
+ "TEST_ENV_NUMBER" => number,
69
92
  "PARALLEL_TEST_GROUPS" => num_processes.to_s,
70
- "PARALLEL_PID_FILE" => ParallelTests.pid_file_path,
93
+ "PARALLEL_PID_FILE" => ParallelTests.pid_file_path
71
94
  )
72
- cmd = "nice #{cmd}" if options[:nice]
73
- cmd = "#{cmd} 2>&1" if options[:combine_stderr]
95
+ cmd = ["nice", *cmd] if options[:nice]
74
96
 
75
- puts cmd if report_process_command?(options) && !options[:serialize_stdout]
97
+ # being able to run with for example `-output foo-$TEST_ENV_NUMBER` worked originally and is convenient
98
+ cmd = cmd.map { |c| c.gsub("$TEST_ENV_NUMBER", number).gsub("${TEST_ENV_NUMBER}", number) }
99
+
100
+ print_command(cmd, env) if report_process_command?(options) && !options[:serialize_stdout]
76
101
 
77
102
  execute_command_and_capture_output(env, cmd, options)
78
103
  end
79
104
 
105
+ def print_command(command, env)
106
+ env_str = ['TEST_ENV_NUMBER', 'PARALLEL_TEST_GROUPS'].map { |e| "#{e}=#{env[e]}" }.join(' ')
107
+ puts [env_str, Shellwords.shelljoin(command)].compact.join(' ')
108
+ end
109
+
80
110
  def execute_command_and_capture_output(env, cmd, options)
111
+ popen_options = {} # do not add `pgroup: true`, it will break `binding.irb` inside the test
112
+ popen_options[:err] = [:child, :out] if options[:combine_stderr]
113
+
81
114
  pid = nil
82
- output = IO.popen(env, cmd) do |io|
115
+ output = IO.popen(env, cmd, popen_options) do |io|
83
116
  pid = io.pid
84
117
  ParallelTests.pids.add(pid)
85
118
  capture_output(io, env, options)
86
119
  end
87
120
  ParallelTests.pids.delete(pid) if pid
88
121
  exitstatus = $?.exitstatus
89
- seed = output[/seed (\d+)/,1]
122
+ seed = output[/seed (\d+)/, 1]
90
123
 
91
- if report_process_command?(options) && options[:serialize_stdout]
92
- output = [cmd, output].join("\n")
93
- end
124
+ output = "#{Shellwords.shelljoin(cmd)}\n#{output}" if report_process_command?(options) && options[:serialize_stdout]
94
125
 
95
- {:stdout => output, :exit_status => exitstatus, :command => cmd, :seed => seed}
126
+ { env: env, stdout: output, exit_status: exitstatus, command: cmd, seed: seed }
96
127
  end
97
128
 
98
129
  def find_results(test_output)
@@ -104,7 +135,7 @@ module ParallelTests
104
135
  end.compact
105
136
  end
106
137
 
107
- def test_env_number(process_number, options={})
138
+ def test_env_number(process_number, options = {})
108
139
  if process_number == 0 && !options[:first_is_1]
109
140
  ''
110
141
  else
@@ -114,39 +145,42 @@ module ParallelTests
114
145
 
115
146
  def summarize_results(results)
116
147
  sums = sum_up_results(results)
117
- sums.sort.map{|word, number| "#{number} #{word}#{'s' if number != 1}" }.join(', ')
148
+ sums.sort.map { |word, number| "#{number} #{word}#{'s' if number != 1}" }.join(', ')
118
149
  end
119
150
 
120
151
  # remove old seed and add new seed
121
152
  def command_with_seed(cmd, seed)
122
- clean = cmd.sub(/\s--seed\s+\d+\b/, '')
123
- "#{clean} --seed #{seed}"
153
+ clean = remove_command_arguments(cmd, '--seed')
154
+ [*clean, '--seed', seed]
124
155
  end
125
156
 
126
157
  protected
127
158
 
128
159
  def executable
129
- ENV['PARALLEL_TESTS_EXECUTABLE'] || determine_executable
160
+ if (executable = ENV['PARALLEL_TESTS_EXECUTABLE'])
161
+ [executable]
162
+ else
163
+ determine_executable
164
+ end
130
165
  end
131
166
 
132
167
  def determine_executable
133
- "ruby"
168
+ ["ruby"]
134
169
  end
135
170
 
136
171
  def sum_up_results(results)
137
- results = results.join(' ').gsub(/s\b/,'') # combine and singularize results
172
+ results = results.join(' ').gsub(/s\b/, '') # combine and singularize results
138
173
  counts = results.scan(/(\d+) (\w+)/)
139
- counts.inject(Hash.new(0)) do |sum, (number, word)|
174
+ counts.each_with_object(Hash.new(0)) do |(number, word), sum|
140
175
  sum[word] += number.to_i
141
- sum
142
176
  end
143
177
  end
144
178
 
145
179
  # read output of the process and print it in chunks
146
- def capture_output(out, env, options={})
147
- result = ""
148
- loop do
149
- begin
180
+ def capture_output(out, env, options = {})
181
+ result = +""
182
+ begin
183
+ loop do
150
184
  read = out.readpartial(1000000) # read whatever chunk we can get
151
185
  if Encoding.default_internal
152
186
  read = read.force_encoding(Encoding.default_internal)
@@ -159,11 +193,13 @@ module ParallelTests
159
193
  $stdout.flush
160
194
  end
161
195
  end
162
- end rescue EOFError
196
+ rescue EOFError
197
+ nil
198
+ end
163
199
  result
164
200
  end
165
201
 
166
- def sort_by_runtime(tests, runtimes, options={})
202
+ def sort_by_runtime(tests, runtimes, options = {})
167
203
  allowed_missing = options[:allowed_missing] || 1.0
168
204
  allowed_missing = tests.size * allowed_missing
169
205
 
@@ -173,14 +209,12 @@ module ParallelTests
173
209
  allowed_missing -= 1 unless time = runtimes[test]
174
210
  if allowed_missing < 0
175
211
  log = options[:runtime_log] || runtime_log
176
- raise "Runtime log file '#{log}' does not contain sufficient data to sort #{tests.size} test files, please update or remove it."
212
+ raise RuntimeLogTooSmallError, "Runtime log file '#{log}' does not contain sufficient data to sort #{tests.size} test files, please update or remove it."
177
213
  end
178
214
  [test, time]
179
215
  end
180
216
 
181
- if options[:verbose]
182
- puts "Runtime found for #{tests.count(&:last)} of #{tests.size} tests"
183
- end
217
+ puts "Runtime found for #{tests.count(&:last)} of #{tests.size} tests" if options[:verbose]
184
218
 
185
219
  set_unknown_runtime tests, options
186
220
  end
@@ -190,7 +224,7 @@ module ParallelTests
190
224
  lines = File.read(log).split("\n")
191
225
  lines.each_with_object({}) do |line, times|
192
226
  test, _, time = line.rpartition(':')
193
- next unless test and time
227
+ next unless test && time
194
228
  times[test] = time.to_f if tests.include?(test)
195
229
  end
196
230
  end
@@ -217,7 +251,7 @@ module ParallelTests
217
251
  end.uniq
218
252
  end
219
253
 
220
- def files_in_folder(folder, options={})
254
+ def files_in_folder(folder, options = {})
221
255
  pattern = if options[:symlinks] == false # not nil or true
222
256
  "**/*"
223
257
  else
@@ -225,7 +259,22 @@ module ParallelTests
225
259
  # http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob
226
260
  "**{,/*/**}/*"
227
261
  end
228
- Dir[File.join(folder, pattern)].uniq
262
+ Dir[File.join(folder, pattern)].uniq.sort
263
+ end
264
+
265
+ def remove_command_arguments(command, *args)
266
+ remove_next = false
267
+ command.select do |arg|
268
+ if remove_next
269
+ remove_next = false
270
+ false
271
+ elsif args.include?(arg)
272
+ remove_next = true
273
+ false
274
+ else
275
+ true
276
+ end
277
+ end
229
278
  end
230
279
 
231
280
  private
@@ -236,12 +285,12 @@ module ParallelTests
236
285
  known, unknown = tests.partition(&:last)
237
286
  return if unknown.empty?
238
287
  unknown_runtime = options[:unknown_runtime] ||
239
- (known.empty? ? 1 : known.map!(&:last).inject(:+) / known.size) # average
288
+ (known.empty? ? 1 : known.map!(&:last).sum / known.size) # average
240
289
  unknown.each { |set| set[1] = unknown_runtime }
241
290
  end
242
291
 
243
292
  def report_process_command?(options)
244
- options[:verbose] || options[:verbose_process_command]
293
+ options[:verbose] || options[:verbose_command]
245
294
  end
246
295
  end
247
296
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'parallel_tests'
2
3
  require 'parallel_tests/test/runner'
3
4
 
@@ -22,7 +23,7 @@ module ParallelTests
22
23
  separator = "\n"
23
24
  groups = logfile.read.split(separator).map { |line| line.split(":") }.group_by(&:first)
24
25
  lines = groups.map do |file, times|
25
- time = "%.2f" % times.map(&:last).map(&:to_f).inject(:+)
26
+ time = "%.2f" % times.map(&:last).map(&:to_f).sum
26
27
  "#{file}:#{time}"
27
28
  end
28
29
  logfile.rewind
@@ -34,7 +35,7 @@ module ParallelTests
34
35
  private
35
36
 
36
37
  def with_locked_log
37
- File.open(logfile, File::RDWR|File::CREAT) do |logfile|
38
+ File.open(logfile, File::RDWR | File::CREAT) do |logfile|
38
39
  logfile.flock(File::LOCK_EX)
39
40
  yield logfile
40
41
  end
@@ -59,7 +60,7 @@ module ParallelTests
59
60
  end
60
61
 
61
62
  def message(test, delta)
62
- return unless method = test.public_instance_methods(true).detect { |method| method =~ /^test_/ }
63
+ return unless method = test.public_instance_methods(true).detect { |m| m =~ /^test_/ }
63
64
  filename = test.instance_method(method).source_location.first.sub("#{Dir.pwd}/", "")
64
65
  "#{filename}:#{delta}"
65
66
  end
@@ -74,22 +75,26 @@ end
74
75
 
75
76
  if defined?(Minitest::Runnable) # Minitest 5
76
77
  class << Minitest::Runnable
77
- prepend(Module.new do
78
- def run(*)
79
- ParallelTests::Test::RuntimeLogger.log_test_run(self) do
80
- super
78
+ prepend(
79
+ Module.new do
80
+ def run(*)
81
+ ParallelTests::Test::RuntimeLogger.log_test_run(self) do
82
+ super
83
+ end
81
84
  end
82
85
  end
83
- end)
86
+ )
84
87
  end
85
88
 
86
89
  class << Minitest
87
- prepend(Module.new do
88
- def run(*args)
89
- result = super
90
- ParallelTests::Test::RuntimeLogger.unique_log
91
- result
90
+ prepend(
91
+ Module.new do
92
+ def run(*args)
93
+ result = super
94
+ ParallelTests::Test::RuntimeLogger.unique_log
95
+ result
96
+ end
92
97
  end
93
- end)
98
+ )
94
99
  end
95
100
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ParallelTests
2
- VERSION = Version = '3.4.0'
3
+ VERSION = '4.3.0'
3
4
  end