rspec-conductor 1.0.8 → 1.0.10
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/CHANGELOG.md +13 -0
- data/lib/rspec/conductor/cli.rb +6 -0
- data/lib/rspec/conductor/ext/rspec.rb +2 -0
- data/lib/rspec/conductor/formatters/base.rb +70 -46
- data/lib/rspec/conductor/formatters/ci.rb +8 -8
- data/lib/rspec/conductor/formatters/fancy.rb +22 -9
- data/lib/rspec/conductor/formatters/plain.rb +3 -1
- data/lib/rspec/conductor/rspec_subscriber.rb +2 -0
- data/lib/rspec/conductor/server.rb +98 -109
- data/lib/rspec/conductor/{results.rb → suite_run.rb} +15 -19
- data/lib/rspec/conductor/util/child_process.rb +40 -29
- data/lib/rspec/conductor/util/terminal.rb +1 -3
- data/lib/rspec/conductor/version.rb +1 -1
- data/lib/rspec/conductor/worker.rb +9 -17
- data/lib/rspec/conductor/worker_process.rb +66 -1
- data/lib/rspec/conductor.rb +5 -1
- data/lib/tasks/rspec_conductor.rake +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9de36557f5b670ac37493d920d186e4dcbcaca0c958d59914448b91aa5e6e488
|
|
4
|
+
data.tar.gz: 9d2371b5598a12adbc6ac907d80dfc4fbbe5f9c7d1c2b0f75cb17337345f51e4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cb67874bb0e5f84176ac601588d7931c97d70e3f8f6caff111e7cc1607b15007fcbc17f0053ff2c92f8e438cdad343530c91667de4d99f4fc7b95ac504f868fd
|
|
7
|
+
data.tar.gz: 4cb10a52cf0a0a6f9948c36287f2b6c5d35183df0ce6370196f9342e67c29ce5332780d8da8172d585be264170958fcbb317d77f03f3c23cb4ff70394b6e4cbe
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## [1.0.10] - 2026-03-08
|
|
2
|
+
|
|
3
|
+
- Add --print-slowest cli param to display the slowest specs in the suite
|
|
4
|
+
|
|
5
|
+
## [1.0.9] - 2026-03-01
|
|
6
|
+
|
|
7
|
+
- Handle workers stdout/stderr better. It is no longer necessary to use --verbose to see worker output. Verbose now only controls whether you see the debug output of the workers
|
|
8
|
+
- For the fancy formatter, allocate one line per stdout/stderr. Not ideal, but I'm not sure what layout I'm even looking for here, since allowing to freely put stuff into stdout/stderr breaks the TUI completely
|
|
9
|
+
- Disable echo when running (also breaks the TUI). The side effect of this is that you probably lose ability to tactically use binding.irb in your specs, but you might want to drop into the regular rspec to do that anyway
|
|
10
|
+
- Way better handling of SIGINT / Ctrl-C. Child processes used to just crash when terminated via signal, now they're safely terminating
|
|
11
|
+
- Support double Ctrl-C to force-kill the workers (same as rspec)
|
|
12
|
+
- When there are spec failures, include a rerun command for failed examples (e.g. `rspec spec/some_spec.rb:28 spec/other_spec.rb:42`). It should also be possible to use rspec-conductor for those, but in my personal practice, I prefer rspec because I also want to have some interactive console during the spec run, which is not going to be possible with forked children
|
|
13
|
+
|
|
1
14
|
## [1.0.8] - 2026-02-18
|
|
2
15
|
|
|
3
16
|
- When --postfork-require is provided, use current dir instead of spec/
|
data/lib/rspec/conductor/cli.rb
CHANGED
|
@@ -15,6 +15,7 @@ module RSpec
|
|
|
15
15
|
display_retry_backtraces: false,
|
|
16
16
|
prefork_require: 'config/application.rb',
|
|
17
17
|
postfork_require: :spec_helper,
|
|
18
|
+
print_slowest_count: nil,
|
|
18
19
|
}.freeze
|
|
19
20
|
|
|
20
21
|
def self.run(argv)
|
|
@@ -97,6 +98,10 @@ module RSpec
|
|
|
97
98
|
@conductor_options[:display_retry_backtraces] = true
|
|
98
99
|
end
|
|
99
100
|
|
|
101
|
+
opts.on("--print-slowest COUNT", Integer, "Print slowest specs with their execution times") do |n|
|
|
102
|
+
@conductor_options[:print_slowest_count] = n
|
|
103
|
+
end
|
|
104
|
+
|
|
100
105
|
opts.on("--verbose", "Enable debug output") do
|
|
101
106
|
@conductor_options[:verbose] = true
|
|
102
107
|
end
|
|
@@ -120,6 +125,7 @@ module RSpec
|
|
|
120
125
|
rspec_args: @rspec_args,
|
|
121
126
|
formatter: @conductor_options[:formatter],
|
|
122
127
|
display_retry_backtraces: @conductor_options[:display_retry_backtraces],
|
|
128
|
+
print_slowest_count: @conductor_options[:print_slowest_count],
|
|
123
129
|
verbose: @conductor_options[:verbose],
|
|
124
130
|
).run
|
|
125
131
|
end
|
|
@@ -2,65 +2,89 @@
|
|
|
2
2
|
|
|
3
3
|
module RSpec
|
|
4
4
|
module Conductor
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
module Formatters
|
|
6
|
+
class Base
|
|
7
|
+
include Util::ANSI
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
def initialize(**_kwargs)
|
|
10
|
+
end
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
def handle_worker_message(_worker_process, _message, _suite_run)
|
|
13
|
+
end
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
def print_startup_banner(worker_count:, seed:, spec_files_count:)
|
|
16
|
+
puts "RSpec Conductor starting with #{worker_count} workers (seed: #{seed})"
|
|
17
|
+
puts "Running #{spec_files_count} spec files\n\n"
|
|
18
|
+
end
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
def print_summary(suite_run, seed:, success:)
|
|
21
|
+
puts "\n\n"
|
|
22
|
+
puts "Randomized with seed #{seed}"
|
|
23
|
+
puts "#{colorize("#{suite_run.examples_passed} passed", :green)}, #{colorize("#{suite_run.examples_failed} failed", :red)}, #{colorize("#{suite_run.examples_pending} pending", :yellow)}"
|
|
24
|
+
puts colorize("Worker crashes: #{suite_run.worker_crashes}", :red) if suite_run.worker_crashes.positive?
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
if suite_run.errors.any?
|
|
27
|
+
puts "\nFailures:\n\n"
|
|
28
|
+
suite_run.errors.each_with_index do |error, i|
|
|
29
|
+
puts " #{i + 1}) #{error[:description]}"
|
|
30
|
+
puts colorize(" #{error[:message]}", :red) if error[:message]
|
|
31
|
+
puts colorize(" #{error[:location]}", :cyan)
|
|
32
|
+
if error[:backtrace]&.any?
|
|
33
|
+
puts " Backtrace:"
|
|
34
|
+
error[:backtrace].each { |line| puts " #{line}" }
|
|
35
|
+
end
|
|
36
|
+
puts
|
|
34
37
|
end
|
|
35
|
-
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
puts "Specs took: #{suite_run.specs_runtime.round(2)}s"
|
|
41
|
+
puts "Total runtime: #{suite_run.total_runtime.round(2)}s"
|
|
42
|
+
puts "Suite: #{success ? colorize("PASSED", :green) : colorize("FAILED", :red)}"
|
|
43
|
+
|
|
44
|
+
if suite_run.errors.any?
|
|
45
|
+
puts ""
|
|
46
|
+
puts "To rerun failed examples:"
|
|
47
|
+
puts " rspec #{suite_run.errors.map { |e| e[:location] }.join(" ")}"
|
|
36
48
|
end
|
|
37
49
|
end
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
def print_slowest(suite_run, n)
|
|
52
|
+
puts "\n\n"
|
|
53
|
+
puts "Slowest #{n} specs:"
|
|
54
|
+
suite_run.example_stats.sort_by { |e| -e[:run_time] }.take(n).each_with_index do |e, i|
|
|
55
|
+
puts "%3d. (%8.2fms) %s @ %s" % [i + 1, e[:run_time] * 1000, colorize(e[:description], :dim), colorize(e[:location], :cyan)]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
43
58
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
def handle_worker_stdout(worker_number, string)
|
|
60
|
+
puts "[worker #{worker_number}] #{string}"
|
|
61
|
+
end
|
|
47
62
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
#{message[:location]}
|
|
52
|
-
#{message[:exception_class]}: #{message[:message]}
|
|
53
|
-
Backtrace:
|
|
54
|
-
#{message[:backtrace].map { " #{_1}" }.join("\n")}
|
|
55
|
-
EOM
|
|
56
|
-
end
|
|
63
|
+
def handle_worker_stderr(worker_number, string)
|
|
64
|
+
$stderr.puts "[worker #{worker_number}] #{string}"
|
|
65
|
+
end
|
|
57
66
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
def print_debug(string)
|
|
68
|
+
$stderr.puts string
|
|
69
|
+
end
|
|
61
70
|
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
def print_retry_message(message)
|
|
72
|
+
puts <<~EOM
|
|
73
|
+
\nRetried: #{message[:description]}
|
|
74
|
+
#{message[:location]}
|
|
75
|
+
#{message[:exception_class]}: #{message[:message]}
|
|
76
|
+
Backtrace:
|
|
77
|
+
#{message[:backtrace].map { " #{_1}" }.join("\n")}
|
|
78
|
+
EOM
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def print_shutdown_banner
|
|
82
|
+
puts "Shutting down... (press ctrl-c again to force quit)"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def colorize(string, colors, **kwargs)
|
|
86
|
+
$stdout.tty? ? super(string, colors, **kwargs) : string
|
|
87
|
+
end
|
|
64
88
|
end
|
|
65
89
|
end
|
|
66
90
|
end
|
|
@@ -14,22 +14,22 @@ module RSpec
|
|
|
14
14
|
super(**kwargs)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def handle_worker_message(_worker_process, message,
|
|
17
|
+
def handle_worker_message(_worker_process, message, suite_run)
|
|
18
18
|
public_send(message[:type], message) if respond_to?(message[:type])
|
|
19
|
-
print_status(
|
|
19
|
+
print_status(suite_run) if @last_printout + @printout_interval < Time.now
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def print_status(
|
|
22
|
+
def print_status(suite_run)
|
|
23
23
|
@last_printout = Time.now
|
|
24
|
-
pct =
|
|
24
|
+
pct = suite_run.spec_file_processed_percentage
|
|
25
25
|
|
|
26
26
|
puts "-" * tty_width
|
|
27
27
|
puts "Current status [#{Time.now.strftime("%H:%M:%S")}]:"
|
|
28
|
-
puts "Processed: #{
|
|
29
|
-
puts "#{
|
|
30
|
-
if
|
|
28
|
+
puts "Processed: #{suite_run.spec_files_processed} / #{suite_run.spec_files_total} (#{(pct * 100).floor}%)"
|
|
29
|
+
puts "#{suite_run.examples_passed} passed, #{suite_run.examples_failed} failed, #{suite_run.examples_pending} pending"
|
|
30
|
+
if suite_run.errors.any?
|
|
31
31
|
puts "Failures:\n"
|
|
32
|
-
|
|
32
|
+
suite_run.errors.each_with_index do |error, i|
|
|
33
33
|
puts " #{i + 1}) #{error[:description]}"
|
|
34
34
|
puts " #{error[:location]}"
|
|
35
35
|
puts " #{error[:message]}" if error[:message]
|
|
@@ -11,9 +11,7 @@ module RSpec
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def initialize(worker_count:, **kwargs)
|
|
14
|
-
@worker_processes = {}
|
|
15
14
|
@terminal = Util::Terminal.new
|
|
16
|
-
@last_rendered_lines = []
|
|
17
15
|
@dots_string = +""
|
|
18
16
|
@last_error = nil
|
|
19
17
|
|
|
@@ -25,13 +23,16 @@ module RSpec
|
|
|
25
23
|
@dots_line = @terminal.line(truncate: false)
|
|
26
24
|
@terminal.puts
|
|
27
25
|
@last_error_line = @terminal.line(truncate: false)
|
|
26
|
+
@stdout_line = @terminal.puts nil
|
|
27
|
+
@stderr_line = @terminal.puts nil
|
|
28
|
+
@shutdown_line = @terminal.puts nil
|
|
28
29
|
|
|
29
30
|
super(**kwargs)
|
|
30
31
|
end
|
|
31
32
|
|
|
32
|
-
def handle_worker_message(worker_process, message,
|
|
33
|
+
def handle_worker_message(worker_process, message, suite_run)
|
|
33
34
|
public_send(message[:type], worker_process, message) if respond_to?(message[:type])
|
|
34
|
-
redraw(worker_process,
|
|
35
|
+
redraw(worker_process, suite_run)
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def example_passed(_worker_process, _message)
|
|
@@ -51,11 +52,23 @@ module RSpec
|
|
|
51
52
|
dot "*", :yellow
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
def handle_worker_stdout(worker_number, string)
|
|
56
|
+
@stdout_line.update("STDOUT: [worker #{worker_number}]: #{string}")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def handle_worker_stderr(worker_number, string)
|
|
60
|
+
@stderr_line.update("STDERR: [worker #{worker_number}]: #{string}")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def print_shutdown_banner
|
|
64
|
+
@shutdown_line.update("Shutting down... (press ctrl-c again to force quit)")
|
|
65
|
+
end
|
|
66
|
+
|
|
54
67
|
private
|
|
55
68
|
|
|
56
|
-
def redraw(worker_process,
|
|
69
|
+
def redraw(worker_process, suite_run)
|
|
57
70
|
update_worker_status_line(worker_process)
|
|
58
|
-
|
|
71
|
+
update_suite_run_line(suite_run)
|
|
59
72
|
update_errors_line
|
|
60
73
|
@terminal.redraw
|
|
61
74
|
@terminal.scroll_to_bottom
|
|
@@ -81,13 +94,13 @@ module RSpec
|
|
|
81
94
|
@worker_lines[worker_process.number].update(status, redraw: false)
|
|
82
95
|
end
|
|
83
96
|
|
|
84
|
-
def
|
|
85
|
-
pct =
|
|
97
|
+
def update_suite_run_line(suite_run)
|
|
98
|
+
pct = suite_run.spec_file_processed_percentage
|
|
86
99
|
bar_width = [tty_width - 20, 20].max
|
|
87
100
|
filled = (pct * bar_width).floor
|
|
88
101
|
empty = bar_width - filled
|
|
89
102
|
|
|
90
|
-
percentage = " %3d%% (%d/%d)" % [(pct * 100).floor,
|
|
103
|
+
percentage = " %3d%% (%d/%d)" % [(pct * 100).floor, suite_run.spec_files_processed, suite_run.spec_files_total]
|
|
91
104
|
bar = colorize("[", :reset) + colorize("▓", :green) * filled + colorize(" ", :reset) * empty + colorize("]", :reset)
|
|
92
105
|
|
|
93
106
|
@progress_bar_line.update(bar + percentage, redraw: false)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module RSpec
|
|
2
4
|
module Conductor
|
|
3
5
|
module Formatters
|
|
4
6
|
class Plain < Base
|
|
5
|
-
def handle_worker_message(_worker_process, message,
|
|
7
|
+
def handle_worker_message(_worker_process, message, _suite_run)
|
|
6
8
|
public_send(message[:type], message) if respond_to?(message[:type])
|
|
7
9
|
end
|
|
8
10
|
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "English"
|
|
4
|
-
require "socket"
|
|
5
|
-
require "json"
|
|
6
|
-
|
|
7
3
|
module RSpec
|
|
8
4
|
module Conductor
|
|
9
5
|
class Server
|
|
@@ -21,6 +17,7 @@ module RSpec
|
|
|
21
17
|
# @option formatter [String] Use a certain formatter
|
|
22
18
|
# @option verbose [Boolean] Use especially verbose output
|
|
23
19
|
# @option display_retry_backtraces [Boolean] Display backtraces for specs retried via rspec-retry
|
|
20
|
+
# @option print_slowest_count [Integer] Print slowest specs in the end of the suite
|
|
24
21
|
def initialize(worker_count:, rspec_args:, **opts)
|
|
25
22
|
@worker_count = worker_count
|
|
26
23
|
@worker_number_offset = opts.fetch(:worker_number_offset, 0)
|
|
@@ -30,10 +27,11 @@ module RSpec
|
|
|
30
27
|
@seed = opts[:seed] || (Random.new_seed % MAX_SEED)
|
|
31
28
|
@fail_fast_after = opts[:fail_fast_after]
|
|
32
29
|
@display_retry_backtraces = opts.fetch(:display_retry_backtraces, false)
|
|
30
|
+
@print_slowest_count = opts.fetch(:print_slowest_count, nil)
|
|
33
31
|
@verbose = opts.fetch(:verbose, false)
|
|
34
32
|
|
|
35
33
|
@rspec_args = rspec_args
|
|
36
|
-
@worker_processes =
|
|
34
|
+
@worker_processes = []
|
|
37
35
|
@spec_queue = []
|
|
38
36
|
@formatter_class = case opts[:formatter]
|
|
39
37
|
when "ci"
|
|
@@ -46,10 +44,12 @@ module RSpec
|
|
|
46
44
|
(!@verbose && Formatters::Fancy.recommended?) ? Formatters::Fancy : Formatters::Plain
|
|
47
45
|
end
|
|
48
46
|
@formatter = @formatter_class.new(worker_count: @worker_count)
|
|
49
|
-
@
|
|
47
|
+
@suite_run = SuiteRun.new
|
|
50
48
|
|
|
49
|
+
$stdout.sync = true
|
|
50
|
+
$stdin.echo = false if $stdin.tty?
|
|
51
51
|
Dir.chdir(Conductor.root)
|
|
52
|
-
ENV[
|
|
52
|
+
ENV["PARALLEL_TEST_GROUPS"] = worker_count.to_s # parallel_tests backward-compatibility
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def run
|
|
@@ -57,53 +57,28 @@ module RSpec
|
|
|
57
57
|
build_spec_queue
|
|
58
58
|
preload_application
|
|
59
59
|
|
|
60
|
-
$stdout.sync = true
|
|
61
60
|
@formatter.print_startup_banner(worker_count: @worker_count, seed: @seed, spec_files_count: @spec_queue.size)
|
|
62
61
|
|
|
63
62
|
start_workers
|
|
64
63
|
run_event_loop
|
|
65
|
-
|
|
64
|
+
wait_for_workers_to_exit
|
|
65
|
+
@suite_run.suite_complete
|
|
66
66
|
|
|
67
|
-
@formatter.print_summary(@
|
|
67
|
+
@formatter.print_summary(@suite_run, seed: @seed, success: success?)
|
|
68
|
+
@formatter.print_slowest(@suite_run, @print_slowest_count) if @print_slowest_count
|
|
68
69
|
exit_with_status
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
private
|
|
72
73
|
|
|
73
|
-
def preload_application
|
|
74
|
-
if !@prefork_require
|
|
75
|
-
debug "Prefork require not set, skipping..."
|
|
76
|
-
return
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
preload = File.expand_path(@prefork_require)
|
|
80
|
-
|
|
81
|
-
if File.exist?(preload)
|
|
82
|
-
debug "Preloading #{@prefork_require}..."
|
|
83
|
-
require preload
|
|
84
|
-
else
|
|
85
|
-
debug "#{@prefork_require} not found, skipping..."
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
debug "Application preloaded, autoload paths configured"
|
|
89
|
-
end
|
|
90
|
-
|
|
91
74
|
def setup_signal_handlers
|
|
92
75
|
%w[INT TERM].each do |signal|
|
|
93
76
|
Signal.trap(signal) do
|
|
94
|
-
@worker_processes.any? ? initiate_shutdown : Kernel.exit(1)
|
|
77
|
+
@worker_processes.any?(&:running?) ? initiate_shutdown : Kernel.exit(1)
|
|
95
78
|
end
|
|
96
79
|
end
|
|
97
80
|
end
|
|
98
81
|
|
|
99
|
-
def initiate_shutdown
|
|
100
|
-
return if @results.shutting_down?
|
|
101
|
-
|
|
102
|
-
@results.shut_down
|
|
103
|
-
@formatter.print_shut_down_banner
|
|
104
|
-
@worker_processes.each_value { |w| w.socket&.send_message({ type: :shutdown }) }
|
|
105
|
-
end
|
|
106
|
-
|
|
107
82
|
def build_spec_queue
|
|
108
83
|
config_options = RSpec::Core::ConfigurationOptions.new(@rspec_args)
|
|
109
84
|
# a bit of a hack, but if they want to require something explicitly, they should use either --prefork-require or --postfork-require,
|
|
@@ -118,140 +93,154 @@ module RSpec
|
|
|
118
93
|
debug "RSpec config: #{config.inspect}"
|
|
119
94
|
debug "Files to run: #{config.files_to_run}"
|
|
120
95
|
@spec_queue = config.files_to_run.shuffle(random: Random.new(@seed))
|
|
121
|
-
@
|
|
96
|
+
@suite_run.spec_files_total = @spec_queue.size
|
|
122
97
|
end
|
|
123
98
|
|
|
124
|
-
def
|
|
125
|
-
|
|
126
|
-
|
|
99
|
+
def preload_application
|
|
100
|
+
if !@prefork_require
|
|
101
|
+
debug "Prefork require not set, skipping..."
|
|
102
|
+
return
|
|
127
103
|
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def spawn_worker(worker_number)
|
|
131
|
-
parent_socket, child_socket = Socket.pair(:UNIX, :STREAM, 0)
|
|
132
104
|
|
|
133
|
-
|
|
105
|
+
preload = File.expand_path(@prefork_require)
|
|
134
106
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
else
|
|
141
|
-
""
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
Worker.new(
|
|
145
|
-
worker_number: worker_number,
|
|
146
|
-
socket: Protocol::Socket.new(child_socket),
|
|
147
|
-
rspec_args: @rspec_args,
|
|
148
|
-
verbose: @verbose,
|
|
149
|
-
postfork_require: @postfork_require,
|
|
150
|
-
).run
|
|
107
|
+
if File.exist?(preload)
|
|
108
|
+
debug "Preloading #{@prefork_require}..."
|
|
109
|
+
require preload
|
|
110
|
+
else
|
|
111
|
+
debug "#{@prefork_require} not found, skipping..."
|
|
151
112
|
end
|
|
152
113
|
|
|
153
|
-
|
|
154
|
-
|
|
114
|
+
debug "Application preloaded, autoload paths configured"
|
|
115
|
+
end
|
|
155
116
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
status: :running,
|
|
160
|
-
socket: Protocol::Socket.new(parent_socket),
|
|
161
|
-
current_spec: nil,
|
|
162
|
-
)
|
|
163
|
-
assign_work(@worker_processes[pid])
|
|
117
|
+
def start_workers
|
|
118
|
+
@worker_processes = @worker_count.times.map { |i| spawn_worker(@worker_number_offset + i + 1) }
|
|
119
|
+
@worker_processes.each { |wp| assign_work(wp) }
|
|
164
120
|
end
|
|
165
121
|
|
|
166
122
|
def run_event_loop
|
|
167
|
-
until @worker_processes.empty?
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
123
|
+
until @worker_processes.select(&:running?).empty?
|
|
124
|
+
if @shutdown_status == :initiated_graceful
|
|
125
|
+
@shutdown_status = :shutdown_messages_sent
|
|
126
|
+
@formatter.print_shutdown_banner
|
|
127
|
+
@worker_processes.select(&:running?).each do |worker_process|
|
|
128
|
+
worker_process.send_message({ type: :shutdown })
|
|
129
|
+
cleanup_worker_process(worker_process)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
WorkerProcess.tick_all(@worker_processes)
|
|
171
134
|
reap_workers
|
|
172
135
|
end
|
|
173
136
|
end
|
|
174
137
|
|
|
175
|
-
def
|
|
176
|
-
|
|
177
|
-
|
|
138
|
+
def wait_for_workers_to_exit
|
|
139
|
+
WorkerProcess.wait_all(@worker_processes)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def spawn_worker(worker_number)
|
|
143
|
+
debug "Spawning worker #{worker_number}"
|
|
144
|
+
|
|
145
|
+
worker_process = WorkerProcess.spawn(
|
|
146
|
+
number: worker_number,
|
|
147
|
+
test_env_number: (@first_is_1 || worker_number != 1) ? worker_number.to_s : "",
|
|
148
|
+
on_message: ->(worker_process, message) { handle_worker_message(worker_process, message) },
|
|
149
|
+
on_stdout: ->(string) { @formatter.handle_worker_stdout(worker_number, string) },
|
|
150
|
+
on_stderr: ->(string) { @formatter.handle_worker_stderr(worker_number, string) },
|
|
151
|
+
debug_io: @verbose ? $stderr : nil,
|
|
152
|
+
rspec_args: @rspec_args,
|
|
153
|
+
postfork_require: @postfork_require,
|
|
154
|
+
)
|
|
155
|
+
debug "Worker #{worker_number} started with pid #{worker_process.pid}"
|
|
156
|
+
worker_process
|
|
157
|
+
end
|
|
178
158
|
|
|
159
|
+
def handle_worker_message(worker_process, message)
|
|
179
160
|
debug "Worker #{worker_process.number}: #{message[:type]}"
|
|
180
161
|
|
|
181
162
|
case message[:type].to_sym
|
|
182
163
|
when :example_passed
|
|
183
|
-
@
|
|
164
|
+
@suite_run.example_passed(message)
|
|
184
165
|
when :example_failed
|
|
185
|
-
@
|
|
166
|
+
@suite_run.example_failed(message)
|
|
186
167
|
|
|
187
|
-
if @fail_fast_after && @
|
|
188
|
-
debug "Shutting down after #{@
|
|
168
|
+
if @fail_fast_after && @suite_run.examples_failed >= @fail_fast_after
|
|
169
|
+
debug "Shutting down after #{@suite_run.examples_failed} failures"
|
|
189
170
|
initiate_shutdown
|
|
190
171
|
end
|
|
191
172
|
when :example_pending
|
|
192
|
-
@
|
|
173
|
+
@suite_run.example_pending
|
|
193
174
|
when :example_retried
|
|
194
175
|
@formatter.print_retry_message(message) if @display_retry_backtraces
|
|
195
176
|
when :spec_complete
|
|
196
|
-
@
|
|
177
|
+
@suite_run.spec_file_complete
|
|
197
178
|
worker_process.current_spec = nil
|
|
198
179
|
assign_work(worker_process)
|
|
199
180
|
when :spec_error
|
|
200
|
-
@
|
|
181
|
+
@suite_run.spec_file_error(message)
|
|
201
182
|
debug "Spec error details: #{message[:error]}"
|
|
202
183
|
worker_process.current_spec = nil
|
|
203
184
|
assign_work(worker_process)
|
|
204
|
-
when :spec_interrupted
|
|
205
|
-
debug "Spec interrupted: #{message[:file]}"
|
|
206
|
-
worker_process.current_spec = nil
|
|
207
185
|
end
|
|
208
|
-
@formatter.handle_worker_message(worker_process, message, @
|
|
186
|
+
@formatter.handle_worker_message(worker_process, message, @suite_run)
|
|
209
187
|
end
|
|
210
188
|
|
|
211
189
|
def assign_work(worker_process)
|
|
212
190
|
spec_file = @spec_queue.shift
|
|
213
191
|
|
|
214
|
-
if
|
|
192
|
+
if shutting_down? || !spec_file
|
|
215
193
|
debug "No more work for worker #{worker_process.number}, sending shutdown"
|
|
216
|
-
worker_process.
|
|
194
|
+
worker_process.send_message({ type: :shutdown })
|
|
217
195
|
cleanup_worker_process(worker_process)
|
|
218
196
|
else
|
|
219
|
-
@
|
|
197
|
+
@suite_run.spec_file_assigned
|
|
220
198
|
worker_process.current_spec = spec_file
|
|
221
199
|
debug "Assigning #{spec_file} to worker #{worker_process.number}"
|
|
222
200
|
message = { type: :worker_assigned_spec, file: spec_file }
|
|
223
|
-
worker_process.
|
|
224
|
-
@formatter.handle_worker_message(worker_process, message, @
|
|
201
|
+
worker_process.send_message(message)
|
|
202
|
+
@formatter.handle_worker_message(worker_process, message, @suite_run)
|
|
225
203
|
end
|
|
226
204
|
end
|
|
227
205
|
|
|
228
206
|
def cleanup_worker_process(worker_process, status: :shut_down)
|
|
229
|
-
|
|
230
|
-
worker_process
|
|
231
|
-
worker_process.status = status
|
|
232
|
-
@formatter.handle_worker_message(worker_process, { type: :worker_shut_down }, @results)
|
|
233
|
-
Process.wait(worker_process.pid)
|
|
234
|
-
rescue Errno::ECHILD
|
|
235
|
-
nil
|
|
207
|
+
worker_process.shut_down(status)
|
|
208
|
+
@formatter.handle_worker_message(worker_process, { type: :worker_shut_down }, @suite_run)
|
|
236
209
|
end
|
|
237
210
|
|
|
238
211
|
def reap_workers
|
|
239
|
-
dead_worker_processes = @worker_processes.each_with_object([]) do |
|
|
240
|
-
result = Process.
|
|
241
|
-
memo << [
|
|
212
|
+
dead_worker_processes = @worker_processes.select(&:running?).each_with_object([]) do |worker_process, memo|
|
|
213
|
+
result, status = Process.waitpid2(worker_process.pid, Process::WNOHANG)
|
|
214
|
+
memo << [worker_process, status] if result
|
|
215
|
+
rescue Errno::ECHILD
|
|
242
216
|
end
|
|
243
217
|
|
|
244
218
|
dead_worker_processes.each do |worker_process, exitstatus|
|
|
245
219
|
cleanup_worker_process(worker_process, status: :terminated)
|
|
246
|
-
@
|
|
220
|
+
@suite_run.worker_crashed
|
|
247
221
|
debug "Worker #{worker_process.number} exited with status #{exitstatus.exitstatus}, signal #{exitstatus.termsig}"
|
|
248
222
|
end
|
|
249
|
-
|
|
250
|
-
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def shutting_down?
|
|
226
|
+
!@shutdown_status.nil?
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def initiate_shutdown
|
|
230
|
+
if @shutdown_status.nil?
|
|
231
|
+
@shutdown_status = :initiated_graceful
|
|
232
|
+
elsif @shutdown_status != :initiated_forced && @worker_processes.any?(&:running?)
|
|
233
|
+
@shutdown_status = :initiated_forced
|
|
234
|
+
Process.kill(:TERM, *@worker_processes.select(&:running?).map(&:pid))
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def success?
|
|
239
|
+
@suite_run.success? && !shutting_down?
|
|
251
240
|
end
|
|
252
241
|
|
|
253
242
|
def exit_with_status
|
|
254
|
-
Kernel.exit(
|
|
243
|
+
Kernel.exit(success? ? 0 : 1)
|
|
255
244
|
end
|
|
256
245
|
|
|
257
246
|
def debug(message)
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module RSpec
|
|
2
4
|
module Conductor
|
|
3
|
-
class
|
|
4
|
-
attr_accessor :
|
|
5
|
+
class SuiteRun
|
|
6
|
+
attr_accessor :examples_passed, :examples_failed, :examples_pending, :worker_crashes, :errors, :started_at, :spec_files_total, :spec_files_processed, :example_stats
|
|
5
7
|
|
|
6
8
|
def initialize
|
|
7
|
-
@
|
|
8
|
-
@
|
|
9
|
-
@
|
|
9
|
+
@examples_passed = 0
|
|
10
|
+
@examples_failed = 0
|
|
11
|
+
@examples_pending = 0
|
|
10
12
|
@worker_crashes = 0
|
|
11
13
|
@errors = []
|
|
12
14
|
@started_at = Time.now
|
|
@@ -14,24 +16,26 @@ module RSpec
|
|
|
14
16
|
@specs_completed_at = nil
|
|
15
17
|
@spec_files_total = 0
|
|
16
18
|
@spec_files_processed = 0
|
|
17
|
-
@
|
|
19
|
+
@example_stats = []
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def success?
|
|
21
|
-
@
|
|
23
|
+
@examples_failed.zero? && @errors.empty? && @worker_crashes.zero? && @spec_files_total == @spec_files_processed
|
|
22
24
|
end
|
|
23
25
|
|
|
24
|
-
def example_passed
|
|
25
|
-
@
|
|
26
|
+
def example_passed(message)
|
|
27
|
+
@example_stats << message.slice(:location, :run_time, :description)
|
|
28
|
+
@examples_passed += 1
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
def example_failed(message)
|
|
29
|
-
@
|
|
32
|
+
@example_stats << message.slice(:location, :run_time, :description)
|
|
33
|
+
@examples_failed += 1
|
|
30
34
|
@errors << message
|
|
31
35
|
end
|
|
32
36
|
|
|
33
37
|
def example_pending
|
|
34
|
-
@
|
|
38
|
+
@examples_pending += 1
|
|
35
39
|
end
|
|
36
40
|
|
|
37
41
|
def spec_file_assigned
|
|
@@ -56,14 +60,6 @@ module RSpec
|
|
|
56
60
|
@worker_crashes += 1
|
|
57
61
|
end
|
|
58
62
|
|
|
59
|
-
def shut_down
|
|
60
|
-
@shutting_down = true
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def shutting_down?
|
|
64
|
-
@shutting_down
|
|
65
|
-
end
|
|
66
|
-
|
|
67
63
|
def suite_complete
|
|
68
64
|
@specs_completed_at ||= Time.now
|
|
69
65
|
end
|
|
@@ -4,6 +4,8 @@ module RSpec
|
|
|
4
4
|
module Conductor
|
|
5
5
|
module Util
|
|
6
6
|
class ChildProcess
|
|
7
|
+
POLL_INTERVAL = 0.01
|
|
8
|
+
|
|
7
9
|
attr_reader :pid, :exit_status
|
|
8
10
|
|
|
9
11
|
def self.fork(**args, &block)
|
|
@@ -12,18 +14,24 @@ module RSpec
|
|
|
12
14
|
|
|
13
15
|
def self.wait_all(processes)
|
|
14
16
|
until processes.all?(&:done?)
|
|
15
|
-
|
|
16
|
-
process.pipes.reject(&:closed?).each { |pipe| memo[pipe] = process }
|
|
17
|
-
end
|
|
18
|
-
break if pipe_to_process.empty?
|
|
19
|
-
|
|
20
|
-
ready, = IO.select(pipe_to_process.keys, nil, nil, 0.1)
|
|
21
|
-
ready&.each { |pipe| pipe_to_process[pipe].read_available(pipe) }
|
|
17
|
+
break unless tick_all(processes)
|
|
22
18
|
end
|
|
23
19
|
|
|
24
20
|
processes.each(&:finalize)
|
|
25
21
|
end
|
|
26
22
|
|
|
23
|
+
def self.tick_all(processes, poll_interval: POLL_INTERVAL)
|
|
24
|
+
processes_by_io = processes.each_with_object({}) do |process, memo|
|
|
25
|
+
process.pipes.reject(&:closed?).each { |pipe| memo[pipe] = process }
|
|
26
|
+
end
|
|
27
|
+
return false if processes_by_io.empty?
|
|
28
|
+
|
|
29
|
+
ready, = IO.select(processes_by_io.keys, nil, nil, poll_interval)
|
|
30
|
+
ready&.each { |pipe| processes_by_io[pipe].read_available(pipe) }
|
|
31
|
+
|
|
32
|
+
true
|
|
33
|
+
end
|
|
34
|
+
|
|
27
35
|
def initialize(on_stdout: nil, on_stderr: nil)
|
|
28
36
|
@on_stdout = on_stdout
|
|
29
37
|
@on_stderr = on_stderr
|
|
@@ -55,11 +63,13 @@ module RSpec
|
|
|
55
63
|
|
|
56
64
|
$stdout = stdout_write
|
|
57
65
|
$stderr = stderr_write
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
$stdin = File.open("/dev/null")
|
|
67
|
+
STDOUT.reopen($stdout)
|
|
68
|
+
STDERR.reopen($stderr)
|
|
69
|
+
STDIN.reopen($stdin)
|
|
60
70
|
|
|
61
71
|
begin
|
|
62
|
-
yield
|
|
72
|
+
yield self
|
|
63
73
|
rescue => e
|
|
64
74
|
stderr_write.puts "#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
|
|
65
75
|
exit 1
|
|
@@ -111,12 +121,17 @@ module RSpec
|
|
|
111
121
|
def finalize
|
|
112
122
|
return if done?
|
|
113
123
|
|
|
114
|
-
process_buffer(@stdout_buffer, @on_stdout, partial: true)
|
|
115
|
-
process_buffer(@stderr_buffer, @on_stderr, partial: true)
|
|
116
|
-
|
|
117
|
-
_, status = Process.wait2(@pid)
|
|
118
|
-
@exit_status = status.exitstatus
|
|
119
124
|
@done = true
|
|
125
|
+
|
|
126
|
+
process_buffer(@stdout_buffer, @on_stdout, drain_remaining: true)
|
|
127
|
+
process_buffer(@stderr_buffer, @on_stderr, drain_remaining: true)
|
|
128
|
+
|
|
129
|
+
begin
|
|
130
|
+
_, status = Process.waitpid2(@pid)
|
|
131
|
+
@exit_status = status.exitstatus
|
|
132
|
+
rescue Errno::ECHILD
|
|
133
|
+
end
|
|
134
|
+
|
|
120
135
|
self
|
|
121
136
|
end
|
|
122
137
|
|
|
@@ -130,21 +145,17 @@ module RSpec
|
|
|
130
145
|
|
|
131
146
|
private
|
|
132
147
|
|
|
133
|
-
def process_buffer(buffer, callback,
|
|
134
|
-
|
|
148
|
+
def process_buffer(buffer, callback, drain_remaining: false)
|
|
149
|
+
while (newline_pos = buffer.index("\n"))
|
|
150
|
+
# String#slice! seems like it was invented specifically for this scenario,
|
|
151
|
+
# when you need to cut out a string fragment destructively
|
|
152
|
+
line = buffer.slice!(0..newline_pos).chomp
|
|
153
|
+
callback&.call(line)
|
|
154
|
+
end
|
|
135
155
|
|
|
136
|
-
if
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
buffer.clear
|
|
140
|
-
end
|
|
141
|
-
else
|
|
142
|
-
while (newline_pos = buffer.index("\n"))
|
|
143
|
-
# String#slice! seems like it was invented specifically for this scenario,
|
|
144
|
-
# when you need to cut out a string fragment destructively
|
|
145
|
-
line = buffer.slice!(0..newline_pos).chomp
|
|
146
|
-
callback.call(line)
|
|
147
|
-
end
|
|
156
|
+
if drain_remaining && !buffer.empty?
|
|
157
|
+
callback&.call(buffer.chomp)
|
|
158
|
+
buffer.clear
|
|
148
159
|
end
|
|
149
160
|
end
|
|
150
161
|
end
|
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'ext/rspec'
|
|
4
|
-
|
|
5
3
|
module RSpec
|
|
6
4
|
module Conductor
|
|
7
5
|
class Worker
|
|
8
|
-
def initialize(worker_number:, socket:, rspec_args: [],
|
|
6
|
+
def initialize(worker_number:, socket:, rspec_args: [], postfork_require: :spec_helper, debug_io: nil)
|
|
9
7
|
@worker_number = worker_number
|
|
10
8
|
@socket = socket
|
|
11
9
|
@rspec_args = rspec_args
|
|
12
|
-
@verbose = verbose
|
|
13
10
|
@postfork_require = postfork_require
|
|
14
|
-
|
|
11
|
+
@debug_io = debug_io
|
|
15
12
|
@message_queue = []
|
|
16
13
|
end
|
|
17
14
|
|
|
18
15
|
def run
|
|
19
|
-
suppress_output unless @verbose
|
|
20
16
|
debug "Worker #{@worker_number} starting"
|
|
17
|
+
setup_signal_handlers
|
|
21
18
|
setup_load_path
|
|
22
19
|
require_postfork_preloads
|
|
23
20
|
|
|
@@ -57,6 +54,11 @@ module RSpec
|
|
|
57
54
|
|
|
58
55
|
private
|
|
59
56
|
|
|
57
|
+
def setup_signal_handlers
|
|
58
|
+
Signal.trap(:INT, :IGNORE)
|
|
59
|
+
Signal.trap(:TERM, :EXIT)
|
|
60
|
+
end
|
|
61
|
+
|
|
60
62
|
def setup_load_path
|
|
61
63
|
parsed_options.configure(RSpec.configuration)
|
|
62
64
|
@default_path = RSpec.configuration.default_path || "spec"
|
|
@@ -75,12 +77,6 @@ module RSpec
|
|
|
75
77
|
$LOAD_PATH.unshift(path)
|
|
76
78
|
end
|
|
77
79
|
|
|
78
|
-
def suppress_output
|
|
79
|
-
$stdout.reopen(null_io_out)
|
|
80
|
-
$stderr.reopen(null_io_out)
|
|
81
|
-
$stdin.reopen(null_io_in)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
80
|
def require_postfork_preloads
|
|
85
81
|
if @postfork_require == :spec_helper
|
|
86
82
|
rails_helper = File.expand_path("rails_helper.rb", @default_full_path)
|
|
@@ -172,16 +168,12 @@ module RSpec
|
|
|
172
168
|
end
|
|
173
169
|
|
|
174
170
|
def debug(message)
|
|
175
|
-
|
|
171
|
+
@debug_io.puts message if @debug_io
|
|
176
172
|
end
|
|
177
173
|
|
|
178
174
|
def null_io_out
|
|
179
175
|
@null_io_out ||= File.open(File::NULL, "w")
|
|
180
176
|
end
|
|
181
|
-
|
|
182
|
-
def null_io_in
|
|
183
|
-
@null_io_in ||= File.open(File::NULL, "r")
|
|
184
|
-
end
|
|
185
177
|
end
|
|
186
178
|
end
|
|
187
179
|
end
|
|
@@ -1,6 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
|
|
1
5
|
module RSpec
|
|
2
6
|
module Conductor
|
|
3
|
-
WorkerProcess = Struct.new(:pid, :number, :status, :socket, :current_spec, keyword_init: true) do
|
|
7
|
+
WorkerProcess = Struct.new(:pid, :child_process, :number, :on_message, :status, :socket, :current_spec, keyword_init: true) do
|
|
8
|
+
def self.spawn(number:, test_env_number:, on_message:, on_stdout: nil, on_stderr: nil, **worker_init_args)
|
|
9
|
+
parent_socket, child_socket = Socket.pair(:UNIX, :STREAM, 0)
|
|
10
|
+
child_process = Util::ChildProcess.fork(on_stdout: on_stdout, on_stderr: on_stderr) do
|
|
11
|
+
ENV["TEST_ENV_NUMBER"] = test_env_number
|
|
12
|
+
parent_socket.close
|
|
13
|
+
Worker.new(
|
|
14
|
+
worker_number: number,
|
|
15
|
+
socket: Protocol::Socket.new(child_socket),
|
|
16
|
+
**worker_init_args
|
|
17
|
+
).run
|
|
18
|
+
end
|
|
19
|
+
child_socket.close
|
|
20
|
+
|
|
21
|
+
new(
|
|
22
|
+
pid: child_process.pid,
|
|
23
|
+
child_process: child_process,
|
|
24
|
+
on_message: on_message,
|
|
25
|
+
number: number,
|
|
26
|
+
status: :running,
|
|
27
|
+
socket: Protocol::Socket.new(parent_socket),
|
|
28
|
+
current_spec: nil
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.tick_all(worker_processes)
|
|
33
|
+
worker_processes_by_io = worker_processes.select(&:running?).to_h { |w| [w.socket.io, w] }
|
|
34
|
+
readable_ios, = IO.select(worker_processes_by_io.keys, nil, nil, 0)
|
|
35
|
+
readable_ios&.each { |io| worker_processes_by_io.fetch(io).handle_message }
|
|
36
|
+
Util::ChildProcess.tick_all(worker_processes.map(&:child_process))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.wait_all(worker_processes)
|
|
40
|
+
Util::ChildProcess.wait_all(worker_processes.map(&:child_process))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def handle_message
|
|
44
|
+
message = receive_message
|
|
45
|
+
return unless message && on_message
|
|
46
|
+
|
|
47
|
+
on_message.call(self, message)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def send_message(message)
|
|
51
|
+
socket.send_message(message)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def receive_message
|
|
55
|
+
socket.receive_message
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def shut_down(status)
|
|
59
|
+
return unless running?
|
|
60
|
+
|
|
61
|
+
self.status = status
|
|
62
|
+
socket.close
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def running?
|
|
66
|
+
status == :running
|
|
67
|
+
end
|
|
68
|
+
|
|
4
69
|
def hash
|
|
5
70
|
[number].hash
|
|
6
71
|
end
|
data/lib/rspec/conductor.rb
CHANGED
|
@@ -28,12 +28,14 @@ module RSpec
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
require_relative "conductor/util/ansi"
|
|
31
|
+
require_relative "conductor/util/screen_buffer"
|
|
31
32
|
require_relative "conductor/util/terminal"
|
|
33
|
+
require_relative "conductor/util/child_process"
|
|
32
34
|
require_relative "conductor/version"
|
|
33
35
|
require_relative "conductor/protocol"
|
|
34
36
|
require_relative "conductor/server"
|
|
35
37
|
require_relative "conductor/worker"
|
|
36
|
-
require_relative "conductor/
|
|
38
|
+
require_relative "conductor/suite_run"
|
|
37
39
|
require_relative "conductor/worker_process"
|
|
38
40
|
require_relative "conductor/cli"
|
|
39
41
|
require_relative "conductor/rspec_subscriber"
|
|
@@ -42,6 +44,8 @@ require_relative "conductor/formatters/plain"
|
|
|
42
44
|
require_relative "conductor/formatters/ci"
|
|
43
45
|
require_relative "conductor/formatters/fancy"
|
|
44
46
|
|
|
47
|
+
require_relative "conductor/ext/rspec"
|
|
48
|
+
|
|
45
49
|
if defined?(Rails)
|
|
46
50
|
require_relative "conductor/railtie"
|
|
47
51
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rspec-conductor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mark Abramov
|
|
@@ -48,9 +48,9 @@ files:
|
|
|
48
48
|
- lib/rspec/conductor/formatters/plain.rb
|
|
49
49
|
- lib/rspec/conductor/protocol.rb
|
|
50
50
|
- lib/rspec/conductor/railtie.rb
|
|
51
|
-
- lib/rspec/conductor/results.rb
|
|
52
51
|
- lib/rspec/conductor/rspec_subscriber.rb
|
|
53
52
|
- lib/rspec/conductor/server.rb
|
|
53
|
+
- lib/rspec/conductor/suite_run.rb
|
|
54
54
|
- lib/rspec/conductor/util/ansi.rb
|
|
55
55
|
- lib/rspec/conductor/util/child_process.rb
|
|
56
56
|
- lib/rspec/conductor/util/screen_buffer.rb
|
|
@@ -82,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
82
82
|
- !ruby/object:Gem::Version
|
|
83
83
|
version: '0'
|
|
84
84
|
requirements: []
|
|
85
|
-
rubygems_version: 4.0.
|
|
85
|
+
rubygems_version: 4.0.6
|
|
86
86
|
specification_version: 4
|
|
87
87
|
summary: Queue-based parallel test runner for rspec
|
|
88
88
|
test_files: []
|