ci-queue 0.67.0 → 0.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/ci/queue/redis/worker.rb +23 -14
- data/lib/ci/queue/static.rb +9 -4
- data/lib/ci/queue/version.rb +1 -1
- data/lib/minitest/queue/build_status_reporter.rb +15 -1
- data/lib/minitest/queue/runner.rb +5 -6
- data/lib/minitest/queue.rb +74 -55
- data/lib/rspec/queue.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dfe678bd81982a8945fff3610d86021f2b32ab96f19cd00bbda28c7837374f0c
|
4
|
+
data.tar.gz: f71d4a43aa80012a863f3ec0c6fccebd3542dd1c92e4cc386ffe1bda80836254
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7af733cedec42252b31b2851cdea9067d5dd3974e9549595c492edbacb366125a4655c0c3d7dccb817eba6ab4b8472e0f35c98bbc4bfeb86f6033f5ffd42d93b
|
7
|
+
data.tar.gz: 2ca6d5bf9b6b2d59c3907656afb536c84fb360eee42aee63c3e48136a7b880ef3c16584b50a7bcb0877917d0a62f1dc6f3a13d31277f1c6f7ec20718efd8c5a6
|
data/Gemfile.lock
CHANGED
@@ -7,14 +7,16 @@ module CI
|
|
7
7
|
module Redis
|
8
8
|
class << self
|
9
9
|
attr_accessor :requeue_offset
|
10
|
+
attr_accessor :max_sleep_time
|
10
11
|
end
|
11
12
|
self.requeue_offset = 42
|
13
|
+
self.max_sleep_time = 2
|
12
14
|
|
13
15
|
class Worker < Base
|
14
16
|
attr_reader :total
|
15
17
|
|
16
18
|
def initialize(redis, config)
|
17
|
-
@
|
19
|
+
@reserved_tests = Set.new
|
18
20
|
@shutdown_required = false
|
19
21
|
super(redis, config)
|
20
22
|
end
|
@@ -46,13 +48,21 @@ module CI
|
|
46
48
|
@master
|
47
49
|
end
|
48
50
|
|
51
|
+
DEFAULT_SLEEP_SECONDS = 0.5
|
52
|
+
|
49
53
|
def poll
|
50
54
|
wait_for_master
|
55
|
+
attempt = 0
|
51
56
|
until shutdown_required? || config.circuit_breakers.any?(&:open?) || exhausted? || max_test_failed?
|
52
57
|
if test = reserve
|
58
|
+
attempt = 0
|
53
59
|
yield index.fetch(test)
|
54
60
|
else
|
55
|
-
|
61
|
+
# Adding exponential backoff to avoid hammering Redis
|
62
|
+
# we just stay online here in case a test gets retried or times out so we can afford to wait
|
63
|
+
sleep_time = [DEFAULT_SLEEP_SECONDS * (2 ** attempt), Redis.max_sleep_time].min
|
64
|
+
attempt += 1
|
65
|
+
sleep sleep_time
|
56
66
|
end
|
57
67
|
end
|
58
68
|
redis.pipelined do |pipeline|
|
@@ -125,7 +135,7 @@ module CI
|
|
125
135
|
argv: [config.max_requeues, global_max_requeues, test_key, offset],
|
126
136
|
) == 1
|
127
137
|
|
128
|
-
|
138
|
+
reserved_tests << test_key unless requeued
|
129
139
|
requeued
|
130
140
|
end
|
131
141
|
|
@@ -142,25 +152,24 @@ module CI
|
|
142
152
|
|
143
153
|
attr_reader :index
|
144
154
|
|
155
|
+
def reserved_tests
|
156
|
+
@reserved_tests ||= Set.new
|
157
|
+
end
|
158
|
+
|
145
159
|
def worker_id
|
146
160
|
config.worker_id
|
147
161
|
end
|
148
162
|
|
149
|
-
def raise_on_mismatching_test(
|
150
|
-
|
151
|
-
|
152
|
-
else
|
153
|
-
raise ReservationError, "Acknowledged #{test_key.inspect} but #{@reserved_test.inspect} was reserved"
|
163
|
+
def raise_on_mismatching_test(test)
|
164
|
+
unless reserved_tests.delete?(test)
|
165
|
+
raise ReservationError, "Acknowledged #{test.inspect} but only #{reserved_tests.map(&:inspect).join(", ")} reserved"
|
154
166
|
end
|
155
167
|
end
|
156
168
|
|
157
169
|
def reserve
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
end
|
162
|
-
|
163
|
-
@reserved_test = (try_to_reserve_lost_test || try_to_reserve_test)
|
170
|
+
test = (try_to_reserve_lost_test || try_to_reserve_test)
|
171
|
+
reserved_tests << test
|
172
|
+
test
|
164
173
|
end
|
165
174
|
|
166
175
|
def try_to_reserve_test
|
data/lib/ci/queue/static.rb
CHANGED
@@ -89,14 +89,15 @@ module CI
|
|
89
89
|
end
|
90
90
|
|
91
91
|
def running
|
92
|
-
|
92
|
+
reserved_tests.empty? ? 0 : 1
|
93
93
|
end
|
94
94
|
|
95
95
|
def poll
|
96
|
-
while !@shutdown && config.circuit_breakers.none?(&:open?) && !max_test_failed? &&
|
97
|
-
|
96
|
+
while !@shutdown && config.circuit_breakers.none?(&:open?) && !max_test_failed? && reserved_test = @queue.shift
|
97
|
+
reserved_tests << reserved_test
|
98
|
+
yield index.fetch(reserved_test)
|
98
99
|
end
|
99
|
-
|
100
|
+
reserved_tests.clear
|
100
101
|
end
|
101
102
|
|
102
103
|
def exhausted?
|
@@ -142,6 +143,10 @@ module CI
|
|
142
143
|
def requeues
|
143
144
|
@requeues ||= Hash.new(0)
|
144
145
|
end
|
146
|
+
|
147
|
+
def reserved_tests
|
148
|
+
@reserved_tests ||= Set.new
|
149
|
+
end
|
145
150
|
end
|
146
151
|
end
|
147
152
|
end
|
data/lib/ci/queue/version.rb
CHANGED
@@ -108,7 +108,16 @@ module Minitest
|
|
108
108
|
build.requeued_tests
|
109
109
|
end
|
110
110
|
|
111
|
+
APPLICATION_ERROR_EXIT_CODE = 42
|
112
|
+
TIMED_OUT_EXIT_CODE = 43
|
113
|
+
TOO_MANY_FAILED_TESTS_EXIT_CODE = 44
|
114
|
+
WORKERS_DIED_EXIT_CODE = 45
|
115
|
+
SUCCESS_EXIT_CODE = 0
|
116
|
+
TEST_FAILURE_EXIT_CODE = 1
|
117
|
+
|
111
118
|
def report
|
119
|
+
exit_code = TEST_FAILURE_EXIT_CODE
|
120
|
+
|
112
121
|
if requeued_tests.to_a.any?
|
113
122
|
step("Requeued #{requeued_tests.size} tests")
|
114
123
|
requeued_tests.to_a.sort.each do |test_id, count|
|
@@ -131,10 +140,14 @@ module Minitest
|
|
131
140
|
if remaining_tests.size > 10
|
132
141
|
puts " ..."
|
133
142
|
end
|
143
|
+
|
144
|
+
exit_code = TIMED_OUT_EXIT_CODE
|
134
145
|
elsif supervisor.time_left_with_no_workers.to_i <= 0
|
135
146
|
puts red("All workers died.")
|
147
|
+
exit_code = WORKERS_DIED_EXIT_CODE
|
136
148
|
elsif supervisor.max_test_failed?
|
137
149
|
puts red("Encountered too many failed tests. Test run was ended early.")
|
150
|
+
exit_code = TOO_MANY_FAILED_TESTS_EXIT_CODE
|
138
151
|
end
|
139
152
|
|
140
153
|
puts
|
@@ -146,9 +159,10 @@ module Minitest
|
|
146
159
|
puts red("Worker #{worker_id } crashed")
|
147
160
|
puts error
|
148
161
|
puts ""
|
162
|
+
exit_code = APPLICATION_ERROR_EXIT_CODE
|
149
163
|
end
|
150
164
|
|
151
|
-
success?
|
165
|
+
success? ? SUCCESS_EXIT_CODE : exit_code
|
152
166
|
end
|
153
167
|
|
154
168
|
def success?
|
@@ -253,25 +253,24 @@ module Minitest
|
|
253
253
|
|
254
254
|
unless supervisor.wait_for_workers { display_warnings(supervisor.build) }
|
255
255
|
unless supervisor.queue_initialized?
|
256
|
-
abort! "No
|
256
|
+
abort! "No leader was elected. This typically means no worker was able to start. Were there any errors during application boot?", 40
|
257
257
|
end
|
258
258
|
|
259
259
|
unless supervisor.exhausted?
|
260
260
|
reporter = BuildStatusReporter.new(supervisor: supervisor)
|
261
|
-
reporter.report
|
261
|
+
exit_code = reporter.report
|
262
262
|
reporter.write_failure_file(queue_config.failure_file) if queue_config.failure_file
|
263
263
|
reporter.write_flaky_tests_file(queue_config.export_flaky_tests_file) if queue_config.export_flaky_tests_file
|
264
264
|
|
265
|
-
abort!("#{supervisor.size} tests weren't run.")
|
265
|
+
abort!("#{supervisor.size} tests weren't run.", exit_code)
|
266
266
|
end
|
267
267
|
end
|
268
268
|
|
269
269
|
reporter = BuildStatusReporter.new(supervisor: supervisor)
|
270
270
|
reporter.write_failure_file(queue_config.failure_file) if queue_config.failure_file
|
271
271
|
reporter.write_flaky_tests_file(queue_config.export_flaky_tests_file) if queue_config.export_flaky_tests_file
|
272
|
-
reporter.report
|
273
|
-
|
274
|
-
exit! reporter.success? ? 0 : 1
|
272
|
+
exit_code = reporter.report
|
273
|
+
exit! exit_code
|
275
274
|
end
|
276
275
|
|
277
276
|
def report_grind_command
|
data/lib/minitest/queue.rb
CHANGED
@@ -107,7 +107,7 @@ module Minitest
|
|
107
107
|
end
|
108
108
|
|
109
109
|
module Queue
|
110
|
-
|
110
|
+
extend ::CI::Queue::OutputHelpers
|
111
111
|
attr_writer :run_command_formatter, :project_root
|
112
112
|
|
113
113
|
def run_command_formatter
|
@@ -149,8 +149,79 @@ module Minitest
|
|
149
149
|
path
|
150
150
|
end
|
151
151
|
|
152
|
+
class << self
|
153
|
+
def queue
|
154
|
+
Minitest.queue
|
155
|
+
end
|
156
|
+
|
157
|
+
def run(reporter, *)
|
158
|
+
rescue_run_errors do
|
159
|
+
queue.poll do |example|
|
160
|
+
result = queue.with_heartbeat(example.id) do
|
161
|
+
example.run
|
162
|
+
end
|
163
|
+
|
164
|
+
handle_test_result(reporter, example, result)
|
165
|
+
end
|
166
|
+
|
167
|
+
queue.stop_heartbeat!
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def handle_test_result(reporter, example, result)
|
172
|
+
failed = !(result.passed? || result.skipped?)
|
173
|
+
|
174
|
+
if example.flaky?
|
175
|
+
result.mark_as_flaked!
|
176
|
+
failed = false
|
177
|
+
end
|
178
|
+
|
179
|
+
if failed && queue.config.failing_test && queue.config.failing_test != example.id
|
180
|
+
# When we do a bisect, we don't care about the result other than the test we're running the bisect on
|
181
|
+
result.mark_as_flaked!
|
182
|
+
failed = false
|
183
|
+
elsif failed
|
184
|
+
queue.report_failure!
|
185
|
+
else
|
186
|
+
queue.report_success!
|
187
|
+
end
|
188
|
+
|
189
|
+
if failed && CI::Queue.requeueable?(result) && queue.requeue(example)
|
190
|
+
result.requeue!
|
191
|
+
end
|
192
|
+
reporter.record(result)
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def rescue_run_errors(&block)
|
198
|
+
block.call
|
199
|
+
rescue Errno::EPIPE
|
200
|
+
# This happens when the heartbeat process dies
|
201
|
+
reopen_previous_step
|
202
|
+
puts red("The heartbeat process died. This worker is exiting early.")
|
203
|
+
exit!(41)
|
204
|
+
rescue CI::Queue::Error => error
|
205
|
+
reopen_previous_step
|
206
|
+
puts red("#{error.class}: #{error.message}")
|
207
|
+
error.backtrace.each do |frame|
|
208
|
+
puts red(frame)
|
209
|
+
end
|
210
|
+
exit!(41)
|
211
|
+
rescue => error
|
212
|
+
reopen_previous_step
|
213
|
+
Minitest.queue.report_worker_error(error)
|
214
|
+
puts red("This worker exited because of an uncaught application error:")
|
215
|
+
puts red("#{error.class}: #{error.message}")
|
216
|
+
error.backtrace.each do |frame|
|
217
|
+
puts red(frame)
|
218
|
+
end
|
219
|
+
exit!(42)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
152
223
|
class SingleExample
|
153
|
-
attr_reader :method_name
|
224
|
+
attr_reader :runnable, :method_name
|
154
225
|
|
155
226
|
def initialize(runnable, method_name)
|
156
227
|
@runnable = runnable
|
@@ -212,7 +283,7 @@ module Minitest
|
|
212
283
|
|
213
284
|
def __run(*args)
|
214
285
|
if queue
|
215
|
-
|
286
|
+
Queue.run(*args)
|
216
287
|
|
217
288
|
if queue.config.circuit_breakers.any?(&:open?)
|
218
289
|
STDERR.puts queue.config.circuit_breakers.map(&:message).join(' ').strip
|
@@ -225,58 +296,6 @@ module Minitest
|
|
225
296
|
super
|
226
297
|
end
|
227
298
|
end
|
228
|
-
|
229
|
-
def run_from_queue(reporter, *)
|
230
|
-
queue.poll do |example|
|
231
|
-
result = queue.with_heartbeat(example.id) do
|
232
|
-
example.run
|
233
|
-
end
|
234
|
-
|
235
|
-
failed = !(result.passed? || result.skipped?)
|
236
|
-
|
237
|
-
if example.flaky?
|
238
|
-
result.mark_as_flaked!
|
239
|
-
failed = false
|
240
|
-
end
|
241
|
-
|
242
|
-
if failed && queue.config.failing_test && queue.config.failing_test != example.id
|
243
|
-
# When we do a bisect, we don't care about the result other than the test we're running the bisect on
|
244
|
-
result.mark_as_flaked!
|
245
|
-
failed = false
|
246
|
-
elsif failed
|
247
|
-
queue.report_failure!
|
248
|
-
else
|
249
|
-
queue.report_success!
|
250
|
-
end
|
251
|
-
|
252
|
-
if failed && CI::Queue.requeueable?(result) && queue.requeue(example)
|
253
|
-
result.requeue!
|
254
|
-
end
|
255
|
-
reporter.record(result)
|
256
|
-
end
|
257
|
-
queue.stop_heartbeat!
|
258
|
-
rescue Errno::EPIPE
|
259
|
-
# This happens when the heartbeat process dies
|
260
|
-
reopen_previous_step
|
261
|
-
puts red("The heartbeat process died. This worker is exiting early.")
|
262
|
-
exit!(41)
|
263
|
-
rescue CI::Queue::Error => error
|
264
|
-
reopen_previous_step
|
265
|
-
puts red("#{error.class}: #{error.message}")
|
266
|
-
error.backtrace.each do |frame|
|
267
|
-
puts red(frame)
|
268
|
-
end
|
269
|
-
exit!(41)
|
270
|
-
rescue => error
|
271
|
-
reopen_previous_step
|
272
|
-
queue.report_worker_error(error)
|
273
|
-
puts red("This worker exited because of an uncaught application error:")
|
274
|
-
puts red("#{error.class}: #{error.message}")
|
275
|
-
error.backtrace.each do |frame|
|
276
|
-
puts red(frame)
|
277
|
-
end
|
278
|
-
exit!(42)
|
279
|
-
end
|
280
299
|
end
|
281
300
|
end
|
282
301
|
|
data/lib/rspec/queue.rb
CHANGED
@@ -283,7 +283,7 @@ module RSpec
|
|
283
283
|
|
284
284
|
unless supervisor.wait_for_workers
|
285
285
|
unless supervisor.queue_initialized?
|
286
|
-
abort! "No
|
286
|
+
abort! "No leader was elected. This typically means no worker was able to start. Were there any errors during application boot?"
|
287
287
|
end
|
288
288
|
|
289
289
|
unless supervisor.exhausted?
|