rspec-conductor 1.0.1 → 1.0.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/CHANGELOG.md +4 -0
- data/lib/rspec/conductor/ansi.rb +106 -0
- data/lib/rspec/conductor/cli.rb +1 -0
- data/lib/rspec/conductor/ext/rspec.rb +13 -0
- data/lib/rspec/conductor/formatters/ci.rb +14 -15
- data/lib/rspec/conductor/formatters/fancy.rb +47 -76
- data/lib/rspec/conductor/formatters/plain.rb +7 -12
- data/lib/rspec/conductor/results.rb +80 -0
- data/lib/rspec/conductor/rspec_subscriber.rb +74 -0
- data/lib/rspec/conductor/server.rb +69 -69
- data/lib/rspec/conductor/version.rb +1 -1
- data/lib/rspec/conductor/worker.rb +5 -89
- data/lib/rspec/conductor/worker_process.rb +13 -0
- data/lib/rspec/conductor.rb +4 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 50d787ec617be586c7f66328850d62f2253bd5f1a50d1effb6417ccfdb4ee3a2
|
|
4
|
+
data.tar.gz: d71b0b92aa35eb18991ae91a2d3c95d532da79beb80e304e227da87dd214656a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fe26f4f57787171577683b762e57ac8360b52d9949fbe1c6f4b6b8161da7747815e1b1296b1098ae05f9d32a605089847a0bb1c2f6f12f1273c50e7398118099
|
|
7
|
+
data.tar.gz: 6377b5235c007f274ad83778809319f7eee8e4cc484187202fb8919b8ef8e9ca7122e96e10b1948b6c9b7ca7db2e639bccc8d0e58c5781f17017cb40b119f1bd
|
data/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSpec
|
|
4
|
+
module Conductor
|
|
5
|
+
module ANSI
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
COLOR_CODES = {
|
|
9
|
+
# Reset
|
|
10
|
+
reset: "0",
|
|
11
|
+
|
|
12
|
+
# Styles
|
|
13
|
+
bold: "1",
|
|
14
|
+
dim: "2",
|
|
15
|
+
italic: "3",
|
|
16
|
+
underline: "4",
|
|
17
|
+
blink: "5",
|
|
18
|
+
inverse: "7",
|
|
19
|
+
hidden: "8",
|
|
20
|
+
strikethrough: "9",
|
|
21
|
+
|
|
22
|
+
# Foreground colors
|
|
23
|
+
black: "30",
|
|
24
|
+
red: "31",
|
|
25
|
+
green: "32",
|
|
26
|
+
yellow: "33",
|
|
27
|
+
blue: "34",
|
|
28
|
+
magenta: "35",
|
|
29
|
+
cyan: "36",
|
|
30
|
+
white: "37",
|
|
31
|
+
|
|
32
|
+
# Bright foreground colors
|
|
33
|
+
bright_black: "90",
|
|
34
|
+
bright_red: "91",
|
|
35
|
+
bright_green: "92",
|
|
36
|
+
bright_yellow: "93",
|
|
37
|
+
bright_blue: "94",
|
|
38
|
+
bright_magenta: "95",
|
|
39
|
+
bright_cyan: "96",
|
|
40
|
+
bright_white: "97",
|
|
41
|
+
|
|
42
|
+
# Background colors
|
|
43
|
+
bg_black: "40",
|
|
44
|
+
bg_red: "41",
|
|
45
|
+
bg_green: "42",
|
|
46
|
+
bg_yellow: "43",
|
|
47
|
+
bg_blue: "44",
|
|
48
|
+
bg_magenta: "45",
|
|
49
|
+
bg_cyan: "46",
|
|
50
|
+
bg_white: "47",
|
|
51
|
+
|
|
52
|
+
# Bright background colors
|
|
53
|
+
bg_bright_black: "100",
|
|
54
|
+
bg_bright_red: "101",
|
|
55
|
+
bg_bright_green: "102",
|
|
56
|
+
bg_bright_yellow: "103",
|
|
57
|
+
bg_bright_blue: "104",
|
|
58
|
+
bg_bright_magenta: "105",
|
|
59
|
+
bg_bright_cyan: "106",
|
|
60
|
+
bg_bright_white: "107",
|
|
61
|
+
}.freeze
|
|
62
|
+
|
|
63
|
+
def colorize(string, colors, reset: true)
|
|
64
|
+
[
|
|
65
|
+
"\e[",
|
|
66
|
+
Array(colors).map { |color| color_code(color) }.join(";"),
|
|
67
|
+
"m",
|
|
68
|
+
string,
|
|
69
|
+
reset ? "\e[#{color_code(:reset)}m" : nil,
|
|
70
|
+
].join
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def color_code(color)
|
|
74
|
+
COLOR_CODES.fetch(color, COLOR_CODES[:reset])
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def cursor_up(n_lines)
|
|
78
|
+
n_lines.positive? ? "\e[#{n_lines}A" : ""
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def cursor_down(n_lines)
|
|
82
|
+
n_lines.positive? ? "\e[#{n_lines}B" : ""
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def clear_line
|
|
86
|
+
"\e[2K\r"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# sticks invisible characters to visible ones when splitting (so that an ansi color code doesn"t get split mid-way)
|
|
90
|
+
def split_visible_char_groups(string)
|
|
91
|
+
invisible = "(?:\\e\\[[0-9;]*[a-zA-Z])"
|
|
92
|
+
visible = "(?:[^\\e])"
|
|
93
|
+
scan_regex = Regexp.new("#{invisible}*#{visible}#{invisible}*|#{invisible}+")
|
|
94
|
+
string.scan(scan_regex)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def visible_chars(string)
|
|
98
|
+
string.gsub(/\e\[[0-9;]*[a-zA-Z]/, '')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def tty_width
|
|
102
|
+
$stdout.tty? ? $stdout.winsize[1] : 80
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
data/lib/rspec/conductor/cli.rb
CHANGED
|
@@ -119,6 +119,7 @@ module RSpec
|
|
|
119
119
|
worker_count: @conductor_options[:workers],
|
|
120
120
|
worker_number_offset: @conductor_options[:offset],
|
|
121
121
|
prefork_require: @conductor_options[:prefork_require],
|
|
122
|
+
postfork_require: @conductor_options[:postfork_require],
|
|
122
123
|
first_is_1: @conductor_options[:first_is_1],
|
|
123
124
|
seed: @conductor_options[:seed],
|
|
124
125
|
fail_fast_after: @conductor_options[:fail_fast_after],
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# RSpec doesn't provide us with a good way to handle before/after suite hooks,
|
|
2
|
+
# doing what we can here
|
|
3
|
+
class RSpec::Core::Configuration
|
|
4
|
+
def __run_before_suite_hooks
|
|
5
|
+
RSpec.current_scope = :before_suite_hook if RSpec.respond_to?(:current_scope=)
|
|
6
|
+
run_suite_hooks("a `before(:suite)` hook", @before_suite_hooks) if respond_to?(:run_suite_hooks)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def __run_after_suite_hooks
|
|
10
|
+
RSpec.current_scope = :after_suite_hook if RSpec.respond_to?(:current_scope=)
|
|
11
|
+
run_suite_hooks("an `after(:suite)` hook", @after_suite_hooks) if respond_to?(:run_suite_hooks)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -2,27 +2,32 @@ module RSpec
|
|
|
2
2
|
module Conductor
|
|
3
3
|
module Formatters
|
|
4
4
|
class CI
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
include Conductor::ANSI
|
|
6
|
+
|
|
7
|
+
DEFAULT_PRINTOUT_INTERVAL = 10
|
|
8
|
+
|
|
9
|
+
# @option printout_interval how often a printout happens, in seconds
|
|
10
|
+
def initialize(printout_interval: DEFAULT_PRINTOUT_INTERVAL)
|
|
11
|
+
@printout_interval = printout_interval
|
|
7
12
|
@last_printout = Time.now
|
|
8
13
|
end
|
|
9
14
|
|
|
10
|
-
def handle_worker_message(
|
|
15
|
+
def handle_worker_message(_worker_process, message, results)
|
|
11
16
|
public_send(message[:type], message) if respond_to?(message[:type])
|
|
12
|
-
print_status(results) if @last_printout + @
|
|
17
|
+
print_status(results) if @last_printout + @printout_interval < Time.now
|
|
13
18
|
end
|
|
14
19
|
|
|
15
20
|
def print_status(results)
|
|
16
21
|
@last_printout = Time.now
|
|
17
|
-
|
|
22
|
+
pct = results.spec_file_processed_percentage
|
|
18
23
|
|
|
19
24
|
puts "-" * tty_width
|
|
20
25
|
puts "Current status [#{Time.now.strftime("%H:%M:%S")}]:"
|
|
21
|
-
puts "Processed: #{results
|
|
22
|
-
puts "#{results
|
|
23
|
-
if results
|
|
26
|
+
puts "Processed: #{results.spec_files_processed} / #{results.spec_files_total} (#{(pct * 100).floor}%)"
|
|
27
|
+
puts "#{results.passed} passed, #{results.failed} failed, #{results.pending} pending"
|
|
28
|
+
if results.errors.any?
|
|
24
29
|
puts "Failures:\n"
|
|
25
|
-
results
|
|
30
|
+
results.errors.each_with_index do |error, i|
|
|
26
31
|
puts " #{i + 1}) #{error[:description]}"
|
|
27
32
|
puts " #{error[:location]}"
|
|
28
33
|
puts " #{error[:message]}" if error[:message]
|
|
@@ -35,12 +40,6 @@ module RSpec
|
|
|
35
40
|
end
|
|
36
41
|
puts "-" * tty_width
|
|
37
42
|
end
|
|
38
|
-
|
|
39
|
-
private
|
|
40
|
-
|
|
41
|
-
def tty_width
|
|
42
|
-
$stdout.tty? ? $stdout.winsize[1] : 80
|
|
43
|
-
end
|
|
44
43
|
end
|
|
45
44
|
end
|
|
46
45
|
end
|
|
@@ -1,54 +1,50 @@
|
|
|
1
1
|
require "pathname"
|
|
2
|
+
require "set"
|
|
2
3
|
|
|
3
4
|
module RSpec
|
|
4
5
|
module Conductor
|
|
5
6
|
module Formatters
|
|
6
7
|
class Fancy
|
|
7
|
-
|
|
8
|
-
GREEN = 32
|
|
9
|
-
YELLOW = 33
|
|
10
|
-
MAGENTA = 35
|
|
11
|
-
CYAN = 36
|
|
12
|
-
NORMAL = 0
|
|
8
|
+
include Conductor::ANSI
|
|
13
9
|
|
|
14
10
|
def self.recommended?
|
|
15
11
|
$stdout.tty? && $stdout.winsize[0] >= 30 && $stdout.winsize[1] >= 80
|
|
16
12
|
end
|
|
17
13
|
|
|
18
14
|
def initialize
|
|
19
|
-
@
|
|
15
|
+
@worker_processes = Set.new
|
|
20
16
|
@last_rendered_lines = []
|
|
21
17
|
@dots = []
|
|
22
18
|
@last_error = nil
|
|
23
19
|
end
|
|
24
20
|
|
|
25
|
-
def handle_worker_message(
|
|
26
|
-
@
|
|
27
|
-
public_send(message[:type],
|
|
21
|
+
def handle_worker_message(worker_process, message, results)
|
|
22
|
+
@worker_processes << worker_process
|
|
23
|
+
public_send(message[:type], worker_process, message) if respond_to?(message[:type])
|
|
28
24
|
redraw(results)
|
|
29
25
|
end
|
|
30
26
|
|
|
31
|
-
def example_passed(
|
|
32
|
-
@dots << { char: ".", color:
|
|
27
|
+
def example_passed(_worker_process, _message)
|
|
28
|
+
@dots << { char: ".", color: :green }
|
|
33
29
|
end
|
|
34
30
|
|
|
35
|
-
def example_failed(
|
|
36
|
-
@dots << { char: "F", color:
|
|
31
|
+
def example_failed(_worker_process, message)
|
|
32
|
+
@dots << { char: "F", color: :red }
|
|
37
33
|
@last_error = message.slice(:description, :location, :exception_class, :message, :backtrace)
|
|
38
34
|
end
|
|
39
35
|
|
|
40
|
-
def example_retried(
|
|
41
|
-
@dots << { char: "R", color:
|
|
36
|
+
def example_retried(_worker_process, _message)
|
|
37
|
+
@dots << { char: "R", color: :magenta }
|
|
42
38
|
end
|
|
43
39
|
|
|
44
|
-
def example_pending(
|
|
45
|
-
@dots << { char: "*", color:
|
|
40
|
+
def example_pending(_worker_process, _message)
|
|
41
|
+
@dots << { char: "*", color: :yellow }
|
|
46
42
|
end
|
|
47
43
|
|
|
48
44
|
private
|
|
49
45
|
|
|
50
46
|
def redraw(results)
|
|
51
|
-
|
|
47
|
+
print_cursor_up(rewrap_lines(@last_rendered_lines).length)
|
|
52
48
|
|
|
53
49
|
lines = []
|
|
54
50
|
lines << progress_bar(results)
|
|
@@ -62,37 +58,47 @@ module RSpec
|
|
|
62
58
|
|
|
63
59
|
lines.each_with_index do |line, i|
|
|
64
60
|
if @last_rendered_lines[i] == line
|
|
65
|
-
|
|
61
|
+
print_cursor_down(1)
|
|
66
62
|
else
|
|
67
|
-
|
|
63
|
+
print_clear_line
|
|
68
64
|
puts line
|
|
69
65
|
end
|
|
70
66
|
end
|
|
71
67
|
|
|
72
68
|
if @last_rendered_lines.length && lines.length < @last_rendered_lines.length
|
|
73
69
|
(@last_rendered_lines.length - lines.length).times do
|
|
74
|
-
|
|
70
|
+
print_clear_line
|
|
75
71
|
puts
|
|
76
72
|
end
|
|
77
|
-
|
|
73
|
+
print_cursor_up(@last_rendered_lines.length - lines.length)
|
|
78
74
|
end
|
|
79
75
|
|
|
80
76
|
@last_rendered_lines = lines
|
|
81
77
|
end
|
|
82
78
|
|
|
83
|
-
def
|
|
84
|
-
|
|
79
|
+
def progress_bar(results)
|
|
80
|
+
pct = results.spec_file_processed_percentage
|
|
81
|
+
bar_width = [tty_width - 20, 20].max
|
|
82
|
+
|
|
83
|
+
filled = (pct * bar_width).floor
|
|
84
|
+
empty = bar_width - filled
|
|
85
|
+
|
|
86
|
+
bar = colorize("[", :reset) + colorize("▓" * filled, :green) + colorize(" " * empty, :reset) + colorize("]", :reset)
|
|
87
|
+
percentage = " #{(pct * 100).floor.to_s.rjust(3)}% (#{results.spec_files_processed}/#{results.spec_files_total})"
|
|
88
|
+
|
|
89
|
+
bar + percentage
|
|
90
|
+
end
|
|
85
91
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
prefix = colorize("Worker #{
|
|
92
|
+
def worker_lines
|
|
93
|
+
@worker_processes.sort_by(&:number).map do |worker_process|
|
|
94
|
+
prefix = colorize("Worker #{worker_process.number}: ", :cyan)
|
|
89
95
|
|
|
90
|
-
if
|
|
96
|
+
if worker_process.status == :shut_down
|
|
91
97
|
prefix + "(finished)"
|
|
92
|
-
elsif
|
|
93
|
-
prefix + colorize("(terminated)",
|
|
94
|
-
elsif
|
|
95
|
-
prefix + truncate(relative_path(
|
|
98
|
+
elsif worker_process.status == :terminated
|
|
99
|
+
prefix + colorize("(terminated)", :red)
|
|
100
|
+
elsif worker_process.current_spec
|
|
101
|
+
prefix + truncate(relative_path(worker_process.current_spec), tty_width - 15)
|
|
96
102
|
else
|
|
97
103
|
prefix + "(idle)"
|
|
98
104
|
end
|
|
@@ -103,7 +109,7 @@ module RSpec
|
|
|
103
109
|
return [] unless @last_error
|
|
104
110
|
|
|
105
111
|
lines = []
|
|
106
|
-
lines << colorize("Most recent failure:",
|
|
112
|
+
lines << colorize("Most recent failure:", :red)
|
|
107
113
|
lines << " #{@last_error[:description]}"
|
|
108
114
|
lines << " #{@last_error[:location]}"
|
|
109
115
|
|
|
@@ -124,37 +130,10 @@ module RSpec
|
|
|
124
130
|
lines.flat_map do |line|
|
|
125
131
|
_, indent, body = line.partition(/^\s*/)
|
|
126
132
|
max_width = tty_width - indent.size
|
|
127
|
-
|
|
133
|
+
split_visible_char_groups(body).each_slice(max_width).map { |chars| "#{indent}#{chars.join}" }
|
|
128
134
|
end
|
|
129
135
|
end
|
|
130
136
|
|
|
131
|
-
# sticks invisible characters to visible ones when splitting (so that an ansi color code doesn"t get split mid-way)
|
|
132
|
-
def split_chars_respecting_ansi(body)
|
|
133
|
-
invisible = "(?:\\e\\[[\\d;]*m)"
|
|
134
|
-
visible = "(?:[^\\e])"
|
|
135
|
-
scan_regex = Regexp.new("#{invisible}*#{visible}#{invisible}*|#{invisible}+")
|
|
136
|
-
body.scan(scan_regex)
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def progress_bar(results)
|
|
140
|
-
total = results[:spec_files_total]
|
|
141
|
-
processed = results[:spec_files_processed]
|
|
142
|
-
pct = total.positive? ? processed.to_f / total : 0
|
|
143
|
-
bar_width = [tty_width - 60, 20].max
|
|
144
|
-
|
|
145
|
-
filled = (pct * bar_width).floor
|
|
146
|
-
empty = bar_width - filled
|
|
147
|
-
|
|
148
|
-
bar = colorize("[", NORMAL) + colorize("▓" * filled, GREEN) + colorize(" " * empty, NORMAL) + colorize("]", NORMAL)
|
|
149
|
-
percentage = " #{(pct * 100).floor.to_s.rjust(3)}% (#{processed}/#{total})"
|
|
150
|
-
|
|
151
|
-
bar + percentage
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def max_worker_num
|
|
155
|
-
@workers.keys.max || 0
|
|
156
|
-
end
|
|
157
|
-
|
|
158
137
|
def relative_path(filename)
|
|
159
138
|
Pathname(filename).relative_path_from(Conductor.root).to_s
|
|
160
139
|
end
|
|
@@ -165,24 +144,16 @@ module RSpec
|
|
|
165
144
|
str.length > max_length ? "...#{str[-(max_length - 3)..]}" : str
|
|
166
145
|
end
|
|
167
146
|
|
|
168
|
-
def
|
|
169
|
-
$stdout.tty?
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def cursor_up(n_lines)
|
|
173
|
-
print("\e[#{n_lines}A") if $stdout.tty? && n_lines.positive?
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def cursor_down(n_lines)
|
|
177
|
-
print("\e[#{n_lines}B") if $stdout.tty? && n_lines.positive?
|
|
147
|
+
def print_cursor_up(n_lines)
|
|
148
|
+
print cursor_up(n_lines) if $stdout.tty?
|
|
178
149
|
end
|
|
179
150
|
|
|
180
|
-
def
|
|
181
|
-
print(
|
|
151
|
+
def print_cursor_down(n_lines)
|
|
152
|
+
print cursor_down(n_lines) if $stdout.tty?
|
|
182
153
|
end
|
|
183
154
|
|
|
184
|
-
def
|
|
185
|
-
$stdout.tty?
|
|
155
|
+
def print_clear_line
|
|
156
|
+
print clear_line if $stdout.tty?
|
|
186
157
|
end
|
|
187
158
|
end
|
|
188
159
|
end
|
|
@@ -2,38 +2,33 @@ module RSpec
|
|
|
2
2
|
module Conductor
|
|
3
3
|
module Formatters
|
|
4
4
|
class Plain
|
|
5
|
-
|
|
6
|
-
RED = 31
|
|
7
|
-
GREEN = 32
|
|
8
|
-
YELLOW = 33
|
|
9
|
-
MAGENTA = 35
|
|
10
|
-
NORMAL = 0
|
|
5
|
+
include Conductor::ANSI
|
|
11
6
|
|
|
12
|
-
def handle_worker_message(
|
|
7
|
+
def handle_worker_message(_worker_process, message, _results)
|
|
13
8
|
public_send(message[:type], message) if respond_to?(message[:type])
|
|
14
9
|
end
|
|
15
10
|
|
|
16
11
|
def example_passed(_message)
|
|
17
|
-
print ".",
|
|
12
|
+
print ".", :green
|
|
18
13
|
end
|
|
19
14
|
|
|
20
15
|
def example_failed(_message)
|
|
21
|
-
print "F",
|
|
16
|
+
print "F", :red
|
|
22
17
|
end
|
|
23
18
|
|
|
24
19
|
def example_retried(_message)
|
|
25
|
-
print "R",
|
|
20
|
+
print "R", :magenta
|
|
26
21
|
end
|
|
27
22
|
|
|
28
23
|
def example_pending(_message)
|
|
29
|
-
print "*",
|
|
24
|
+
print "*", :yellow
|
|
30
25
|
end
|
|
31
26
|
|
|
32
27
|
private
|
|
33
28
|
|
|
34
29
|
def print(string, color)
|
|
35
30
|
if $stdout.tty?
|
|
36
|
-
$stdout.print(
|
|
31
|
+
$stdout.print(colorize(string, color))
|
|
37
32
|
else
|
|
38
33
|
$stdout.print(string)
|
|
39
34
|
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module RSpec
|
|
2
|
+
module Conductor
|
|
3
|
+
class Results
|
|
4
|
+
attr_accessor :passed, :failed, :pending, :worker_crashes, :errors, :started_at, :spec_files_total, :spec_files_processed
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@passed = 0
|
|
8
|
+
@failed = 0
|
|
9
|
+
@pending = 0
|
|
10
|
+
@worker_crashes = 0
|
|
11
|
+
@errors = []
|
|
12
|
+
@started_at = Time.now
|
|
13
|
+
@specs_started_at = nil
|
|
14
|
+
@specs_completed_at = nil
|
|
15
|
+
@spec_files_total = 0
|
|
16
|
+
@spec_files_processed = 0
|
|
17
|
+
@shutting_down = false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def success?
|
|
21
|
+
@failed.zero? && @errors.empty? && @worker_crashes.zero? && !shutting_down?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def example_passed
|
|
25
|
+
@passed += 1
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def example_failed(message)
|
|
29
|
+
@failed += 1
|
|
30
|
+
@errors << message
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def example_pending
|
|
34
|
+
@pending += 1
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def spec_file_assigned
|
|
38
|
+
@specs_started_at ||= Time.now
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def spec_file_complete
|
|
42
|
+
@spec_files_processed += 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def spec_file_error(message)
|
|
46
|
+
@errors << message
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def spec_file_processed_percentage
|
|
50
|
+
return 0.0 if @spec_files_total.zero?
|
|
51
|
+
|
|
52
|
+
@spec_files_processed.to_f / @spec_files_total
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def worker_crashed
|
|
56
|
+
@worker_crashes += 1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def shut_down
|
|
60
|
+
@shutting_down = true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def shutting_down?
|
|
64
|
+
@shutting_down
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def suite_complete
|
|
68
|
+
@specs_completed_at ||= Time.now
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def specs_runtime
|
|
72
|
+
((@specs_completed_at || Time.now) - (@specs_started_at || @started_at)).to_f
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def total_runtime
|
|
76
|
+
((@specs_completed_at || Time.now) - @started_at).to_f
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module RSpec
|
|
2
|
+
module Conductor
|
|
3
|
+
# Technically this is a **Formatter**, as in RSpec Formatter, but that was too confusing,
|
|
4
|
+
# and there is another thing called formatter in this library. Hence, Subscriber.
|
|
5
|
+
class RSpecSubscriber
|
|
6
|
+
RSpec::Core::Formatters.register self,
|
|
7
|
+
:example_passed,
|
|
8
|
+
:example_failed,
|
|
9
|
+
:example_pending
|
|
10
|
+
|
|
11
|
+
def initialize(socket, file, shutdown_check)
|
|
12
|
+
@socket = socket
|
|
13
|
+
@file = file
|
|
14
|
+
@shutdown_check = shutdown_check
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def example_passed(notification)
|
|
18
|
+
@socket.send_message(
|
|
19
|
+
type: :example_passed,
|
|
20
|
+
file: @file,
|
|
21
|
+
description: notification.example.full_description,
|
|
22
|
+
location: notification.example.location,
|
|
23
|
+
run_time: notification.example.execution_result.run_time
|
|
24
|
+
)
|
|
25
|
+
@shutdown_check.call
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def example_failed(notification)
|
|
29
|
+
ex = notification.example
|
|
30
|
+
@socket.send_message(
|
|
31
|
+
type: :example_failed,
|
|
32
|
+
file: @file,
|
|
33
|
+
description: ex.full_description,
|
|
34
|
+
location: ex.location,
|
|
35
|
+
run_time: ex.execution_result.run_time,
|
|
36
|
+
exception_class: ex.execution_result.exception&.class&.name,
|
|
37
|
+
message: ex.execution_result.exception&.message,
|
|
38
|
+
backtrace: format_backtrace(ex.execution_result.exception&.backtrace, ex.metadata)
|
|
39
|
+
)
|
|
40
|
+
@shutdown_check.call
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def example_pending(notification)
|
|
44
|
+
ex = notification.example
|
|
45
|
+
@socket.send_message(
|
|
46
|
+
type: :example_pending,
|
|
47
|
+
file: @file,
|
|
48
|
+
description: ex.full_description,
|
|
49
|
+
location: ex.location,
|
|
50
|
+
pending_message: ex.execution_result.pending_message
|
|
51
|
+
)
|
|
52
|
+
@shutdown_check.call
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# This one is invoked by rspec-retry, hence the slightly different api from example_* methods
|
|
56
|
+
def retry(ex)
|
|
57
|
+
@socket.send_message(
|
|
58
|
+
type: :example_retried,
|
|
59
|
+
description: ex.full_description,
|
|
60
|
+
location: ex.location,
|
|
61
|
+
exception_class: ex.exception&.class&.name,
|
|
62
|
+
message: ex.exception&.message,
|
|
63
|
+
backtrace: format_backtrace(ex.exception&.backtrace, ex.metadata)
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def format_backtrace(backtrace, example_metadata = nil)
|
|
70
|
+
RSpec::Core::BacktraceFormatter.new.format_backtrace(backtrace || [], example_metadata || {})
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -8,6 +8,9 @@ require "io/console"
|
|
|
8
8
|
module RSpec
|
|
9
9
|
module Conductor
|
|
10
10
|
class Server
|
|
11
|
+
MAX_SEED = 2**16
|
|
12
|
+
WORKER_POLL_INTERVAL = 0.01
|
|
13
|
+
|
|
11
14
|
# @option worker_count [Integer] How many workers to spin
|
|
12
15
|
# @option rspec_args [Array<String>] A list of rspec options
|
|
13
16
|
# @option worker_number_offset [Integer] Start worker numbering with an offset
|
|
@@ -25,16 +28,14 @@ module RSpec
|
|
|
25
28
|
@prefork_require = opts.fetch(:prefork_require, nil)
|
|
26
29
|
@postfork_require = opts.fetch(:postfork_require, nil)
|
|
27
30
|
@first_is_one = opts.fetch(:first_is_1, false)
|
|
28
|
-
@seed = opts[:seed] || (Random.new_seed %
|
|
31
|
+
@seed = opts[:seed] || (Random.new_seed % MAX_SEED)
|
|
29
32
|
@fail_fast_after = opts[:fail_fast_after]
|
|
30
33
|
@display_retry_backtraces = opts.fetch(:display_retry_backtraces, false)
|
|
31
34
|
@verbose = opts.fetch(:verbose, false)
|
|
32
35
|
|
|
33
36
|
@rspec_args = rspec_args
|
|
34
|
-
@
|
|
37
|
+
@worker_processes = {}
|
|
35
38
|
@spec_queue = []
|
|
36
|
-
@started_at = Time.now
|
|
37
|
-
@shutting_down = false
|
|
38
39
|
@formatter = case opts[:formatter]
|
|
39
40
|
when "ci"
|
|
40
41
|
Formatters::CI.new
|
|
@@ -45,7 +46,7 @@ module RSpec
|
|
|
45
46
|
else
|
|
46
47
|
(!@verbose && Formatters::Fancy.recommended?) ? Formatters::Fancy.new : Formatters::Plain.new
|
|
47
48
|
end
|
|
48
|
-
@results =
|
|
49
|
+
@results = Results.new
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
def run
|
|
@@ -59,6 +60,7 @@ module RSpec
|
|
|
59
60
|
|
|
60
61
|
start_workers
|
|
61
62
|
run_event_loop
|
|
63
|
+
@results.suite_complete
|
|
62
64
|
|
|
63
65
|
print_summary
|
|
64
66
|
exit_with_status
|
|
@@ -87,18 +89,17 @@ module RSpec
|
|
|
87
89
|
def setup_signal_handlers
|
|
88
90
|
%w[INT TERM].each do |signal|
|
|
89
91
|
Signal.trap(signal) do
|
|
90
|
-
@
|
|
92
|
+
@worker_processes.any? ? initiate_shutdown : Kernel.exit(1)
|
|
91
93
|
end
|
|
92
94
|
end
|
|
93
95
|
end
|
|
94
96
|
|
|
95
97
|
def initiate_shutdown
|
|
96
|
-
return if @shutting_down
|
|
97
|
-
|
|
98
|
-
@shutting_down = true
|
|
98
|
+
return if @results.shutting_down?
|
|
99
99
|
|
|
100
|
+
@results.shut_down
|
|
100
101
|
puts "Shutting down..."
|
|
101
|
-
@
|
|
102
|
+
@worker_processes.each_value { |w| w.socket&.send_message({ type: :shutdown }) }
|
|
102
103
|
end
|
|
103
104
|
|
|
104
105
|
def build_spec_queue
|
|
@@ -108,7 +109,7 @@ module RSpec
|
|
|
108
109
|
config.files_or_directories_to_run = paths
|
|
109
110
|
|
|
110
111
|
@spec_queue = config.files_to_run.shuffle(random: Random.new(@seed))
|
|
111
|
-
@results
|
|
112
|
+
@results.spec_files_total = @spec_queue.size
|
|
112
113
|
end
|
|
113
114
|
|
|
114
115
|
def parsed_rspec_args
|
|
@@ -152,104 +153,99 @@ module RSpec
|
|
|
152
153
|
child_socket.close
|
|
153
154
|
debug "Worker #{worker_number} started with pid #{pid}"
|
|
154
155
|
|
|
155
|
-
@
|
|
156
|
+
@worker_processes[pid] = WorkerProcess.new(
|
|
156
157
|
pid: pid,
|
|
157
158
|
number: worker_number,
|
|
158
159
|
status: :running,
|
|
159
160
|
socket: Protocol::Socket.new(parent_socket),
|
|
160
161
|
current_spec: nil,
|
|
161
|
-
|
|
162
|
-
assign_work(@
|
|
162
|
+
)
|
|
163
|
+
assign_work(@worker_processes[pid])
|
|
163
164
|
end
|
|
164
165
|
|
|
165
166
|
def run_event_loop
|
|
166
|
-
until @
|
|
167
|
-
|
|
168
|
-
readable_ios, = IO.select(
|
|
169
|
-
|
|
170
|
-
readable_ios&.each do |io|
|
|
171
|
-
worker = workers_by_io.fetch(io)
|
|
172
|
-
handle_worker_message(worker)
|
|
173
|
-
end
|
|
174
|
-
|
|
167
|
+
until @worker_processes.empty?
|
|
168
|
+
worker_processes_by_io = @worker_processes.values.to_h { |w| [w.socket.io, w] }
|
|
169
|
+
readable_ios, = IO.select(worker_processes_by_io.keys, nil, nil, WORKER_POLL_INTERVAL)
|
|
170
|
+
readable_ios&.each { |io| handle_worker_message(worker_processes_by_io.fetch(io)) }
|
|
175
171
|
reap_workers
|
|
176
172
|
end
|
|
177
173
|
end
|
|
178
174
|
|
|
179
|
-
def handle_worker_message(
|
|
180
|
-
message =
|
|
175
|
+
def handle_worker_message(worker_process)
|
|
176
|
+
message = worker_process.socket.receive_message
|
|
181
177
|
return unless message
|
|
182
178
|
|
|
183
|
-
debug "Worker #{
|
|
179
|
+
debug "Worker #{worker_process.number}: #{message[:type]}"
|
|
184
180
|
|
|
185
181
|
case message[:type].to_sym
|
|
186
182
|
when :example_passed
|
|
187
|
-
@results
|
|
183
|
+
@results.example_passed
|
|
188
184
|
when :example_failed
|
|
189
|
-
@results
|
|
190
|
-
@results[:errors] << message
|
|
185
|
+
@results.example_failed(message)
|
|
191
186
|
|
|
192
|
-
if @fail_fast_after && @results
|
|
193
|
-
debug "Shutting after #{@results
|
|
187
|
+
if @fail_fast_after && @results.failed >= @fail_fast_after
|
|
188
|
+
debug "Shutting after #{@results.failed} failures"
|
|
194
189
|
initiate_shutdown
|
|
195
190
|
end
|
|
196
191
|
when :example_pending
|
|
197
|
-
@results
|
|
192
|
+
@results.example_pending
|
|
198
193
|
when :example_retried
|
|
199
194
|
if @display_retry_backtraces
|
|
200
195
|
puts "\nExample #{message[:description]} retried:\n #{message[:location]}\n #{message[:exception_class]}: #{message[:message]}\n#{message[:backtrace].map { " #{_1}" }.join("\n")}\n"
|
|
201
196
|
end
|
|
202
197
|
when :spec_complete
|
|
203
|
-
@results
|
|
204
|
-
|
|
205
|
-
assign_work(
|
|
198
|
+
@results.spec_file_complete
|
|
199
|
+
worker_process.current_spec = nil
|
|
200
|
+
assign_work(worker_process)
|
|
206
201
|
when :spec_error
|
|
207
|
-
@results
|
|
202
|
+
@results.spec_file_error(message)
|
|
208
203
|
debug "Spec error details: #{message[:error]}"
|
|
209
|
-
|
|
210
|
-
assign_work(
|
|
204
|
+
worker_process.current_spec = nil
|
|
205
|
+
assign_work(worker_process)
|
|
211
206
|
when :spec_interrupted
|
|
212
207
|
debug "Spec interrupted: #{message[:file]}"
|
|
213
|
-
|
|
208
|
+
worker_process.current_spec = nil
|
|
214
209
|
end
|
|
215
|
-
@formatter.handle_worker_message(
|
|
210
|
+
@formatter.handle_worker_message(worker_process, message, @results)
|
|
216
211
|
end
|
|
217
212
|
|
|
218
|
-
def assign_work(
|
|
219
|
-
if @spec_queue.empty? || @shutting_down
|
|
220
|
-
debug "No more work for worker #{
|
|
221
|
-
|
|
222
|
-
|
|
213
|
+
def assign_work(worker_process)
|
|
214
|
+
if @spec_queue.empty? || @results.shutting_down?
|
|
215
|
+
debug "No more work for worker #{worker_process.number}, sending shutdown"
|
|
216
|
+
worker_process.socket.send_message({ type: :shutdown })
|
|
217
|
+
cleanup_worker_process(worker_process)
|
|
223
218
|
else
|
|
224
|
-
@
|
|
219
|
+
@results.spec_file_assigned
|
|
225
220
|
spec_file = @spec_queue.shift
|
|
226
|
-
|
|
227
|
-
debug "Assigning #{spec_file} to worker #{
|
|
221
|
+
worker_process.current_spec = spec_file
|
|
222
|
+
debug "Assigning #{spec_file} to worker #{worker_process.number}"
|
|
228
223
|
message = { type: :worker_assigned_spec, file: spec_file }
|
|
229
|
-
|
|
230
|
-
@formatter.handle_worker_message(
|
|
224
|
+
worker_process.socket.send_message(message)
|
|
225
|
+
@formatter.handle_worker_message(worker_process, message, @results)
|
|
231
226
|
end
|
|
232
227
|
end
|
|
233
228
|
|
|
234
|
-
def
|
|
235
|
-
@
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
@formatter.handle_worker_message(
|
|
239
|
-
Process.wait(
|
|
229
|
+
def cleanup_worker_process(worker_process, status: :shut_down)
|
|
230
|
+
@worker_processes.delete(worker_process.pid)
|
|
231
|
+
worker_process.socket.close
|
|
232
|
+
worker_process.status = status
|
|
233
|
+
@formatter.handle_worker_message(worker_process, { type: :worker_shut_down }, @results)
|
|
234
|
+
Process.wait(worker_process.pid)
|
|
240
235
|
rescue Errno::ECHILD
|
|
241
236
|
nil
|
|
242
237
|
end
|
|
243
238
|
|
|
244
239
|
def reap_workers
|
|
245
|
-
|
|
240
|
+
dead_worker_processes = @worker_processes.each_with_object([]) do |(pid, worker), memo|
|
|
246
241
|
result = Process.waitpid(pid, Process::WNOHANG)
|
|
247
242
|
memo << [worker, $CHILD_STATUS] if result
|
|
248
243
|
end
|
|
249
244
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
245
|
+
dead_worker_processes.each do |worker_process, exitstatus|
|
|
246
|
+
cleanup_worker_process(worker_process, status: :terminated)
|
|
247
|
+
@results.worker_crashed
|
|
248
|
+
debug "Worker #{worker_process.number} exited with status #{exitstatus.exitstatus}, signal #{exitstatus.termsig}"
|
|
253
249
|
end
|
|
254
250
|
rescue Errno::ECHILD
|
|
255
251
|
nil
|
|
@@ -257,12 +253,13 @@ module RSpec
|
|
|
257
253
|
|
|
258
254
|
def print_summary
|
|
259
255
|
puts "\n\n"
|
|
260
|
-
puts "
|
|
261
|
-
puts "
|
|
256
|
+
puts "Randomized with seed #{@seed}"
|
|
257
|
+
puts "#{colorize("#{@results.passed} passed", :green)}, #{colorize("#{@results.failed} failed", :red)}, #{colorize("#{@results.pending} pending", :yellow)}"
|
|
258
|
+
puts colorize("Worker crashes: #{@results.worker_crashes}", :red) if @results.worker_crashes.positive?
|
|
262
259
|
|
|
263
|
-
if @results
|
|
260
|
+
if @results.errors.any?
|
|
264
261
|
puts "\nFailures:\n\n"
|
|
265
|
-
@results
|
|
262
|
+
@results.errors.each_with_index do |error, i|
|
|
266
263
|
puts " #{i + 1}) #{error[:description]}"
|
|
267
264
|
puts " #{error[:location]}"
|
|
268
265
|
puts " #{error[:message]}" if error[:message]
|
|
@@ -274,14 +271,17 @@ module RSpec
|
|
|
274
271
|
end
|
|
275
272
|
end
|
|
276
273
|
|
|
277
|
-
puts "
|
|
278
|
-
puts "
|
|
279
|
-
puts "
|
|
274
|
+
puts "Specs took: #{@results.specs_runtime.round(2)}s"
|
|
275
|
+
puts "Total runtime: #{@results.total_runtime.round(2)}s"
|
|
276
|
+
puts "Suite: #{@results.success? ? colorize("PASSED", :green) : colorize("FAILED", :red)}"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def colorize(string, color)
|
|
280
|
+
$stdout.tty? ? ANSI.colorize(string, color) : string
|
|
280
281
|
end
|
|
281
282
|
|
|
282
283
|
def exit_with_status
|
|
283
|
-
|
|
284
|
-
Kernel.exit(success ? 0 : 1)
|
|
284
|
+
Kernel.exit(@results.success? ? 0 : 1)
|
|
285
285
|
end
|
|
286
286
|
|
|
287
287
|
def debug(message)
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
# doing what we can here
|
|
5
|
-
class RSpec::Core::Configuration
|
|
6
|
-
def __run_before_suite_hooks
|
|
7
|
-
RSpec.current_scope = :before_suite_hook if RSpec.respond_to?(:current_scope=)
|
|
8
|
-
run_suite_hooks("a `before(:suite)` hook", @before_suite_hooks) if respond_to?(:run_suite_hooks)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def __run_after_suite_hooks
|
|
12
|
-
RSpec.current_scope = :after_suite_hook if RSpec.respond_to?(:current_scope=)
|
|
13
|
-
run_suite_hooks("an `after(:suite)` hook", @after_suite_hooks) if respond_to?(:run_suite_hooks)
|
|
14
|
-
end
|
|
15
|
-
end
|
|
3
|
+
require_relative 'ext/rspec'
|
|
16
4
|
|
|
17
5
|
module RSpec
|
|
18
6
|
module Conductor
|
|
@@ -120,7 +108,10 @@ module RSpec
|
|
|
120
108
|
RSpec.world.reset
|
|
121
109
|
RSpec.configuration.reset_reporter
|
|
122
110
|
RSpec.configuration.files_or_directories_to_run = []
|
|
123
|
-
|
|
111
|
+
RSpec.configuration.output_stream = null_io_out
|
|
112
|
+
RSpec.configuration.error_stream = null_io_out
|
|
113
|
+
RSpec.configuration.formatter_loader.formatters.clear
|
|
114
|
+
RSpec.configuration.add_formatter(RSpecSubscriber.new(@socket, file, -> { check_for_shutdown }))
|
|
124
115
|
|
|
125
116
|
begin
|
|
126
117
|
debug "Loading spec file: #{file}"
|
|
@@ -172,13 +163,6 @@ module RSpec
|
|
|
172
163
|
@parsed_options ||= RSpec::Core::ConfigurationOptions.new(@rspec_args)
|
|
173
164
|
end
|
|
174
165
|
|
|
175
|
-
def setup_formatter(conductor_formatter)
|
|
176
|
-
RSpec.configuration.output_stream = null_io_out
|
|
177
|
-
RSpec.configuration.error_stream = null_io_out
|
|
178
|
-
RSpec.configuration.formatter_loader.formatters.clear
|
|
179
|
-
RSpec.configuration.add_formatter(conductor_formatter)
|
|
180
|
-
end
|
|
181
|
-
|
|
182
166
|
def debug(message)
|
|
183
167
|
$stderr.puts "[worker #{@worker_number}] #{message}"
|
|
184
168
|
end
|
|
@@ -191,73 +175,5 @@ module RSpec
|
|
|
191
175
|
@null_io_in ||= File.open(File::NULL, "r")
|
|
192
176
|
end
|
|
193
177
|
end
|
|
194
|
-
|
|
195
|
-
class ConductorFormatter
|
|
196
|
-
RSpec::Core::Formatters.register self,
|
|
197
|
-
:example_passed,
|
|
198
|
-
:example_failed,
|
|
199
|
-
:example_pending
|
|
200
|
-
|
|
201
|
-
def initialize(socket, file, shutdown_check)
|
|
202
|
-
@socket = socket
|
|
203
|
-
@file = file
|
|
204
|
-
@shutdown_check = shutdown_check
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def example_passed(notification)
|
|
208
|
-
@socket.send_message(
|
|
209
|
-
type: :example_passed,
|
|
210
|
-
file: @file,
|
|
211
|
-
description: notification.example.full_description,
|
|
212
|
-
location: notification.example.location,
|
|
213
|
-
run_time: notification.example.execution_result.run_time
|
|
214
|
-
)
|
|
215
|
-
@shutdown_check.call
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
def example_failed(notification)
|
|
219
|
-
ex = notification.example
|
|
220
|
-
@socket.send_message(
|
|
221
|
-
type: :example_failed,
|
|
222
|
-
file: @file,
|
|
223
|
-
description: ex.full_description,
|
|
224
|
-
location: ex.location,
|
|
225
|
-
run_time: ex.execution_result.run_time,
|
|
226
|
-
exception_class: ex.execution_result.exception&.class&.name,
|
|
227
|
-
message: ex.execution_result.exception&.message,
|
|
228
|
-
backtrace: format_backtrace(ex.execution_result.exception&.backtrace, ex.metadata)
|
|
229
|
-
)
|
|
230
|
-
@shutdown_check.call
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
def example_pending(notification)
|
|
234
|
-
ex = notification.example
|
|
235
|
-
@socket.send_message(
|
|
236
|
-
type: :example_pending,
|
|
237
|
-
file: @file,
|
|
238
|
-
description: ex.full_description,
|
|
239
|
-
location: ex.location,
|
|
240
|
-
pending_message: ex.execution_result.pending_message
|
|
241
|
-
)
|
|
242
|
-
@shutdown_check.call
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
def retry(ex)
|
|
246
|
-
@socket.send_message(
|
|
247
|
-
type: :example_retried,
|
|
248
|
-
description: ex.full_description,
|
|
249
|
-
location: ex.location,
|
|
250
|
-
exception_class: ex.exception&.class&.name,
|
|
251
|
-
message: ex.exception&.message,
|
|
252
|
-
backtrace: format_backtrace(ex.exception&.backtrace, ex.metadata)
|
|
253
|
-
)
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
private
|
|
257
|
-
|
|
258
|
-
def format_backtrace(backtrace, example_metadata = nil)
|
|
259
|
-
RSpec::Core::BacktraceFormatter.new.format_backtrace(backtrace || [], example_metadata || {})
|
|
260
|
-
end
|
|
261
|
-
end
|
|
262
178
|
end
|
|
263
179
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module RSpec
|
|
2
|
+
module Conductor
|
|
3
|
+
WorkerProcess = Struct.new(:pid, :number, :status, :socket, :current_spec, keyword_init: true) do
|
|
4
|
+
def hash
|
|
5
|
+
[number].hash
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def eql?(other)
|
|
9
|
+
other.is_a?(self.class) && other.number == number
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/rspec/conductor.rb
CHANGED
|
@@ -6,7 +6,11 @@ require_relative "conductor/version"
|
|
|
6
6
|
require_relative "conductor/protocol"
|
|
7
7
|
require_relative "conductor/server"
|
|
8
8
|
require_relative "conductor/worker"
|
|
9
|
+
require_relative "conductor/results"
|
|
10
|
+
require_relative "conductor/worker_process"
|
|
9
11
|
require_relative "conductor/cli"
|
|
12
|
+
require_relative "conductor/ansi"
|
|
13
|
+
require_relative "conductor/rspec_subscriber"
|
|
10
14
|
require_relative "conductor/formatters/plain"
|
|
11
15
|
require_relative "conductor/formatters/ci"
|
|
12
16
|
require_relative "conductor/formatters/fancy"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mark Abramov
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rspec-core
|
|
@@ -40,14 +40,19 @@ files:
|
|
|
40
40
|
- Rakefile
|
|
41
41
|
- exe/rspec-conductor
|
|
42
42
|
- lib/rspec/conductor.rb
|
|
43
|
+
- lib/rspec/conductor/ansi.rb
|
|
43
44
|
- lib/rspec/conductor/cli.rb
|
|
45
|
+
- lib/rspec/conductor/ext/rspec.rb
|
|
44
46
|
- lib/rspec/conductor/formatters/ci.rb
|
|
45
47
|
- lib/rspec/conductor/formatters/fancy.rb
|
|
46
48
|
- lib/rspec/conductor/formatters/plain.rb
|
|
47
49
|
- lib/rspec/conductor/protocol.rb
|
|
50
|
+
- lib/rspec/conductor/results.rb
|
|
51
|
+
- lib/rspec/conductor/rspec_subscriber.rb
|
|
48
52
|
- lib/rspec/conductor/server.rb
|
|
49
53
|
- lib/rspec/conductor/version.rb
|
|
50
54
|
- lib/rspec/conductor/worker.rb
|
|
55
|
+
- lib/rspec/conductor/worker_process.rb
|
|
51
56
|
- rspec-conductor.gemspec
|
|
52
57
|
homepage: https://github.com/markiz/rspec-conductor
|
|
53
58
|
licenses:
|