parallel_tests 2.28.0 → 3.7.3

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