parallel_tests 2.28.0 → 3.7.3

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
  module ParallelTests
2
3
  module RSpec
3
4
  end
@@ -13,26 +14,27 @@ class ParallelTests::RSpec::LoggerBase < RSpec::Core::Formatters::BaseTextFormat
13
14
 
14
15
  @output ||= args[0]
15
16
 
16
- if String === @output # a path ?
17
+ case @output
18
+ when String # a path ?
17
19
  FileUtils.mkdir_p(File.dirname(@output))
18
- File.open(@output, 'w'){} # overwrite previous results
20
+ File.open(@output, 'w') {} # overwrite previous results
19
21
  @output = File.open(@output, 'a')
20
- elsif File === @output # close and restart in append mode
22
+ when File # close and restart in append mode
21
23
  @output.close
22
24
  @output = File.open(@output.path, 'a')
23
25
  end
24
26
  end
25
27
 
26
- #stolen from Rspec
27
- def close(*args)
28
- @output.close if (IO === @output) & (@output != $stdout)
28
+ # stolen from Rspec
29
+ def close(*)
30
+ @output.close if (IO === @output) & (@output != $stdout)
29
31
  end
30
32
 
31
33
  protected
32
34
 
33
35
  # do not let multiple processes get in each others way
34
36
  def lock_output
35
- if File === @output
37
+ if @output.is_a?(File)
36
38
  begin
37
39
  @output.flock File::LOCK_EX
38
40
  yield
@@ -1,11 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  require "parallel_tests/test/runner"
2
3
 
3
4
  module ParallelTests
4
5
  module RSpec
5
6
  class Runner < ParallelTests::Test::Runner
6
7
  DEV_NULL = (WINDOWS ? "NUL" : "/dev/null")
7
- NAME = 'RSpec'
8
-
9
8
  class << self
10
9
  def run_tests(test_files, process_number, num_processes, options)
11
10
  exe = executable # expensive, so we cache
@@ -14,21 +13,21 @@ module ParallelTests
14
13
  end
15
14
 
16
15
  def determine_executable
17
- cmd = case
18
- when File.exist?("bin/rspec")
16
+ if File.exist?("bin/rspec")
19
17
  ParallelTests.with_ruby_binary("bin/rspec")
20
- when ParallelTests.bundler_enabled?
21
- cmd = (run("bundle show rspec-core") =~ %r{Could not find gem.*} ? "spec" : "rspec")
22
- "bundle exec #{cmd}"
18
+ elsif ParallelTests.bundler_enabled?
19
+ "bundle exec rspec"
23
20
  else
24
- %w[spec rspec].detect{|cmd| system "#{cmd} --version > #{DEV_NULL} 2>&1" }
21
+ "rspec"
25
22
  end
26
-
27
- cmd or raise("Can't find executables rspec or spec")
28
23
  end
29
24
 
30
25
  def runtime_log
31
- 'tmp/parallel_runtime_rspec.log'
26
+ "tmp/parallel_runtime_rspec.log"
27
+ end
28
+
29
+ def default_test_folder
30
+ "spec"
32
31
  end
33
32
 
34
33
  def test_file_name
@@ -53,6 +52,22 @@ module ParallelTests
53
52
  "#{clean} --seed #{seed}"
54
53
  end
55
54
 
55
+ # Summarize results from threads and colorize results based on failure and pending counts.
56
+ #
57
+ def summarize_results(results)
58
+ text = super
59
+ return text unless $stdout.tty?
60
+ sums = sum_up_results(results)
61
+ color =
62
+ if sums['failure'] > 0
63
+ 31 # red
64
+ elsif sums['pending'] > 0
65
+ 33 # yellow
66
+ else
67
+ 32 # green
68
+ end
69
+ "\e[#{color}m#{text}\e[0m"
70
+ end
56
71
 
57
72
  private
58
73
 
@@ -66,7 +81,7 @@ module ParallelTests
66
81
  end
67
82
 
68
83
  def spec_opts
69
- options_file = ['.rspec_parallel', 'spec/parallel_spec.opts', 'spec/spec.opts'].detect{|f| File.file?(f) }
84
+ options_file = ['.rspec_parallel', 'spec/parallel_spec.opts', 'spec/spec.opts'].detect { |f| File.file?(f) }
70
85
  return unless options_file
71
86
  "-O #{options_file}"
72
87
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'parallel_tests'
2
3
  require 'parallel_tests/rspec/logger_base'
3
4
 
@@ -8,9 +9,7 @@ class ParallelTests::RSpec::RuntimeLogger < ParallelTests::RSpec::LoggerBase
8
9
  @group_nesting = 0
9
10
  end
10
11
 
11
- unless RSPEC_2
12
- RSpec::Core::Formatters.register self, :example_group_started, :example_group_finished, :start_dump
13
- end
12
+ RSpec::Core::Formatters.register self, :example_group_started, :example_group_finished, :start_dump unless RSPEC_2
14
13
 
15
14
  def example_group_started(example_group)
16
15
  @time = ParallelTests.now if @group_nesting == 0
@@ -27,16 +26,19 @@ class ParallelTests::RSpec::RuntimeLogger < ParallelTests::RSpec::LoggerBase
27
26
  super if defined?(super)
28
27
  end
29
28
 
30
- def dump_summary(*args);end
31
- def dump_failures(*args);end
32
- def dump_failure(*args);end
33
- def dump_pending(*args);end
29
+ def dump_summary(*); end
30
+
31
+ def dump_failures(*); end
32
+
33
+ def dump_failure(*); end
34
+
35
+ def dump_pending(*); end
34
36
 
35
- def start_dump(*args)
36
- return unless ENV['TEST_ENV_NUMBER'] #only record when running in parallel
37
+ def start_dump(*)
38
+ return unless ENV['TEST_ENV_NUMBER'] # only record when running in parallel
37
39
  lock_output do
38
40
  @example_times.each do |file, time|
39
- relative_path = file.sub(/^#{Regexp.escape Dir.pwd}\//,'').sub(/^\.\//, "")
41
+ relative_path = file.sub(%r{^#{Regexp.escape Dir.pwd}/}, '').sub(%r{^\./}, "")
40
42
  @output.puts "#{relative_path}:#{time > 0 ? time : 0}"
41
43
  end
42
44
  end
@@ -1,9 +1,8 @@
1
+ # frozen_string_literal: true
1
2
  require 'parallel_tests/rspec/failures_logger'
2
3
 
3
4
  class ParallelTests::RSpec::SummaryLogger < ParallelTests::RSpec::LoggerBase
4
- unless RSPEC_2
5
- RSpec::Core::Formatters.register self, :dump_failures
6
- end
5
+ RSpec::Core::Formatters.register self, :dump_failures unless RSPEC_2
7
6
 
8
7
  def dump_failures(*args)
9
8
  lock_output { super }
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require "parallel_tests/gherkin/runner"
2
3
 
3
4
  module ParallelTests
@@ -8,11 +9,14 @@ module ParallelTests
8
9
  'spinach'
9
10
  end
10
11
 
12
+ def default_test_folder
13
+ 'features'
14
+ end
15
+
11
16
  def runtime_logging
12
- #Not Yet Supported
17
+ # Not Yet Supported
13
18
  ""
14
19
  end
15
-
16
20
  end
17
21
  end
18
22
  end
@@ -1,10 +1,20 @@
1
+ # frozen_string_literal: true
1
2
  require 'rake'
3
+ require 'shellwords'
2
4
 
3
5
  module ParallelTests
4
6
  module Tasks
5
7
  class << self
6
8
  def rails_env
7
- ENV['RAILS_ENV'] || 'test'
9
+ 'test'
10
+ end
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"
8
18
  end
9
19
 
10
20
  def load_lib
@@ -18,12 +28,13 @@ module ParallelTests
18
28
  end
19
29
  end
20
30
 
21
- def run_in_parallel(cmd, options={})
31
+ def run_in_parallel(cmd, options = {})
22
32
  load_lib
23
33
  count = " -n #{options[:count]}" unless options[:count].to_s.empty?
24
34
  # Using the relative path to find the binary allow to run a specific version of it
25
- executable = File.expand_path("../../../bin/parallel_test", __FILE__)
26
- command = "#{ParallelTests.with_ruby_binary(Shellwords.escape(executable))} --exec '#{cmd}'#{count}#{' --non-parallel' if options[:non_parallel]}"
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}"
27
38
  abort unless system(command)
28
39
  end
29
40
 
@@ -41,12 +52,12 @@ module ParallelTests
41
52
  # - simple system "set -o pipefail" returns nil even though set -o pipefail exists with 0
42
53
  def suppress_output(command, ignore_regex)
43
54
  activate_pipefail = "set -o pipefail"
44
- remove_ignored_lines = %Q{(grep -v "#{ignore_regex}" || test 1)}
55
+ remove_ignored_lines = %{(grep -v "#{ignore_regex}" || test 1)}
45
56
 
46
57
  if File.executable?('/bin/bash') && system('/bin/bash', '-c', "#{activate_pipefail} 2>/dev/null && test 1")
47
58
  # We need to shell escape single quotes (' becomes '"'"') because
48
59
  # run_in_parallel wraps command in single quotes
49
- %Q{/bin/bash -c '"'"'#{activate_pipefail} && (#{command}) | #{remove_ignored_lines}'"'"'}
60
+ %{/bin/bash -c '"'"'#{activate_pipefail} && (#{command}) | #{remove_ignored_lines}'"'"'}
50
61
  else
51
62
  command
52
63
  end
@@ -74,7 +85,7 @@ module ParallelTests
74
85
  # parallel:spec[2,models,options]
75
86
  # parallel:spec[,models,options]
76
87
  count = args.shift if args.first.to_s =~ /^\d*$/
77
- num_processes = count.to_i unless count.to_s.empty?
88
+ num_processes = (count.to_s.empty? ? nil : Integer(count))
78
89
  pattern = args.shift
79
90
  options = args.shift
80
91
  pass_through = args.shift
@@ -87,73 +98,104 @@ end
87
98
 
88
99
  namespace :parallel do
89
100
  desc "Setup test databases via db:setup --> parallel:setup[num_cpus]"
90
- task :setup, :count do |_,args|
91
- command = "rake db:setup RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
101
+ task :setup, :count do |_, args|
102
+ command = "#{ParallelTests::Tasks.rake_bin} db:setup RAILS_ENV=#{ParallelTests::Tasks.rails_env}"
92
103
  ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
93
104
  end
94
105
 
95
106
  desc "Create test databases via db:create --> parallel:create[num_cpus]"
96
- task :create, :count do |_,args|
97
- ParallelTests::Tasks.run_in_parallel("rake db:create RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
107
+ task :create, :count do |_, args|
108
+ ParallelTests::Tasks.run_in_parallel(
109
+ "#{ParallelTests::Tasks.rake_bin} db:create RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
110
+ )
98
111
  end
99
112
 
100
113
  desc "Drop test databases via db:drop --> parallel:drop[num_cpus]"
101
- task :drop, :count do |_,args|
102
- ParallelTests::Tasks.run_in_parallel("rake db:drop RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args)
114
+ task :drop, :count do |_, args|
115
+ ParallelTests::Tasks.run_in_parallel(
116
+ "#{ParallelTests::Tasks.rake_bin} db:drop RAILS_ENV=#{ParallelTests::Tasks.rails_env} " \
117
+ "DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args
118
+ )
103
119
  end
104
120
 
105
121
  desc "Update test databases by dumping and loading --> parallel:prepare[num_cpus]"
106
- task(:prepare, [:count]) do |_,args|
122
+ task(:prepare, [:count]) do |_, args|
107
123
  ParallelTests::Tasks.check_for_pending_migrations
108
- if defined?(ActiveRecord) && ActiveRecord::Base.schema_format == :ruby
109
- # dump then load in parallel
110
- Rake::Task['db:schema:dump'].invoke
111
- Rake::Task['parallel:load_schema'].invoke(args[:count])
124
+ if defined?(ActiveRecord::Base) && [:ruby, :sql].include?(ActiveRecord::Base.schema_format)
125
+ # fast: dump once, load in parallel
126
+ type =
127
+ if Gem::Version.new(Rails.version) >= Gem::Version.new('6.1.0')
128
+ "schema"
129
+ else
130
+ ActiveRecord::Base.schema_format == :ruby ? "schema" : "structure"
131
+ end
132
+
133
+ Rake::Task["db:#{type}:dump"].invoke
134
+
135
+ # remove database connection to prevent "database is being accessed by other users"
136
+ ActiveRecord::Base.remove_connection if ActiveRecord::Base.configurations.any?
137
+
138
+ Rake::Task["parallel:load_#{type}"].invoke(args[:count])
112
139
  else
113
- # there is no separate dump / load for schema_format :sql -> do it safe and slow
114
- args = args.to_hash.merge(:non_parallel => true) # normal merge returns nil
115
- taskname = Rake::Task.task_defined?('db:test:prepare') ? 'db:test:prepare' : 'app:db:test:prepare'
116
- ParallelTests::Tasks.run_in_parallel("rake #{taskname}", args)
140
+ # slow: dump and load in in serial
141
+ args = args.to_hash.merge(non_parallel: true) # normal merge returns nil
142
+ task_name = Rake::Task.task_defined?('db:test:prepare') ? 'db:test:prepare' : 'app:db:test:prepare'
143
+ ParallelTests::Tasks.run_in_parallel("#{ParallelTests::Tasks.rake_bin} #{task_name}", args)
144
+ next
117
145
  end
118
146
  end
119
147
 
120
148
  # when dumping/resetting takes too long
121
149
  desc "Update test databases via db:migrate --> parallel:migrate[num_cpus]"
122
- task :migrate, :count do |_,args|
123
- ParallelTests::Tasks.run_in_parallel("rake db:migrate RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
150
+ task :migrate, :count do |_, args|
151
+ ParallelTests::Tasks.run_in_parallel(
152
+ "#{ParallelTests::Tasks.rake_bin} db:migrate RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
153
+ )
124
154
  end
125
155
 
126
156
  desc "Rollback test databases via db:rollback --> parallel:rollback[num_cpus]"
127
- task :rollback, :count do |_,args|
128
- ParallelTests::Tasks.run_in_parallel("rake db:rollback RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
157
+ task :rollback, :count do |_, args|
158
+ ParallelTests::Tasks.run_in_parallel(
159
+ "#{ParallelTests::Tasks.rake_bin} db:rollback RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
160
+ )
129
161
  end
130
162
 
131
163
  # just load the schema (good for integration server <-> no development db)
132
164
  desc "Load dumped schema for test databases via db:schema:load --> parallel:load_schema[num_cpus]"
133
- task :load_schema, :count do |_,args|
134
- command = "rake #{ParallelTests::Tasks.purge_before_load} db:schema:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
165
+ task :load_schema, :count do |_, args|
166
+ command = "#{ParallelTests::Tasks.rake_bin} #{ParallelTests::Tasks.purge_before_load} " \
167
+ "db:schema:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
135
168
  ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
136
169
  end
137
170
 
138
171
  # load the structure from the structure.sql file
139
- desc "Load structure for test databases via db:structure:load --> parallel:load_structure[num_cpus]"
140
- task :load_structure, :count do |_,args|
141
- ParallelTests::Tasks.run_in_parallel("rake #{ParallelTests::Tasks.purge_before_load} db:structure:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args)
172
+ # (faster for rails < 6.1, deprecated after and only configured by `ActiveRecord::Base.schema_format`)
173
+ desc "Load structure for test databases via db:schema:load --> parallel:load_structure[num_cpus]"
174
+ task :load_structure, :count do |_, args|
175
+ ParallelTests::Tasks.run_in_parallel(
176
+ "#{ParallelTests::Tasks.rake_bin} #{ParallelTests::Tasks.purge_before_load} " \
177
+ "db:structure:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args
178
+ )
142
179
  end
143
180
 
144
181
  desc "Load the seed data from db/seeds.rb via db:seed --> parallel:seed[num_cpus]"
145
- task :seed, :count do |_,args|
146
- ParallelTests::Tasks.run_in_parallel("rake db:seed RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
182
+ task :seed, :count do |_, args|
183
+ ParallelTests::Tasks.run_in_parallel(
184
+ "#{ParallelTests::Tasks.rake_bin} db:seed RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
185
+ )
147
186
  end
148
187
 
149
188
  desc "Launch given rake command in parallel"
150
189
  task :rake, :command, :count do |_, args|
151
- ParallelTests::Tasks.run_in_parallel("RAILS_ENV=#{ParallelTests::Tasks.rails_env} rake #{args.command}", args)
190
+ ParallelTests::Tasks.run_in_parallel(
191
+ "RAILS_ENV=#{ParallelTests::Tasks.rails_env} #{ParallelTests::Tasks.rake_bin} " \
192
+ "#{args.command}", args
193
+ )
152
194
  end
153
195
 
154
196
  ['test', 'spec', 'features', 'features-spinach'].each do |type|
155
197
  desc "Run #{type} in parallel with parallel:#{type}[num_cpus]"
156
- task type, [:count, :pattern, :options, :pass_through] do |t, args|
198
+ task type, [:count, :pattern, :options, :pass_through] do |_t, args|
157
199
  ParallelTests::Tasks.check_for_pending_migrations
158
200
  ParallelTests::Tasks.load_lib
159
201
 
@@ -162,16 +204,15 @@ namespace :parallel do
162
204
  'spec' => 'rspec',
163
205
  'test' => 'test',
164
206
  'features' => 'cucumber',
165
- 'features-spinach' => 'spinach',
207
+ 'features-spinach' => 'spinach'
166
208
  }[type]
167
209
 
168
- if test_framework == 'spinach'
169
- type = 'features'
170
- end
210
+ type = 'features' if test_framework == 'spinach'
171
211
  # Using the relative path to find the binary allow to run a specific version of it
172
212
  executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
173
213
 
174
- command = "#{ParallelTests.with_ruby_binary(Shellwords.escape(executable))} #{type} --type #{test_framework} " \
214
+ command = "#{ParallelTests.with_ruby_binary(Shellwords.escape(executable))} #{type} " \
215
+ "--type #{test_framework} " \
175
216
  "-n #{count} " \
176
217
  "--pattern '#{pattern}' " \
177
218
  "--test-options '#{options}' " \
@@ -1,17 +1,12 @@
1
+ # frozen_string_literal: true
1
2
  require 'parallel_tests'
2
3
 
3
4
  module ParallelTests
4
5
  module Test
5
6
  class Runner
6
- NAME = 'Test'
7
-
8
7
  class << self
9
8
  # --- usually overwritten by other runners
10
9
 
11
- def name
12
- NAME
13
- end
14
-
15
10
  def runtime_log
16
11
  'tmp/parallel_runtime_test.log'
17
12
  end
@@ -20,12 +15,16 @@ module ParallelTests
20
15
  /_(test|spec).rb$/
21
16
  end
22
17
 
18
+ def default_test_folder
19
+ "test"
20
+ end
21
+
23
22
  def test_file_name
24
23
  "test"
25
24
  end
26
25
 
27
26
  def run_tests(test_files, process_number, num_processes, options)
28
- require_list = test_files.map { |file| file.sub(" ", "\\ ") }.join(" ")
27
+ require_list = test_files.map { |file| file.gsub(" ", "\\ ") }.join(" ")
29
28
  cmd = "#{executable} -Itest -e '%w[#{require_list}].each { |f| require %{./\#{f}} }' -- #{options[:test_options]}"
30
29
  execute_command(cmd, process_number, num_processes, options)
31
30
  end
@@ -38,7 +37,7 @@ module ParallelTests
38
37
  # --- usually used by other runners
39
38
 
40
39
  # finds all tests and partitions them into groups
41
- def tests_in_groups(tests, num_groups, options={})
40
+ def tests_in_groups(tests, num_groups, options = {})
42
41
  tests = tests_with_size(tests, options)
43
42
  Grouper.in_even_groups_by_size(tests, num_groups, options)
44
43
  end
@@ -52,10 +51,17 @@ module ParallelTests
52
51
  when :filesize
53
52
  sort_by_filesize(tests)
54
53
  when :runtime
55
- sort_by_runtime(tests, runtimes(tests, options), options.merge(allowed_missing: (options[:allowed_missing_percent] || 50) / 100.0))
54
+ sort_by_runtime(
55
+ tests, runtimes(tests, options),
56
+ options.merge(allowed_missing: (options[:allowed_missing_percent] || 50) / 100.0)
57
+ )
56
58
  when nil
57
59
  # use recorded test runtime if we got enough data
58
- runtimes = runtimes(tests, options) rescue []
60
+ runtimes = begin
61
+ runtimes(tests, options)
62
+ rescue StandardError
63
+ []
64
+ end
59
65
  if runtimes.size * 1.5 > tests.size
60
66
  puts "Using recorded test runtime"
61
67
  sort_by_runtime(tests, runtimes)
@@ -73,12 +79,12 @@ module ParallelTests
73
79
  env = (options[:env] || {}).merge(
74
80
  "TEST_ENV_NUMBER" => test_env_number(process_number, options).to_s,
75
81
  "PARALLEL_TEST_GROUPS" => num_processes.to_s,
76
- "PARALLEL_PID_FILE" => ParallelTests.pid_file_path,
82
+ "PARALLEL_PID_FILE" => ParallelTests.pid_file_path
77
83
  )
78
84
  cmd = "nice #{cmd}" if options[:nice]
79
85
  cmd = "#{cmd} 2>&1" if options[:combine_stderr]
80
86
 
81
- puts cmd if options[:verbose] && !options[:serialize_stdout]
87
+ puts cmd if report_process_command?(options) && !options[:serialize_stdout]
82
88
 
83
89
  execute_command_and_capture_output(env, cmd, options)
84
90
  end
@@ -92,11 +98,11 @@ module ParallelTests
92
98
  end
93
99
  ParallelTests.pids.delete(pid) if pid
94
100
  exitstatus = $?.exitstatus
95
- seed = output[/seed (\d+)/,1]
101
+ seed = output[/seed (\d+)/, 1]
96
102
 
97
- output = [cmd, output].join("\n") if options[:verbose] && options[:serialize_stdout]
103
+ output = [cmd, output].join("\n") if report_process_command?(options) && options[:serialize_stdout]
98
104
 
99
- {:stdout => output, :exit_status => exitstatus, :command => cmd, :seed => seed}
105
+ { stdout: output, exit_status: exitstatus, command: cmd, seed: seed }
100
106
  end
101
107
 
102
108
  def find_results(test_output)
@@ -108,7 +114,7 @@ module ParallelTests
108
114
  end.compact
109
115
  end
110
116
 
111
- def test_env_number(process_number, options={})
117
+ def test_env_number(process_number, options = {})
112
118
  if process_number == 0 && !options[:first_is_1]
113
119
  ''
114
120
  else
@@ -118,7 +124,7 @@ module ParallelTests
118
124
 
119
125
  def summarize_results(results)
120
126
  sums = sum_up_results(results)
121
- sums.sort.map{|word, number| "#{number} #{word}#{'s' if number != 1}" }.join(', ')
127
+ sums.sort.map { |word, number| "#{number} #{word}#{'s' if number != 1}" }.join(', ')
122
128
  end
123
129
 
124
130
  # remove old seed and add new seed
@@ -138,19 +144,18 @@ module ParallelTests
138
144
  end
139
145
 
140
146
  def sum_up_results(results)
141
- results = results.join(' ').gsub(/s\b/,'') # combine and singularize results
147
+ results = results.join(' ').gsub(/s\b/, '') # combine and singularize results
142
148
  counts = results.scan(/(\d+) (\w+)/)
143
- counts.inject(Hash.new(0)) do |sum, (number, word)|
149
+ counts.each_with_object(Hash.new(0)) do |(number, word), sum|
144
150
  sum[word] += number.to_i
145
- sum
146
151
  end
147
152
  end
148
153
 
149
154
  # read output of the process and print it in chunks
150
- def capture_output(out, env, options={})
151
- result = ""
152
- loop do
153
- begin
155
+ def capture_output(out, env, options = {})
156
+ result = +""
157
+ begin
158
+ loop do
154
159
  read = out.readpartial(1000000) # read whatever chunk we can get
155
160
  if Encoding.default_internal
156
161
  read = read.force_encoding(Encoding.default_internal)
@@ -163,11 +168,13 @@ module ParallelTests
163
168
  $stdout.flush
164
169
  end
165
170
  end
166
- end rescue EOFError
171
+ rescue EOFError
172
+ nil
173
+ end
167
174
  result
168
175
  end
169
176
 
170
- def sort_by_runtime(tests, runtimes, options={})
177
+ def sort_by_runtime(tests, runtimes, options = {})
171
178
  allowed_missing = options[:allowed_missing] || 1.0
172
179
  allowed_missing = tests.size * allowed_missing
173
180
 
@@ -177,20 +184,14 @@ module ParallelTests
177
184
  allowed_missing -= 1 unless time = runtimes[test]
178
185
  if allowed_missing < 0
179
186
  log = options[:runtime_log] || runtime_log
180
- raise "Runtime log file '#{log}' does not contain sufficient data to sort #{tests.size} test files, please update it."
187
+ raise "Runtime log file '#{log}' does not contain sufficient data to sort #{tests.size} test files, please update or remove it."
181
188
  end
182
189
  [test, time]
183
190
  end
184
191
 
185
- if options[:verbose]
186
- puts "Runtime found for #{tests.count(&:last)} of #{tests.size} tests"
187
- end
192
+ puts "Runtime found for #{tests.count(&:last)} of #{tests.size} tests" if options[:verbose]
188
193
 
189
- # fill gaps with unknown-runtime if given, average otherwise
190
- known, unknown = tests.partition(&:last)
191
- average = (known.any? ? known.map!(&:last).inject(:+) / known.size : 1)
192
- unknown_runtime = options[:unknown_runtime] || average
193
- unknown.each { |set| set[1] = unknown_runtime }
194
+ set_unknown_runtime tests, options
194
195
  end
195
196
 
196
197
  def runtimes(tests, options)
@@ -198,7 +199,7 @@ module ParallelTests
198
199
  lines = File.read(log).split("\n")
199
200
  lines.each_with_object({}) do |line, times|
200
201
  test, _, time = line.rpartition(':')
201
- next unless test and time
202
+ next unless test && time
202
203
  times[test] = time.to_f if tests.include?(test)
203
204
  end
204
205
  end
@@ -225,7 +226,7 @@ module ParallelTests
225
226
  end.uniq
226
227
  end
227
228
 
228
- def files_in_folder(folder, options={})
229
+ def files_in_folder(folder, options = {})
229
230
  pattern = if options[:symlinks] == false # not nil or true
230
231
  "**/*"
231
232
  else
@@ -233,7 +234,23 @@ module ParallelTests
233
234
  # http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob
234
235
  "**{,/*/**}/*"
235
236
  end
236
- Dir[File.join(folder, pattern)].uniq
237
+ Dir[File.join(folder, pattern)].uniq.sort
238
+ end
239
+
240
+ private
241
+
242
+ # fill gaps with unknown-runtime if given, average otherwise
243
+ # NOTE: an optimization could be doing runtime by average runtime per file size, but would need file checks
244
+ def set_unknown_runtime(tests, options)
245
+ known, unknown = tests.partition(&:last)
246
+ return if unknown.empty?
247
+ unknown_runtime = options[:unknown_runtime] ||
248
+ (known.empty? ? 1 : known.map!(&:last).sum / known.size) # average
249
+ unknown.each { |set| set[1] = unknown_runtime }
250
+ end
251
+
252
+ def report_process_command?(options)
253
+ options[:verbose] || options[:verbose_process_command]
237
254
  end
238
255
  end
239
256
  end