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