parallel_specs 0.9.0 → 0.9.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 +89 -3
- data/lib/parallel_specs/cli/dashboard.rb +92 -112
- data/lib/parallel_specs/cli.rb +140 -57
- data/lib/parallel_specs/grouper.rb +2 -2
- 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 +31 -25
- 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,60 @@ 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
|
-
opts.on(
|
|
280
|
-
opts.on(
|
|
281
|
-
opts.on(
|
|
282
|
-
opts.on(
|
|
283
|
-
opts.on(
|
|
284
|
-
opts.on(
|
|
285
|
-
opts.on(
|
|
286
|
-
opts.on(
|
|
287
|
-
|
|
347
|
+
opts.on("--dashboard-mode MODE", %w[interactive plain], "Dashboard mode: interactive or plain") { |mode| options[:dashboard_mode] = mode.to_sym }
|
|
348
|
+
opts.on("--plain-dashboard", "Use the plain text dashboard output") { options[:dashboard_mode] = :plain }
|
|
349
|
+
opts.on("--plain", "Use the plain text dashboard output") { options[:dashboard_mode] = :plain }
|
|
350
|
+
opts.on("--pattern PATTERN", "Only run spec files matching PATTERN") { |pattern| options[:pattern] = Regexp.new(pattern) }
|
|
351
|
+
opts.on("--exclude-pattern PATTERN", "Skip spec files matching PATTERN") { |pattern| options[:exclude_pattern] = Regexp.new(pattern) }
|
|
352
|
+
opts.on("--runtime-log PATH", "Read spec runtimes from PATH; with --record-runtime, write the completed run there") { |path| options[:runtime_log] = path }
|
|
353
|
+
opts.on("--allowed-missing COUNT", Integer, "Allowed percentage of missing runtimes (default = 50)") { |percent| options[:allowed_missing_percent] = percent }
|
|
354
|
+
opts.on("--unknown-runtime SECONDS", Float, "Use given number as unknown runtime (otherwise use average time)") { |time| options[:unknown_runtime] = time }
|
|
355
|
+
opts.on("--record-runtime", "Record runtimes and replace the runtime log only after a successful complete run") { options[:record_runtime] = true }
|
|
356
|
+
opts.on("--fail-fast", "Stop remaining workers after one worker fails") { options[:fail_fast] = true }
|
|
357
|
+
opts.on("-v", "--version", "Show version") {
|
|
358
|
+
puts ParallelSpecs::VERSION
|
|
359
|
+
exit 0
|
|
360
|
+
}
|
|
361
|
+
opts.on("-h", "--help", "Show this help") {
|
|
362
|
+
puts opts
|
|
363
|
+
exit 0
|
|
364
|
+
}
|
|
365
|
+
end.parse!(cli_argv)
|
|
288
366
|
|
|
289
367
|
options[:dashboard] = !options[:record_runtime]
|
|
290
368
|
|
|
291
|
-
files, remaining = extract_file_paths(
|
|
369
|
+
files, remaining = extract_file_paths(cli_argv, rspec_argv)
|
|
292
370
|
files = [@runner.default_test_folder] if files.empty?
|
|
293
371
|
options[:files] = files.map { |file_path| Pathname.new(file_path).cleanpath.to_s }
|
|
294
372
|
append_test_options(options, remaining)
|
|
295
373
|
options
|
|
296
374
|
end
|
|
297
375
|
|
|
298
|
-
def
|
|
299
|
-
dash_index = argv.
|
|
300
|
-
|
|
301
|
-
|
|
376
|
+
def split_rspec_args(argv)
|
|
377
|
+
dash_index = argv.index("--")
|
|
378
|
+
return [argv, []] unless dash_index
|
|
379
|
+
|
|
380
|
+
[argv[0...dash_index], argv[(dash_index + 1)..]]
|
|
302
381
|
end
|
|
303
382
|
|
|
304
|
-
def
|
|
305
|
-
dash_index =
|
|
306
|
-
argv
|
|
383
|
+
def extract_file_paths(argv, rspec_argv)
|
|
384
|
+
dash_index = rspec_argv.index("--")
|
|
385
|
+
return [argv, rspec_argv] unless dash_index
|
|
386
|
+
|
|
387
|
+
[argv + rspec_argv[(dash_index + 1)..], rspec_argv[0...dash_index]]
|
|
307
388
|
end
|
|
308
389
|
|
|
309
390
|
def append_test_options(options, argv)
|
|
310
|
-
new_opts =
|
|
391
|
+
new_opts = argv
|
|
311
392
|
return if new_opts.empty?
|
|
312
393
|
|
|
313
394
|
options[:test_options] ||= []
|
|
@@ -316,18 +397,18 @@ module ParallelSpecs
|
|
|
316
397
|
|
|
317
398
|
def report_time_taken(&block)
|
|
318
399
|
seconds = ParallelSpecs.delta(&block).to_i
|
|
319
|
-
puts "\nTook #{pluralize(seconds,
|
|
400
|
+
puts "\nTook #{pluralize(seconds, "second")}#{detailed_duration(seconds)}"
|
|
320
401
|
end
|
|
321
402
|
|
|
322
403
|
def detailed_duration(seconds)
|
|
323
404
|
parts = [seconds / 3600, (seconds % 3600) / 60, seconds % 60].drop_while(&:zero?)
|
|
324
405
|
return if parts.size < 2
|
|
325
406
|
|
|
326
|
-
" (#{parts.map { |part| format(
|
|
407
|
+
" (#{parts.map { |part| format("%02d", part) }.join(":").sub(/^0/, "")})"
|
|
327
408
|
end
|
|
328
409
|
|
|
329
410
|
def final_fail_message
|
|
330
|
-
message =
|
|
411
|
+
message = "Specs Failed"
|
|
331
412
|
use_colors? ? "\e[31m#{message}\e[0m" : message
|
|
332
413
|
end
|
|
333
414
|
|
|
@@ -339,11 +420,13 @@ module ParallelSpecs
|
|
|
339
420
|
options[:dashboard_runner]&.plain?
|
|
340
421
|
end
|
|
341
422
|
|
|
342
|
-
def dashboard_mode
|
|
343
|
-
|
|
423
|
+
def dashboard_mode(options = {})
|
|
424
|
+
return options[:dashboard_mode] if options[:dashboard_mode]
|
|
425
|
+
|
|
426
|
+
override = ENV["PARALLEL_SPECS_DASHBOARD_MODE"]
|
|
344
427
|
return override.to_sym if %w[interactive plain].include?(override)
|
|
345
428
|
|
|
346
|
-
if ENV[
|
|
429
|
+
if ENV["CI"] || !$stdout.tty?
|
|
347
430
|
:plain
|
|
348
431
|
else
|
|
349
432
|
:interactive
|
|
@@ -354,10 +437,10 @@ module ParallelSpecs
|
|
|
354
437
|
return yield unless simulate
|
|
355
438
|
|
|
356
439
|
progress_indicator = Thread.new do
|
|
357
|
-
interval = Float(ENV[
|
|
440
|
+
interval = Float(ENV["PARALLEL_SPECS_HEARTBEAT_INTERVAL"] || 60)
|
|
358
441
|
loop do
|
|
359
442
|
sleep interval
|
|
360
|
-
$stdout.print
|
|
443
|
+
$stdout.print "."
|
|
361
444
|
$stdout.flush
|
|
362
445
|
end
|
|
363
446
|
end
|
|
@@ -368,12 +451,12 @@ module ParallelSpecs
|
|
|
368
451
|
end
|
|
369
452
|
|
|
370
453
|
def heredoc(text, newline_padding)
|
|
371
|
-
text.rstrip.gsub("\n", "\n#{
|
|
454
|
+
text.rstrip.gsub("\n", "\n#{" " * newline_padding}")
|
|
372
455
|
end
|
|
373
456
|
|
|
374
457
|
def pluralize(number, singular)
|
|
375
458
|
return "1 #{singular}" if number == 1
|
|
376
|
-
return "#{number} #{singular}es" if singular.end_with?(
|
|
459
|
+
return "#{number} #{singular}es" if singular.end_with?("s", "sh", "ch", "x", "z")
|
|
377
460
|
|
|
378
461
|
"#{number} #{singular}s"
|
|
379
462
|
end
|
|
@@ -4,12 +4,12 @@ module ParallelSpecs
|
|
|
4
4
|
class Grouper
|
|
5
5
|
class << self
|
|
6
6
|
def in_even_groups_by_size(items, num_groups, _options = {})
|
|
7
|
-
groups = Array.new(num_groups) { {
|
|
7
|
+
groups = Array.new(num_groups) { {items: [], size: 0} }
|
|
8
8
|
|
|
9
9
|
items_to_group(items).each do |item, size|
|
|
10
10
|
group = groups.min_by { |entry| entry[:size] }
|
|
11
11
|
group[:items] << item
|
|
12
|
-
group[:size] +=
|
|
12
|
+
group[:size] += size || 1
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
groups.map { |group| group[:items].sort }
|
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", "progress", "--format", "ParallelSpecs::RSpec::RuntimeLogger", "--out", runtime_log_path]
|
|
118
118
|
end
|
|
119
119
|
end
|
|
120
120
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "parallel_specs"
|
|
4
|
+
require "parallel_specs/rspec/logger_base"
|
|
5
5
|
|
|
6
6
|
class ParallelSpecs::RSpec::RuntimeLogger < ParallelSpecs::RSpec::LoggerBase
|
|
7
7
|
RSpec::Core::Formatters.register(self, :example_group_started, :example_group_finished, :start_dump)
|
|
@@ -26,18 +26,27 @@ class ParallelSpecs::RSpec::RuntimeLogger < ParallelSpecs::RSpec::LoggerBase
|
|
|
26
26
|
super if defined?(super)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def seed(*)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def
|
|
33
|
-
|
|
29
|
+
def seed(*)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def dump_summary(*)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def dump_failures(*)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def dump_failure(*)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def dump_pending(*)
|
|
42
|
+
end
|
|
34
43
|
|
|
35
44
|
def start_dump(*)
|
|
36
|
-
return unless ENV[
|
|
45
|
+
return unless ENV["TEST_ENV_NUMBER"]
|
|
37
46
|
|
|
38
47
|
lock_output do
|
|
39
48
|
@example_times.sort_by(&:last).reverse_each do |file, time|
|
|
40
|
-
relative_path = file.sub(%r{^#{Regexp.escape(Dir.pwd)}/},
|
|
49
|
+
relative_path = file.sub(%r{^#{Regexp.escape(Dir.pwd)}/}, "").sub(%r{^\./}, "")
|
|
41
50
|
@output.puts "#{relative_path}:#{[time, 0].max}"
|
|
42
51
|
end
|
|
43
52
|
end
|