parallel_specs 0.9.0 → 0.9.2
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 +105 -3
- data/lib/parallel_specs/cli/dashboard.rb +92 -112
- data/lib/parallel_specs/cli.rb +148 -60
- data/lib/parallel_specs/grouper.rb +35 -6
- data/lib/parallel_specs/pids.rb +1 -1
- data/lib/parallel_specs/railtie.rb +11 -0
- data/lib/parallel_specs/rspec/dashboard_logger.rb +12 -12
- data/lib/parallel_specs/rspec/logger_base.rb +6 -6
- data/lib/parallel_specs/rspec/runner.rb +16 -16
- data/lib/parallel_specs/rspec/runtime_logger.rb +18 -9
- data/lib/parallel_specs/tasks.rb +264 -0
- data/lib/parallel_specs/test/runner.rb +32 -26
- data/lib/parallel_specs/version.rb +1 -1
- data/lib/parallel_specs.rb +30 -23
- metadata +4 -2
data/lib/parallel_specs/cli.rb
CHANGED
|
@@ -1,31 +1,38 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
3
|
+
require "optparse"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "pathname"
|
|
6
|
+
require "shellwords"
|
|
7
|
+
require "tmpdir"
|
|
8
8
|
|
|
9
|
-
require
|
|
10
|
-
require
|
|
11
|
-
require
|
|
9
|
+
require "parallel_specs"
|
|
10
|
+
require "parallel_specs/cli/dashboard"
|
|
11
|
+
require "parallel_specs/rspec/runner"
|
|
12
12
|
|
|
13
13
|
module ParallelSpecs
|
|
14
14
|
class CLI
|
|
15
|
+
DEFAULT_RERUN_COMMAND_SPEC_FILE_LIMIT = 25
|
|
16
|
+
DEFAULT_RERUN_COMMAND_CHAR_LIMIT = 2_000
|
|
17
|
+
|
|
15
18
|
def initialize
|
|
16
19
|
@runner = ParallelSpecs::RSpec::Runner
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
def run(argv)
|
|
20
|
-
Signal.trap(
|
|
23
|
+
Signal.trap("INT") { handle_interrupt }
|
|
21
24
|
|
|
22
25
|
options = parse_options!(argv)
|
|
23
|
-
ENV[
|
|
26
|
+
ENV["DISABLE_SPRING"] ||= "1"
|
|
24
27
|
|
|
25
28
|
num_processes = ParallelSpecs.determine_number_of_processes(options[:count])
|
|
26
|
-
abort
|
|
29
|
+
abort "Process count must be greater than 0" unless num_processes.positive?
|
|
27
30
|
|
|
28
31
|
run_tests_in_parallel(num_processes, options)
|
|
32
|
+
rescue ParallelSpecs::ConfigurationError => e
|
|
33
|
+
abort e.message
|
|
34
|
+
rescue ParallelSpecs::Test::Runner::MissingTestFileError => e
|
|
35
|
+
abort e.message
|
|
29
36
|
end
|
|
30
37
|
|
|
31
38
|
private
|
|
@@ -137,7 +144,7 @@ module ParallelSpecs
|
|
|
137
144
|
runtime_log = options[:runtime_log] || @runner.runtime_log
|
|
138
145
|
should_merge_runtime_logs = false
|
|
139
146
|
|
|
140
|
-
Dir.mktmpdir(
|
|
147
|
+
Dir.mktmpdir("parallel_specs-runtime") do |dir|
|
|
141
148
|
runtime_log_files = groups.each_index.to_h do |index|
|
|
142
149
|
[index, File.join(dir, "worker-#{index + 1}.log")]
|
|
143
150
|
end
|
|
@@ -162,13 +169,13 @@ module ParallelSpecs
|
|
|
162
169
|
|
|
163
170
|
missing_logs = runtime_log_files.values.reject { |path| File.file?(path) }
|
|
164
171
|
unless missing_logs.empty?
|
|
165
|
-
warn "parallel_specs: not updating runtime log #{runtime_log}; missing worker runtime logs: #{missing_logs.join(
|
|
172
|
+
warn "parallel_specs: not updating runtime log #{runtime_log}; missing worker runtime logs: #{missing_logs.join(", ")}"
|
|
166
173
|
return false
|
|
167
174
|
end
|
|
168
175
|
|
|
169
176
|
FileUtils.mkdir_p(File.dirname(runtime_log))
|
|
170
177
|
temporary_runtime_log = "#{runtime_log}.#{Process.pid}.tmp"
|
|
171
|
-
File.open(temporary_runtime_log,
|
|
178
|
+
File.open(temporary_runtime_log, "w") do |output|
|
|
172
179
|
runtime_log_files.each_value do |path|
|
|
173
180
|
File.foreach(path) { |line| output.write(line) }
|
|
174
181
|
end
|
|
@@ -182,10 +189,10 @@ module ParallelSpecs
|
|
|
182
189
|
def with_dashboard(groups, options)
|
|
183
190
|
return yield unless options[:dashboard]
|
|
184
191
|
|
|
185
|
-
Dir.mktmpdir(
|
|
192
|
+
Dir.mktmpdir("parallel_specs-dashboard") do |dir|
|
|
186
193
|
event_files = groups.each_index.to_h do |index|
|
|
187
194
|
path = File.join(dir, "worker-#{index + 1}.jsonl")
|
|
188
|
-
File.write(path,
|
|
195
|
+
File.write(path, "")
|
|
189
196
|
[index, path]
|
|
190
197
|
end
|
|
191
198
|
|
|
@@ -193,7 +200,7 @@ module ParallelSpecs
|
|
|
193
200
|
options[:dashboard_runner] = ParallelSpecs::CLI::Dashboard.new(
|
|
194
201
|
groups: groups,
|
|
195
202
|
event_files: event_files,
|
|
196
|
-
mode: dashboard_mode,
|
|
203
|
+
mode: dashboard_mode(options),
|
|
197
204
|
use_colors: use_colors?
|
|
198
205
|
)
|
|
199
206
|
|
|
@@ -216,8 +223,8 @@ module ParallelSpecs
|
|
|
216
223
|
|
|
217
224
|
puts "\nFailed worker output:\n"
|
|
218
225
|
failures.each do |result|
|
|
219
|
-
worker_label = result.dig(:env,
|
|
220
|
-
worker_label =
|
|
226
|
+
worker_label = result.dig(:env, "TEST_ENV_NUMBER")
|
|
227
|
+
worker_label = "1" if worker_label.to_s.empty?
|
|
221
228
|
puts "--- worker #{worker_label} ---"
|
|
222
229
|
puts result[:stdout]
|
|
223
230
|
end
|
|
@@ -227,18 +234,79 @@ module ParallelSpecs
|
|
|
227
234
|
failures = test_results.reject { |result| result[:exit_status].zero? }
|
|
228
235
|
return if failures.empty?
|
|
229
236
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
237
|
+
rerun_commands = failures.map do |result|
|
|
238
|
+
{result: result, command: @runner.rerun_command(result[:command], seed: result[:seed])}
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
if print_full_rerun_commands?(rerun_commands)
|
|
242
|
+
puts "\nRerun failed worker commands:\n"
|
|
243
|
+
rerun_commands.each do |entry|
|
|
244
|
+
@runner.print_command(entry[:command], entry[:result][:env] || {})
|
|
245
|
+
end
|
|
246
|
+
else
|
|
247
|
+
report_failure_rerun_command_summary(rerun_commands)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def print_full_rerun_commands?(rerun_commands)
|
|
252
|
+
return true if truthy_env?("PARALLEL_SPECS_FULL_RERUN_COMMANDS")
|
|
253
|
+
|
|
254
|
+
rerun_commands.all? do |entry|
|
|
255
|
+
rerun_command_spec_file_count(entry[:command]) <= rerun_command_spec_file_limit &&
|
|
256
|
+
rerun_command_length(entry[:command], entry[:result][:env] || {}) <= rerun_command_char_limit
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def report_failure_rerun_command_summary(rerun_commands)
|
|
261
|
+
total_spec_files = rerun_commands.sum { |entry| rerun_command_spec_file_count(entry[:command]) }
|
|
262
|
+
|
|
263
|
+
puts "\nFull worker rerun commands omitted to keep failure output readable."
|
|
264
|
+
puts "#{pluralize(rerun_commands.size, "failed worker")} included #{pluralize(total_spec_files, @runner.test_file_name)}."
|
|
265
|
+
puts "RSpec failure output above includes failed example locations."
|
|
266
|
+
puts "Set PARALLEL_SPECS_FULL_RERUN_COMMANDS=1 to print full worker rerun commands."
|
|
267
|
+
|
|
268
|
+
rerun_commands.each do |entry|
|
|
269
|
+
result = entry[:result]
|
|
270
|
+
worker_label = result.dig(:env, "TEST_ENV_NUMBER")
|
|
271
|
+
worker_label = "1" if worker_label.to_s.empty?
|
|
272
|
+
seed = result[:seed] ? ", seed #{result[:seed]}" : ""
|
|
273
|
+
puts "worker #{worker_label}: #{pluralize(rerun_command_spec_file_count(entry[:command]), @runner.test_file_name)}#{seed}"
|
|
234
274
|
end
|
|
235
275
|
end
|
|
236
276
|
|
|
277
|
+
def rerun_command_spec_file_count(command)
|
|
278
|
+
command.count { |arg| arg.end_with?("_spec.rb") }
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def rerun_command_length(command, env)
|
|
282
|
+
rerun_env = env.slice("TEST_ENV_NUMBER", "PARALLEL_SPECS_GROUPS").reject { |_key, value| value.to_s.empty? }
|
|
283
|
+
env_string = rerun_env.map { |key, value| "#{key}=#{Shellwords.escape(value)}" }.join(" ")
|
|
284
|
+
[env_string, Shellwords.shelljoin(command)].reject(&:empty?).join(" ").length
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def rerun_command_spec_file_limit
|
|
288
|
+
positive_integer_env("PARALLEL_SPECS_RERUN_COMMAND_SPEC_FILE_LIMIT") || DEFAULT_RERUN_COMMAND_SPEC_FILE_LIMIT
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def rerun_command_char_limit
|
|
292
|
+
positive_integer_env("PARALLEL_SPECS_RERUN_COMMAND_CHAR_LIMIT") || DEFAULT_RERUN_COMMAND_CHAR_LIMIT
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def positive_integer_env(name)
|
|
296
|
+
Integer(ENV.fetch(name, nil)).then { |value| value.positive? ? value : nil }
|
|
297
|
+
rescue ArgumentError, TypeError
|
|
298
|
+
nil
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def truthy_env?(name)
|
|
302
|
+
%w[1 true yes].include?(ENV.fetch(name, "").downcase)
|
|
303
|
+
end
|
|
304
|
+
|
|
237
305
|
def report_number_of_tests(groups)
|
|
238
306
|
num_processes = groups.size
|
|
239
307
|
num_tests = groups.map(&:size).sum
|
|
240
308
|
tests_per_process = num_processes.zero? ? 0 : num_tests / num_processes
|
|
241
|
-
puts "#{pluralize(num_processes,
|
|
309
|
+
puts "#{pluralize(num_processes, "process")} for #{pluralize(num_tests, @runner.test_file_name)}, ~ #{pluralize(tests_per_process, @runner.test_file_name)} per process"
|
|
242
310
|
end
|
|
243
311
|
|
|
244
312
|
def any_test_failed?(test_results)
|
|
@@ -251,7 +319,8 @@ module ParallelSpecs
|
|
|
251
319
|
|
|
252
320
|
def parse_options!(argv)
|
|
253
321
|
newline_padding = 33
|
|
254
|
-
options = {
|
|
322
|
+
options = {dashboard: true}
|
|
323
|
+
cli_argv, rspec_argv = split_rspec_args(argv)
|
|
255
324
|
|
|
256
325
|
OptionParser.new do |opts|
|
|
257
326
|
opts.banner = <<~BANNER
|
|
@@ -266,48 +335,65 @@ module ParallelSpecs
|
|
|
266
335
|
Options are:
|
|
267
336
|
BANNER
|
|
268
337
|
|
|
269
|
-
opts.on(
|
|
270
|
-
opts.on(
|
|
271
|
-
opts.on(
|
|
338
|
+
opts.on("-n PROCESSES", Integer, "How many processes to use, default: available CPUs") { |n| options[:count] = n }
|
|
339
|
+
opts.on("-o", "--test-options OPTIONS", "Pass these options to rspec") { |arg| options[:test_options] = Shellwords.shellsplit(arg) }
|
|
340
|
+
opts.on("--group-by TYPE", heredoc(<<~TEXT, newline_padding)) { |type| options[:group_by] = type.to_sym }
|
|
272
341
|
group specs by:
|
|
273
342
|
found - order of finding files
|
|
274
343
|
filesize - by size of the file
|
|
275
344
|
runtime - info from runtime log
|
|
276
345
|
default - runtime when runtime log is filled otherwise filesize
|
|
277
346
|
TEXT
|
|
278
|
-
opts.on(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
opts.on(
|
|
282
|
-
opts.on(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
opts.on(
|
|
286
|
-
opts.on(
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
347
|
+
opts.on("-s PATTERN", "--single PATTERN", "Run all matching spec files in the same process") do |pattern|
|
|
348
|
+
(options[:single_process] ||= []) << Regexp.new(pattern)
|
|
349
|
+
end
|
|
350
|
+
opts.on("-i", "--isolate", "Do not run other specs in workers reserved by --single") { options[:isolate] = true }
|
|
351
|
+
opts.on("--isolate-n PROCESSES", Integer, "Spread --single specs across this many isolated workers") do |count|
|
|
352
|
+
options[:isolate_count] = count
|
|
353
|
+
end
|
|
354
|
+
opts.on("--dashboard-mode MODE", %w[interactive plain], "Dashboard mode: interactive or plain") { |mode| options[:dashboard_mode] = mode.to_sym }
|
|
355
|
+
opts.on("--plain-dashboard", "Use the plain text dashboard output") { options[:dashboard_mode] = :plain }
|
|
356
|
+
opts.on("--plain", "Use the plain text dashboard output") { options[:dashboard_mode] = :plain }
|
|
357
|
+
opts.on("--pattern PATTERN", "Only run spec files matching PATTERN") { |pattern| options[:pattern] = Regexp.new(pattern) }
|
|
358
|
+
opts.on("--exclude-pattern PATTERN", "Skip spec files matching PATTERN") { |pattern| options[:exclude_pattern] = Regexp.new(pattern) }
|
|
359
|
+
opts.on("--runtime-log PATH", "Read spec runtimes from PATH; with --record-runtime, write the completed run there") { |path| options[:runtime_log] = path }
|
|
360
|
+
opts.on("--allowed-missing COUNT", Integer, "Allowed percentage of missing runtimes (default = 50)") { |percent| options[:allowed_missing_percent] = percent }
|
|
361
|
+
opts.on("--unknown-runtime SECONDS", Float, "Use given number as unknown runtime (otherwise use average time)") { |time| options[:unknown_runtime] = time }
|
|
362
|
+
opts.on("--record-runtime", "Record runtimes and replace the runtime log only after a successful complete run") { options[:record_runtime] = true }
|
|
363
|
+
opts.on("--fail-fast", "Stop remaining workers after one worker fails") { options[:fail_fast] = true }
|
|
364
|
+
opts.on("-v", "--version", "Show version") {
|
|
365
|
+
puts ParallelSpecs::VERSION
|
|
366
|
+
exit 0
|
|
367
|
+
}
|
|
368
|
+
opts.on("-h", "--help", "Show this help") {
|
|
369
|
+
puts opts
|
|
370
|
+
exit 0
|
|
371
|
+
}
|
|
372
|
+
end.parse!(cli_argv)
|
|
373
|
+
|
|
374
|
+
files, remaining = extract_file_paths(cli_argv, rspec_argv)
|
|
292
375
|
files = [@runner.default_test_folder] if files.empty?
|
|
293
376
|
options[:files] = files.map { |file_path| Pathname.new(file_path).cleanpath.to_s }
|
|
294
377
|
append_test_options(options, remaining)
|
|
295
378
|
options
|
|
296
379
|
end
|
|
297
380
|
|
|
298
|
-
def
|
|
299
|
-
dash_index = argv.
|
|
300
|
-
|
|
301
|
-
|
|
381
|
+
def split_rspec_args(argv)
|
|
382
|
+
dash_index = argv.index("--")
|
|
383
|
+
return [argv, []] unless dash_index
|
|
384
|
+
|
|
385
|
+
[argv[0...dash_index], argv[(dash_index + 1)..]]
|
|
302
386
|
end
|
|
303
387
|
|
|
304
|
-
def
|
|
305
|
-
dash_index =
|
|
306
|
-
argv
|
|
388
|
+
def extract_file_paths(argv, rspec_argv)
|
|
389
|
+
dash_index = rspec_argv.index("--")
|
|
390
|
+
return [argv, rspec_argv] unless dash_index
|
|
391
|
+
|
|
392
|
+
[argv + rspec_argv[(dash_index + 1)..], rspec_argv[0...dash_index]]
|
|
307
393
|
end
|
|
308
394
|
|
|
309
395
|
def append_test_options(options, argv)
|
|
310
|
-
new_opts =
|
|
396
|
+
new_opts = argv
|
|
311
397
|
return if new_opts.empty?
|
|
312
398
|
|
|
313
399
|
options[:test_options] ||= []
|
|
@@ -316,18 +402,18 @@ module ParallelSpecs
|
|
|
316
402
|
|
|
317
403
|
def report_time_taken(&block)
|
|
318
404
|
seconds = ParallelSpecs.delta(&block).to_i
|
|
319
|
-
puts "\nTook #{pluralize(seconds,
|
|
405
|
+
puts "\nTook #{pluralize(seconds, "second")}#{detailed_duration(seconds)}"
|
|
320
406
|
end
|
|
321
407
|
|
|
322
408
|
def detailed_duration(seconds)
|
|
323
409
|
parts = [seconds / 3600, (seconds % 3600) / 60, seconds % 60].drop_while(&:zero?)
|
|
324
410
|
return if parts.size < 2
|
|
325
411
|
|
|
326
|
-
" (#{parts.map { |part| format(
|
|
412
|
+
" (#{parts.map { |part| format("%02d", part) }.join(":").sub(/^0/, "")})"
|
|
327
413
|
end
|
|
328
414
|
|
|
329
415
|
def final_fail_message
|
|
330
|
-
message =
|
|
416
|
+
message = "Specs Failed"
|
|
331
417
|
use_colors? ? "\e[31m#{message}\e[0m" : message
|
|
332
418
|
end
|
|
333
419
|
|
|
@@ -339,11 +425,13 @@ module ParallelSpecs
|
|
|
339
425
|
options[:dashboard_runner]&.plain?
|
|
340
426
|
end
|
|
341
427
|
|
|
342
|
-
def dashboard_mode
|
|
343
|
-
|
|
428
|
+
def dashboard_mode(options = {})
|
|
429
|
+
return options[:dashboard_mode] if options[:dashboard_mode]
|
|
430
|
+
|
|
431
|
+
override = ENV["PARALLEL_SPECS_DASHBOARD_MODE"]
|
|
344
432
|
return override.to_sym if %w[interactive plain].include?(override)
|
|
345
433
|
|
|
346
|
-
if ENV[
|
|
434
|
+
if ENV["CI"] || !$stdout.tty?
|
|
347
435
|
:plain
|
|
348
436
|
else
|
|
349
437
|
:interactive
|
|
@@ -354,10 +442,10 @@ module ParallelSpecs
|
|
|
354
442
|
return yield unless simulate
|
|
355
443
|
|
|
356
444
|
progress_indicator = Thread.new do
|
|
357
|
-
interval = Float(ENV[
|
|
445
|
+
interval = Float(ENV["PARALLEL_SPECS_HEARTBEAT_INTERVAL"] || 60)
|
|
358
446
|
loop do
|
|
359
447
|
sleep interval
|
|
360
|
-
$stdout.print
|
|
448
|
+
$stdout.print "."
|
|
361
449
|
$stdout.flush
|
|
362
450
|
end
|
|
363
451
|
end
|
|
@@ -368,12 +456,12 @@ module ParallelSpecs
|
|
|
368
456
|
end
|
|
369
457
|
|
|
370
458
|
def heredoc(text, newline_padding)
|
|
371
|
-
text.rstrip.gsub("\n", "\n#{
|
|
459
|
+
text.rstrip.gsub("\n", "\n#{" " * newline_padding}")
|
|
372
460
|
end
|
|
373
461
|
|
|
374
462
|
def pluralize(number, singular)
|
|
375
463
|
return "1 #{singular}" if number == 1
|
|
376
|
-
return "#{number} #{singular}es" if singular.end_with?(
|
|
464
|
+
return "#{number} #{singular}es" if singular.end_with?("s", "sh", "ch", "x", "z")
|
|
377
465
|
|
|
378
466
|
"#{number} #{singular}s"
|
|
379
467
|
end
|
|
@@ -3,13 +3,27 @@
|
|
|
3
3
|
module ParallelSpecs
|
|
4
4
|
class Grouper
|
|
5
5
|
class << self
|
|
6
|
-
def in_even_groups_by_size(items, num_groups,
|
|
7
|
-
groups = Array.new(num_groups) { {
|
|
6
|
+
def in_even_groups_by_size(items, num_groups, options = {})
|
|
7
|
+
groups = Array.new(num_groups) { {items: [], size: 0} }
|
|
8
|
+
single_process_patterns = options[:single_process] || []
|
|
9
|
+
single_items, items = items.partition do |item, _size|
|
|
10
|
+
single_process_patterns.any? { |pattern| item.match?(pattern) }
|
|
11
|
+
end
|
|
12
|
+
isolate_count = isolated_process_count(options)
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
if options.key?(:isolate_count) && !isolate_count.positive?
|
|
15
|
+
raise ParallelSpecs::ConfigurationError, "Isolated process count must be greater than 0"
|
|
16
|
+
end
|
|
17
|
+
if isolate_count >= num_groups
|
|
18
|
+
raise ParallelSpecs::ConfigurationError, "Isolated process count must be less than total process count"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if isolate_count.positive?
|
|
22
|
+
group_by_size(single_items, groups.first(isolate_count))
|
|
23
|
+
group_by_size(items, groups.drop(isolate_count))
|
|
24
|
+
else
|
|
25
|
+
single_items.each { |item, size| add_to_group(groups.first, item, size) }
|
|
26
|
+
group_by_size(items, groups)
|
|
13
27
|
end
|
|
14
28
|
|
|
15
29
|
groups.map { |group| group[:items].sort }
|
|
@@ -17,6 +31,21 @@ module ParallelSpecs
|
|
|
17
31
|
|
|
18
32
|
private
|
|
19
33
|
|
|
34
|
+
def isolated_process_count(options)
|
|
35
|
+
options[:isolate_count] || (options[:isolate] ? 1 : 0)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def group_by_size(items, groups)
|
|
39
|
+
items_to_group(items).each do |item, size|
|
|
40
|
+
add_to_group(groups.min_by { |group| group[:size] }, item, size)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def add_to_group(group, item, size)
|
|
45
|
+
group[:items] << item
|
|
46
|
+
group[:size] += size || 1
|
|
47
|
+
end
|
|
48
|
+
|
|
20
49
|
def items_to_group(items)
|
|
21
50
|
return items unless items.first&.size == 2
|
|
22
51
|
|
data/lib/parallel_specs/pids.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require "rspec/core"
|
|
6
|
+
require "rspec/core/formatters/base_text_formatter"
|
|
7
7
|
|
|
8
8
|
module ParallelSpecs
|
|
9
9
|
module RSpec
|
|
@@ -23,32 +23,32 @@ class ParallelSpecs::RSpec::DashboardLogger < RSpec::Core::Formatters::BaseTextF
|
|
|
23
23
|
def initialize(output)
|
|
24
24
|
super
|
|
25
25
|
|
|
26
|
-
path = ENV[
|
|
27
|
-
raise
|
|
26
|
+
path = ENV["PARALLEL_SPECS_DASHBOARD_EVENT_LOG"]
|
|
27
|
+
raise "A dashboard event log env var is required for DashboardLogger" if path.to_s.empty?
|
|
28
28
|
|
|
29
29
|
FileUtils.mkdir_p(File.dirname(path))
|
|
30
|
-
@event_output = File.open(path,
|
|
30
|
+
@event_output = File.open(path, "a")
|
|
31
31
|
@event_output.sync = true
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def start(notification)
|
|
35
|
-
emit(event:
|
|
35
|
+
emit(event: "start", total: notification.count)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def example_started(notification)
|
|
39
|
-
emit_example(
|
|
39
|
+
emit_example("example_started", notification)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def example_passed(notification)
|
|
43
|
-
emit_example(
|
|
43
|
+
emit_example("example_passed", notification)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def example_pending(notification)
|
|
47
|
-
emit_example(
|
|
47
|
+
emit_example("example_pending", notification)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def example_failed(notification)
|
|
51
|
-
emit_example(
|
|
51
|
+
emit_example("example_failed", notification)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def close(*)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "rspec/core"
|
|
5
|
+
require "rspec/core/formatters/base_text_formatter"
|
|
6
6
|
|
|
7
7
|
module ParallelSpecs
|
|
8
8
|
module RSpec
|
|
@@ -17,11 +17,11 @@ class ParallelSpecs::RSpec::LoggerBase < RSpec::Core::Formatters::BaseTextFormat
|
|
|
17
17
|
case @output
|
|
18
18
|
when String
|
|
19
19
|
FileUtils.mkdir_p(File.dirname(@output))
|
|
20
|
-
File.open(@output,
|
|
21
|
-
@output = File.open(@output,
|
|
20
|
+
File.open(@output, "w") {}
|
|
21
|
+
@output = File.open(@output, "a")
|
|
22
22
|
when File
|
|
23
23
|
@output.close
|
|
24
|
-
@output = File.open(@output.path,
|
|
24
|
+
@output = File.open(@output.path, "a")
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "parallel_specs/test/runner"
|
|
4
4
|
|
|
5
5
|
module ParallelSpecs
|
|
6
6
|
module RSpec
|
|
@@ -16,15 +16,15 @@ module ParallelSpecs
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def runtime_log
|
|
19
|
-
|
|
19
|
+
"tmp/parallel_runtime_rspec.log"
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def default_test_folder
|
|
23
|
-
|
|
23
|
+
"spec"
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def test_file_name
|
|
27
|
-
|
|
27
|
+
"spec"
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def line_is_result?(line)
|
|
@@ -36,9 +36,9 @@ module ParallelSpecs
|
|
|
36
36
|
return text unless $stdout.tty?
|
|
37
37
|
|
|
38
38
|
sums = send(:sum_up_results, results)
|
|
39
|
-
color_code = if sums[
|
|
39
|
+
color_code = if sums["failure"] > 0
|
|
40
40
|
31
|
|
41
|
-
elsif sums[
|
|
41
|
+
elsif sums["pending"] > 0
|
|
42
42
|
33
|
|
43
43
|
else
|
|
44
44
|
32
|
|
@@ -52,7 +52,7 @@ module ParallelSpecs
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def command_with_seed(command, seed)
|
|
55
|
-
[*remove_command_arguments(command,
|
|
55
|
+
[*remove_command_arguments(command, "--seed", "--order"), "--seed", seed]
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
private
|
|
@@ -69,17 +69,17 @@ module ParallelSpecs
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def remove_rerun_only_formatters(command)
|
|
72
|
-
remove_formatter(command,
|
|
73
|
-
.then { |cmd| remove_formatter(cmd,
|
|
72
|
+
remove_formatter(command, "ParallelSpecs::RSpec::DashboardLogger")
|
|
73
|
+
.then { |cmd| remove_formatter(cmd, "ParallelSpecs::RSpec::RuntimeLogger") }
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def remove_formatter(command, formatter)
|
|
77
77
|
cleaned = []
|
|
78
78
|
index = 0
|
|
79
79
|
while index < command.length
|
|
80
|
-
if command[index] ==
|
|
80
|
+
if command[index] == "--format" && command[index + 1] == formatter
|
|
81
81
|
index += 2
|
|
82
|
-
elsif command[index] ==
|
|
82
|
+
elsif command[index] == "--out" && command[index - 2] == "--format" && command[index - 1] == formatter
|
|
83
83
|
index += 2
|
|
84
84
|
else
|
|
85
85
|
cleaned << command[index]
|
|
@@ -90,12 +90,12 @@ module ParallelSpecs
|
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
def executable
|
|
93
|
-
if File.exist?(
|
|
94
|
-
ParallelSpecs.with_ruby_binary(
|
|
93
|
+
if File.exist?("bin/rspec")
|
|
94
|
+
ParallelSpecs.with_ruby_binary("bin/rspec")
|
|
95
95
|
elsif ParallelSpecs.bundler_enabled?
|
|
96
96
|
%w[bundle exec rspec]
|
|
97
97
|
else
|
|
98
|
-
[
|
|
98
|
+
["rspec"]
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
|
|
@@ -104,7 +104,7 @@ module ParallelSpecs
|
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
def dashboard_formatter(options)
|
|
107
|
-
[
|
|
107
|
+
["--format", "ParallelSpecs::RSpec::DashboardLogger"] if options[:dashboard]
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
def record_runtime_formatters(process_number, options)
|
|
@@ -114,7 +114,7 @@ module ParallelSpecs
|
|
|
114
114
|
options[:runtime_log] || runtime_log
|
|
115
115
|
end
|
|
116
116
|
|
|
117
|
-
[
|
|
117
|
+
["--format", "ParallelSpecs::RSpec::RuntimeLogger", "--out", runtime_log_path]
|
|
118
118
|
end
|
|
119
119
|
end
|
|
120
120
|
end
|