test(host/audio): prove a reopen recovered with a live uplink, not one frame

`reopens_after_push_death` failed about one run in nine, and widening its
timeout did not help — the earlier commit blamed the backoff and was wrong.

The pump drops whatever queued while it was down: audio from before the
device came back is stale, so a fresh instance drains the channel right
after opening. The harness counts `opens` from the START of the open, so
the moment the test sees the counter move, the pump has not reached that
drain yet. The single frame it then sent landed inside the drain window and
was discarded exactly as designed, leaving the test waiting for audio that
was never going to arrive.

So the test now keeps feeding, which is what a real uplink does and what
the drain assumes. The sequence advances each time or the de-jitter reads
the repeats as duplicates and drops them for a second, correct reason.

Production behaviour is unchanged: this was the test asserting something
the pump never promised.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 00:41:07 +02:00
co-authored by Claude Fable 5
parent 23f1debe69
commit f3a39df7b3
+22 -5
View File
@@ -496,9 +496,6 @@ mod pump_tests {
start_tuned(fail_first, false, Duration::ZERO)
}
/// The budget has to clear `backoff_start` (2 s) with room to spare: a reopen test that
/// waits exactly as long as the backoff it is waiting for fails whenever the machine is
/// busy or the binary is cold, which is precisely what CI is.
fn wait_until(what: &str, mut cond: impl FnMut() -> bool) {
for _ in 0..600 {
if cond() {
@@ -509,6 +506,27 @@ mod pump_tests {
panic!("timed out waiting for: {what}");
}
/// Keep a live uplink running until audio reaches the backend.
///
/// A single frame is NOT enough to prove a reopen recovered: a freshly opened backend
/// deliberately drops whatever queued while it was down (the `try_recv` drain after the
/// open — that audio predates the device), and `opens` counts from the START of the open,
/// so a frame sent the moment the counter moves lands inside that drain and is discarded
/// by design. A real uplink keeps sending, so the test does too. The sequence has to keep
/// advancing or the de-jitter reads the repeats as duplicates and drops them.
fn wait_until_pushed(what: &str, h: &Harness, from_seq: u32) {
let mut seq = from_seq;
for _ in 0..600 {
let _ = h.tx.try_send(mic_frame(seq));
seq = seq.wrapping_add(1);
if h.pushed.load(Ordering::SeqCst) > 0 {
return;
}
std::thread::sleep(Duration::from_millis(10));
}
panic!("timed out waiting for: {what}");
}
fn opus_frame() -> Vec<u8> {
let mut enc = opus::Encoder::new(48_000, opus::Channels::Stereo, opus::Application::Voip)
.expect("opus encoder");
@@ -582,8 +600,7 @@ mod pump_tests {
.store(false, Ordering::Release);
h.tx.send(mic_frame(0)).unwrap(); // push sees death → reopen
wait_until("reopen", || h.opens.load(Ordering::SeqCst) >= 2);
h.tx.send(mic_frame(1)).unwrap();
wait_until("pcm after reopen", || h.pushed.load(Ordering::SeqCst) > 0);
wait_until_pushed("pcm after reopen", &h, 1);
drop(h.tx);
h.join.join().unwrap();
}