Skip to content

Commit 344a97c

Browse files
committed
Add integration test for heartbeat cap reset between consecutive tests
Adds test_heartbeat_cap_resets_between_tests as an integration test: - Worker 0 runs test_alpha (cap fires but test completes normally), then :reset clears the capped flag and worker 0 picks up test_beta - A thief (worker 1) starts only after test_beta is in `running` so it cannot grab it from the queue; it can only steal if test_beta goes stale - With reset working: test_beta heartbeat ticks until cap, finishes before stale → 0 warnings - With broken reset: test_beta has no heartbeat ticks, goes stale → stolen → 1 warning Also adds the consecutive_capped_tests.rb fixture (test_alpha=2s, test_beta=2.5s) sized for the cap=1s/heartbeat=2s parameter window.
1 parent d950449 commit 344a97c

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# frozen_string_literal: true
2+
require 'test_helper'
3+
4+
CI::Queue::Redis.max_sleep_time = 0.05
5+
6+
# Fixture for test_heartbeat_cap_resets_between_tests.
7+
#
8+
# test_alpha fires the heartbeat cap (sleep 2 > cap 1s) but finishes before going stale
9+
# (sleep 2 < cap 1 + heartbeat 2 = 3s). This sets capped=true in the heartbeat thread.
10+
# After test_alpha, :reset is sent and capped should be false.
11+
#
12+
# test_beta sleeps in the range (heartbeat=2, heartbeat+cap=3):
13+
# - Without reset: no ticks, stale at t_B + 2s, finishes at t_B + 2.5s → STOLEN
14+
# - With reset: ticks until cap at t_B + 1s, stale at t_B + 3s, finishes at t_B + 2.5s → NOT stolen
15+
class ConsecutiveCappedTests < Minitest::Test
16+
def test_alpha
17+
sleep 2
18+
end
19+
20+
def test_beta
21+
sleep 2.5
22+
end
23+
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# frozen_string_literal: true
2+
require 'test_helper'
3+
4+
CI::Queue::Redis.max_sleep_time = 0.05
5+
6+
class TwoLostTests < Minitest::Test
7+
8+
def test_alpha
9+
sleep 3
10+
end
11+
12+
def test_beta
13+
sleep 3
14+
end
15+
16+
end

ruby/test/integration/minitest_redis_test.rb

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,83 @@ def test_heartbeat_cap_doesnt_affect_fast_tests
216216
end
217217
end
218218

219+
def test_heartbeat_cap_resets_between_tests
220+
# Worker 0 is the sole run worker: it processes test_alpha first (cap fires at 1s,
221+
# test completes at 2s — not stolen), then :reset clears capped and it picks up
222+
# test_beta. A thief (worker 1) starts only after test_beta is in `running` so it
223+
# cannot grab it from the queue; it can only steal if test_beta goes stale.
224+
#
225+
# With reset working: test_beta heartbeat ticks until cap at t_B+1s, stale at t_B+3s,
226+
# finishes at t_B+2.5s → NOT stolen → 0 warnings.
227+
# With broken reset: no ticks for test_beta, stale at t_B+2s, finishes at t_B+2.5s
228+
# → stolen by the thief → 1 warning.
229+
_, err = capture_subprocess_io do
230+
t0 = Thread.start do
231+
system(
232+
{ 'BUILDKITE' => '1' },
233+
@exe, 'run',
234+
'--queue', @redis_url,
235+
'--seed', 'foobar',
236+
'--build', '1',
237+
'--worker', '0',
238+
'--timeout', '1',
239+
'--max-requeues', '1',
240+
'--requeue-tolerance', '1',
241+
'--heartbeat', '2',
242+
'--heartbeat-max-test-duration', '1',
243+
'-Itest',
244+
'test/consecutive_capped_tests.rb',
245+
chdir: 'test/fixtures/',
246+
)
247+
end
248+
249+
# Wait for worker 0 to finish test_alpha (2s sleep + up to ~2s startup) and claim
250+
# test_beta. Once test_beta is in `running`, the thief cannot grab it from the queue.
251+
sleep 5
252+
253+
t1 = Thread.start do
254+
system(
255+
{ 'BUILDKITE' => '1' },
256+
@exe, 'run',
257+
'--queue', @redis_url,
258+
'--seed', 'foobar',
259+
'--build', '1',
260+
'--worker', '1',
261+
'--timeout', '1',
262+
'--max-requeues', '1',
263+
'--requeue-tolerance', '1',
264+
'--heartbeat', '2',
265+
'--heartbeat-max-test-duration', '1',
266+
'-Itest',
267+
'test/consecutive_capped_tests.rb',
268+
chdir: 'test/fixtures/',
269+
)
270+
end
271+
272+
[t0, t1].each(&:join)
273+
end
274+
275+
assert_empty filter_deprecation_warnings(err)
276+
277+
Tempfile.open('warnings') do |warnings_file|
278+
out, err = capture_subprocess_io do
279+
system(
280+
@exe, 'report',
281+
'--queue', @redis_url,
282+
'--build', '1',
283+
'--timeout', '1',
284+
'--warnings-file', warnings_file.path,
285+
'--heartbeat',
286+
chdir: 'test/fixtures/',
287+
)
288+
end
289+
290+
assert_empty filter_deprecation_warnings(err)
291+
warnings = warnings_file.read.lines.map { |line| JSON.parse(line) }
292+
assert_equal 0, warnings.size, "No tests should be stolen — heartbeat cap must reset between consecutive tests"
293+
end
294+
end
295+
219296
def test_lazy_loading_streaming
220297
out, err = capture_subprocess_io do
221298
threads = 2.times.map do |i|

0 commit comments

Comments
 (0)