parallel_tests 3.3.0 → 3.6.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.
- checksums.yaml +4 -4
- data/Readme.md +25 -9
- 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 +12 -12
- data/lib/parallel_tests/cli.rb +105 -65
- 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 +8 -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 +20 -21
- data/lib/parallel_tests/gherkin/runtime_logger.rb +2 -1
- data/lib/parallel_tests/grouper.rb +56 -5
- 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 +25 -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 +6 -2
- data/lib/parallel_tests/tasks.rb +44 -30
- data/lib/parallel_tests/test/runner.rb +36 -27
- data/lib/parallel_tests/test/runtime_logger.rb +19 -14
- data/lib/parallel_tests/version.rb +2 -1
- metadata +5 -5
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,11 @@ module ParallelTests
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def runtime_log
|
26
|
-
|
26
|
+
"tmp/parallel_runtime_rspec.log"
|
27
|
+
end
|
28
|
+
|
29
|
+
def default_test_folder
|
30
|
+
"spec"
|
27
31
|
end
|
28
32
|
|
29
33
|
def test_file_name
|
@@ -48,6 +52,22 @@ module ParallelTests
|
|
48
52
|
"#{clean} --seed #{seed}"
|
49
53
|
end
|
50
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
|
51
71
|
|
52
72
|
private
|
53
73
|
|
@@ -61,7 +81,7 @@ module ParallelTests
|
|
61
81
|
end
|
62
82
|
|
63
83
|
def spec_opts
|
64
|
-
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) }
|
65
85
|
return unless options_file
|
66
86
|
"-O #{options_file}"
|
67
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(*
|
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
|
@@ -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
|
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
|
|
@@ -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,30 +98,38 @@ 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
|
-
type =
|
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
|
+
|
123
133
|
Rake::Task["db:#{type}:dump"].invoke
|
124
134
|
|
125
135
|
# remove database connection to prevent "database is being accessed by other users"
|
@@ -128,7 +138,7 @@ namespace :parallel do
|
|
128
138
|
Rake::Task["parallel:load_#{type}"].invoke(args[:count])
|
129
139
|
else
|
130
140
|
# slow: dump and load in in serial
|
131
|
-
args = args.to_hash.merge(:
|
141
|
+
args = args.to_hash.merge(non_parallel: true) # normal merge returns nil
|
132
142
|
task_name = Rake::Task.task_defined?('db:test:prepare') ? 'db:test:prepare' : 'app:db:test:prepare'
|
133
143
|
ParallelTests::Tasks.run_in_parallel("#{ParallelTests::Tasks.rake_bin} #{task_name}", args)
|
134
144
|
next
|
@@ -137,49 +147,55 @@ namespace :parallel do
|
|
137
147
|
|
138
148
|
# when dumping/resetting takes too long
|
139
149
|
desc "Update test databases via db:migrate --> parallel:migrate[num_cpus]"
|
140
|
-
task :migrate, :count do |_,args|
|
150
|
+
task :migrate, :count do |_, args|
|
141
151
|
ParallelTests::Tasks.run_in_parallel(
|
142
|
-
"#{ParallelTests::Tasks.rake_bin} db:migrate RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
|
152
|
+
"#{ParallelTests::Tasks.rake_bin} db:migrate RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
|
153
|
+
)
|
143
154
|
end
|
144
155
|
|
145
156
|
desc "Rollback test databases via db:rollback --> parallel:rollback[num_cpus]"
|
146
|
-
task :rollback, :count do |_,args|
|
157
|
+
task :rollback, :count do |_, args|
|
147
158
|
ParallelTests::Tasks.run_in_parallel(
|
148
|
-
"#{ParallelTests::Tasks.rake_bin} db:rollback RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
|
159
|
+
"#{ParallelTests::Tasks.rake_bin} db:rollback RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
|
160
|
+
)
|
149
161
|
end
|
150
162
|
|
151
163
|
# just load the schema (good for integration server <-> no development db)
|
152
164
|
desc "Load dumped schema for test databases via db:schema:load --> parallel:load_schema[num_cpus]"
|
153
|
-
task :load_schema, :count do |_,args|
|
165
|
+
task :load_schema, :count do |_, args|
|
154
166
|
command = "#{ParallelTests::Tasks.rake_bin} #{ParallelTests::Tasks.purge_before_load} " \
|
155
167
|
"db:schema:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
|
156
168
|
ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
|
157
169
|
end
|
158
170
|
|
159
171
|
# load the structure from the structure.sql file
|
160
|
-
|
161
|
-
|
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|
|
162
175
|
ParallelTests::Tasks.run_in_parallel(
|
163
176
|
"#{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
|
177
|
+
"db:structure:load RAILS_ENV=#{ParallelTests::Tasks.rails_env} DISABLE_DATABASE_ENVIRONMENT_CHECK=1", args
|
178
|
+
)
|
165
179
|
end
|
166
180
|
|
167
181
|
desc "Load the seed data from db/seeds.rb via db:seed --> parallel:seed[num_cpus]"
|
168
|
-
task :seed, :count do |_,args|
|
182
|
+
task :seed, :count do |_, args|
|
169
183
|
ParallelTests::Tasks.run_in_parallel(
|
170
|
-
"#{ParallelTests::Tasks.rake_bin} db:seed RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
|
184
|
+
"#{ParallelTests::Tasks.rake_bin} db:seed RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args
|
185
|
+
)
|
171
186
|
end
|
172
187
|
|
173
188
|
desc "Launch given rake command in parallel"
|
174
189
|
task :rake, :command, :count do |_, args|
|
175
190
|
ParallelTests::Tasks.run_in_parallel(
|
176
191
|
"RAILS_ENV=#{ParallelTests::Tasks.rails_env} #{ParallelTests::Tasks.rake_bin} " \
|
177
|
-
"#{args.command}", args
|
192
|
+
"#{args.command}", args
|
193
|
+
)
|
178
194
|
end
|
179
195
|
|
180
196
|
['test', 'spec', 'features', 'features-spinach'].each do |type|
|
181
197
|
desc "Run #{type} in parallel with parallel:#{type}[num_cpus]"
|
182
|
-
task type, [:count, :pattern, :options, :pass_through] do |
|
198
|
+
task type, [:count, :pattern, :options, :pass_through] do |_t, args|
|
183
199
|
ParallelTests::Tasks.check_for_pending_migrations
|
184
200
|
ParallelTests::Tasks.load_lib
|
185
201
|
|
@@ -188,12 +204,10 @@ namespace :parallel do
|
|
188
204
|
'spec' => 'rspec',
|
189
205
|
'test' => 'test',
|
190
206
|
'features' => 'cucumber',
|
191
|
-
'features-spinach' => 'spinach'
|
207
|
+
'features-spinach' => 'spinach'
|
192
208
|
}[type]
|
193
209
|
|
194
|
-
if test_framework == 'spinach'
|
195
|
-
type = 'features'
|
196
|
-
end
|
210
|
+
type = 'features' if test_framework == 'spinach'
|
197
211
|
# Using the relative path to find the binary allow to run a specific version of it
|
198
212
|
executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
|
199
213
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'parallel_tests'
|
2
3
|
|
3
4
|
module ParallelTests
|
@@ -14,6 +15,10 @@ module ParallelTests
|
|
14
15
|
/_(test|spec).rb$/
|
15
16
|
end
|
16
17
|
|
18
|
+
def default_test_folder
|
19
|
+
"test"
|
20
|
+
end
|
21
|
+
|
17
22
|
def test_file_name
|
18
23
|
"test"
|
19
24
|
end
|
@@ -32,7 +37,7 @@ module ParallelTests
|
|
32
37
|
# --- usually used by other runners
|
33
38
|
|
34
39
|
# finds all tests and partitions them into groups
|
35
|
-
def tests_in_groups(tests, num_groups, options={})
|
40
|
+
def tests_in_groups(tests, num_groups, options = {})
|
36
41
|
tests = tests_with_size(tests, options)
|
37
42
|
Grouper.in_even_groups_by_size(tests, num_groups, options)
|
38
43
|
end
|
@@ -46,10 +51,17 @@ module ParallelTests
|
|
46
51
|
when :filesize
|
47
52
|
sort_by_filesize(tests)
|
48
53
|
when :runtime
|
49
|
-
sort_by_runtime(
|
54
|
+
sort_by_runtime(
|
55
|
+
tests, runtimes(tests, options),
|
56
|
+
options.merge(allowed_missing: (options[:allowed_missing_percent] || 50) / 100.0)
|
57
|
+
)
|
50
58
|
when nil
|
51
59
|
# use recorded test runtime if we got enough data
|
52
|
-
runtimes =
|
60
|
+
runtimes = begin
|
61
|
+
runtimes(tests, options)
|
62
|
+
rescue StandardError
|
63
|
+
[]
|
64
|
+
end
|
53
65
|
if runtimes.size * 1.5 > tests.size
|
54
66
|
puts "Using recorded test runtime"
|
55
67
|
sort_by_runtime(tests, runtimes)
|
@@ -67,7 +79,7 @@ module ParallelTests
|
|
67
79
|
env = (options[:env] || {}).merge(
|
68
80
|
"TEST_ENV_NUMBER" => test_env_number(process_number, options).to_s,
|
69
81
|
"PARALLEL_TEST_GROUPS" => num_processes.to_s,
|
70
|
-
"PARALLEL_PID_FILE" => ParallelTests.pid_file_path
|
82
|
+
"PARALLEL_PID_FILE" => ParallelTests.pid_file_path
|
71
83
|
)
|
72
84
|
cmd = "nice #{cmd}" if options[:nice]
|
73
85
|
cmd = "#{cmd} 2>&1" if options[:combine_stderr]
|
@@ -86,13 +98,11 @@ module ParallelTests
|
|
86
98
|
end
|
87
99
|
ParallelTests.pids.delete(pid) if pid
|
88
100
|
exitstatus = $?.exitstatus
|
89
|
-
seed = output[/seed (\d+)/,1]
|
101
|
+
seed = output[/seed (\d+)/, 1]
|
90
102
|
|
91
|
-
if report_process_command?(options) && options[:serialize_stdout]
|
92
|
-
output = [cmd, output].join("\n")
|
93
|
-
end
|
103
|
+
output = [cmd, output].join("\n") if report_process_command?(options) && options[:serialize_stdout]
|
94
104
|
|
95
|
-
{:
|
105
|
+
{ stdout: output, exit_status: exitstatus, command: cmd, seed: seed }
|
96
106
|
end
|
97
107
|
|
98
108
|
def find_results(test_output)
|
@@ -104,7 +114,7 @@ module ParallelTests
|
|
104
114
|
end.compact
|
105
115
|
end
|
106
116
|
|
107
|
-
def test_env_number(process_number, options={})
|
117
|
+
def test_env_number(process_number, options = {})
|
108
118
|
if process_number == 0 && !options[:first_is_1]
|
109
119
|
''
|
110
120
|
else
|
@@ -114,7 +124,7 @@ module ParallelTests
|
|
114
124
|
|
115
125
|
def summarize_results(results)
|
116
126
|
sums = sum_up_results(results)
|
117
|
-
sums.sort.map{|word, number|
|
127
|
+
sums.sort.map { |word, number| "#{number} #{word}#{'s' if number != 1}" }.join(', ')
|
118
128
|
end
|
119
129
|
|
120
130
|
# remove old seed and add new seed
|
@@ -134,19 +144,18 @@ module ParallelTests
|
|
134
144
|
end
|
135
145
|
|
136
146
|
def sum_up_results(results)
|
137
|
-
results = results.join(' ').gsub(/s\b/,'') # combine and singularize results
|
147
|
+
results = results.join(' ').gsub(/s\b/, '') # combine and singularize results
|
138
148
|
counts = results.scan(/(\d+) (\w+)/)
|
139
|
-
counts.
|
149
|
+
counts.each_with_object(Hash.new(0)) do |(number, word), sum|
|
140
150
|
sum[word] += number.to_i
|
141
|
-
sum
|
142
151
|
end
|
143
152
|
end
|
144
153
|
|
145
154
|
# read output of the process and print it in chunks
|
146
|
-
def capture_output(out, env, options={})
|
147
|
-
result = ""
|
148
|
-
|
149
|
-
|
155
|
+
def capture_output(out, env, options = {})
|
156
|
+
result = +""
|
157
|
+
begin
|
158
|
+
loop do
|
150
159
|
read = out.readpartial(1000000) # read whatever chunk we can get
|
151
160
|
if Encoding.default_internal
|
152
161
|
read = read.force_encoding(Encoding.default_internal)
|
@@ -159,11 +168,13 @@ module ParallelTests
|
|
159
168
|
$stdout.flush
|
160
169
|
end
|
161
170
|
end
|
162
|
-
|
171
|
+
rescue EOFError
|
172
|
+
nil
|
173
|
+
end
|
163
174
|
result
|
164
175
|
end
|
165
176
|
|
166
|
-
def sort_by_runtime(tests, runtimes, options={})
|
177
|
+
def sort_by_runtime(tests, runtimes, options = {})
|
167
178
|
allowed_missing = options[:allowed_missing] || 1.0
|
168
179
|
allowed_missing = tests.size * allowed_missing
|
169
180
|
|
@@ -178,9 +189,7 @@ module ParallelTests
|
|
178
189
|
[test, time]
|
179
190
|
end
|
180
191
|
|
181
|
-
if options[:verbose]
|
182
|
-
puts "Runtime found for #{tests.count(&:last)} of #{tests.size} tests"
|
183
|
-
end
|
192
|
+
puts "Runtime found for #{tests.count(&:last)} of #{tests.size} tests" if options[:verbose]
|
184
193
|
|
185
194
|
set_unknown_runtime tests, options
|
186
195
|
end
|
@@ -190,7 +199,7 @@ module ParallelTests
|
|
190
199
|
lines = File.read(log).split("\n")
|
191
200
|
lines.each_with_object({}) do |line, times|
|
192
201
|
test, _, time = line.rpartition(':')
|
193
|
-
next unless test
|
202
|
+
next unless test && time
|
194
203
|
times[test] = time.to_f if tests.include?(test)
|
195
204
|
end
|
196
205
|
end
|
@@ -217,7 +226,7 @@ module ParallelTests
|
|
217
226
|
end.uniq
|
218
227
|
end
|
219
228
|
|
220
|
-
def files_in_folder(folder, options={})
|
229
|
+
def files_in_folder(folder, options = {})
|
221
230
|
pattern = if options[:symlinks] == false # not nil or true
|
222
231
|
"**/*"
|
223
232
|
else
|
@@ -225,7 +234,7 @@ module ParallelTests
|
|
225
234
|
# http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob
|
226
235
|
"**{,/*/**}/*"
|
227
236
|
end
|
228
|
-
Dir[File.join(folder, pattern)].uniq
|
237
|
+
Dir[File.join(folder, pattern)].uniq.sort
|
229
238
|
end
|
230
239
|
|
231
240
|
private
|
@@ -236,7 +245,7 @@ module ParallelTests
|
|
236
245
|
known, unknown = tests.partition(&:last)
|
237
246
|
return if unknown.empty?
|
238
247
|
unknown_runtime = options[:unknown_runtime] ||
|
239
|
-
(known.empty? ? 1 : known.map!(&:last).
|
248
|
+
(known.empty? ? 1 : known.map!(&:last).sum / known.size) # average
|
240
249
|
unknown.each { |set| set[1] = unknown_runtime }
|
241
250
|
end
|
242
251
|
|