parallel_tests 2.28.0 → 3.7.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 +46 -28
- 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 +133 -68
- data/lib/parallel_tests/cucumber/failures_logger.rb +1 -1
- data/lib/parallel_tests/cucumber/features_with_steps.rb +32 -0
- data/lib/parallel_tests/cucumber/runner.rb +8 -5
- data/lib/parallel_tests/cucumber/scenario_line_logger.rb +18 -16
- data/lib/parallel_tests/cucumber/scenarios.rb +20 -30
- data/lib/parallel_tests/gherkin/io.rb +2 -3
- data/lib/parallel_tests/gherkin/listener.rb +10 -12
- data/lib/parallel_tests/gherkin/runner.rb +20 -21
- data/lib/parallel_tests/gherkin/runtime_logger.rb +3 -2
- data/lib/parallel_tests/grouper.rb +92 -28
- data/lib/parallel_tests/pids.rb +4 -3
- 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 +27 -12
- 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 +81 -40
- data/lib/parallel_tests/test/runner.rb +54 -37
- data/lib/parallel_tests/test/runtime_logger.rb +19 -14
- data/lib/parallel_tests/version.rb +2 -1
- metadata +11 -7
@@ -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,6 +15,10 @@ 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
|
@@ -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(
|
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 =
|
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
|
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
|
103
|
+
output = [cmd, output].join("\n") if report_process_command?(options) && options[:serialize_stdout]
|
98
104
|
|
99
|
-
{:
|
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|
|
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.
|
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
|
-
|
153
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'parallel_tests'
|
2
3
|
require 'parallel_tests/test/runner'
|
3
4
|
|
@@ -22,7 +23,7 @@ module ParallelTests
|
|
22
23
|
separator = "\n"
|
23
24
|
groups = logfile.read.split(separator).map { |line| line.split(":") }.group_by(&:first)
|
24
25
|
lines = groups.map do |file, times|
|
25
|
-
time = "%.2f" % times.map(&:last).map(&:to_f).
|
26
|
+
time = "%.2f" % times.map(&:last).map(&:to_f).sum
|
26
27
|
"#{file}:#{time}"
|
27
28
|
end
|
28
29
|
logfile.rewind
|
@@ -34,7 +35,7 @@ module ParallelTests
|
|
34
35
|
private
|
35
36
|
|
36
37
|
def with_locked_log
|
37
|
-
File.open(logfile, File::RDWR|File::CREAT) do |logfile|
|
38
|
+
File.open(logfile, File::RDWR | File::CREAT) do |logfile|
|
38
39
|
logfile.flock(File::LOCK_EX)
|
39
40
|
yield logfile
|
40
41
|
end
|
@@ -59,7 +60,7 @@ module ParallelTests
|
|
59
60
|
end
|
60
61
|
|
61
62
|
def message(test, delta)
|
62
|
-
return unless method = test.public_instance_methods(true).detect { |
|
63
|
+
return unless method = test.public_instance_methods(true).detect { |m| m =~ /^test_/ }
|
63
64
|
filename = test.instance_method(method).source_location.first.sub("#{Dir.pwd}/", "")
|
64
65
|
"#{filename}:#{delta}"
|
65
66
|
end
|
@@ -74,22 +75,26 @@ end
|
|
74
75
|
|
75
76
|
if defined?(Minitest::Runnable) # Minitest 5
|
76
77
|
class << Minitest::Runnable
|
77
|
-
prepend(
|
78
|
-
|
79
|
-
|
80
|
-
|
78
|
+
prepend(
|
79
|
+
Module.new do
|
80
|
+
def run(*)
|
81
|
+
ParallelTests::Test::RuntimeLogger.log_test_run(self) do
|
82
|
+
super
|
83
|
+
end
|
81
84
|
end
|
82
85
|
end
|
83
|
-
|
86
|
+
)
|
84
87
|
end
|
85
88
|
|
86
89
|
class << Minitest
|
87
|
-
prepend(
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
90
|
+
prepend(
|
91
|
+
Module.new do
|
92
|
+
def run(*args)
|
93
|
+
result = super
|
94
|
+
ParallelTests::Test::RuntimeLogger.unique_log
|
95
|
+
result
|
96
|
+
end
|
92
97
|
end
|
93
|
-
|
98
|
+
)
|
94
99
|
end
|
95
100
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: parallel_tests
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Grosser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-04-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parallel
|
@@ -42,6 +42,7 @@ files:
|
|
42
42
|
- lib/parallel_tests.rb
|
43
43
|
- lib/parallel_tests/cli.rb
|
44
44
|
- lib/parallel_tests/cucumber/failures_logger.rb
|
45
|
+
- lib/parallel_tests/cucumber/features_with_steps.rb
|
45
46
|
- lib/parallel_tests/cucumber/runner.rb
|
46
47
|
- lib/parallel_tests/cucumber/scenario_line_logger.rb
|
47
48
|
- lib/parallel_tests/cucumber/scenarios.rb
|
@@ -62,10 +63,14 @@ files:
|
|
62
63
|
- lib/parallel_tests/test/runner.rb
|
63
64
|
- lib/parallel_tests/test/runtime_logger.rb
|
64
65
|
- lib/parallel_tests/version.rb
|
65
|
-
homepage:
|
66
|
+
homepage: https://github.com/grosser/parallel_tests
|
66
67
|
licenses:
|
67
68
|
- MIT
|
68
|
-
metadata:
|
69
|
+
metadata:
|
70
|
+
bug_tracker_uri: https://github.com/grosser/parallel_tests/issues
|
71
|
+
documentation_uri: https://github.com/grosser/parallel_tests/blob/v3.7.0/Readme.md
|
72
|
+
source_code_uri: https://github.com/grosser/parallel_tests/tree/v3.7.0
|
73
|
+
wiki_uri: https://github.com/grosser/parallel_tests/wiki
|
69
74
|
post_install_message:
|
70
75
|
rdoc_options: []
|
71
76
|
require_paths:
|
@@ -74,15 +79,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
79
|
requirements:
|
75
80
|
- - ">="
|
76
81
|
- !ruby/object:Gem::Version
|
77
|
-
version: 2.
|
82
|
+
version: 2.5.0
|
78
83
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
84
|
requirements:
|
80
85
|
- - ">="
|
81
86
|
- !ruby/object:Gem::Version
|
82
87
|
version: '0'
|
83
88
|
requirements: []
|
84
|
-
|
85
|
-
rubygems_version: 2.7.6
|
89
|
+
rubygems_version: 3.1.3
|
86
90
|
signing_key:
|
87
91
|
specification_version: 4
|
88
92
|
summary: Run Test::Unit / RSpec / Cucumber / Spinach in parallel
|