test(nests-interop): revert hot-swap browser to soft-pass per plan Risk § option (a)
Follow-up to commit `029329af`: a verification sweep (5×) showed
the `pcm.size > warmupSamples` hard floor on
`chromium_listener_speaker_hot_swap_does_not_crash` flakes 3/5 —
empirical capture varies 2880–7680 samples (= 60–160 ms TOTAL),
straddling the 4800-sample warmup threshold. The browser-side
@moq/lite 0.2.x re-attach behaviour across `Active::Ended →
Active` is fundamentally unreliable; ANY hard floor that
excludes the regression mode ("swap killed the WT session
entirely") fail-flakes because the steady state itself sits
right at that threshold.
Per `2026-05-07-tighten-cross-stack-assertions.md`'s Risk § option
(a) — "If any scenario fail-flakes after tightening, [...] revert
the tightening on that scenario" — this commit reverts the
hot-swap floor to a documented soft-pass that prints to
System.err when triggered. T12 (group sequence carry across
hot-swaps) is still asserted by the hang-tier counterpart
(`speaker_hot_swap_does_not_crash` in `HangInteropTest`), which
hard-asserts the full post-swap window decodes the 440 Hz peak.
The deferred "browser hot-swap re-attach" follow-up in
`2026-05-06-cross-stack-interop-test-results.md` captures the
underlying @moq/lite client work that would let this scenario
become a hard-floor test in the future.
This commit is contained in:
@@ -111,7 +111,7 @@ hard floors on both tiers.
|
||||
| **I2 late-join** | `≥ 1.5 s after warmup` | per plan recommendation |
|
||||
| **I3 mute-window** | lower `≥ 2.5 s` + upper `< 5.5 s` | upper bound left at 5.5 s; the plan's 5.0 s tightening tripped 5/5 against empirical 5.1–5.2 s steady state |
|
||||
| **I4 stereo** | `≥ 1 s × 2 ch` | new floor (was vacuous) |
|
||||
| **I5 hot-swap** | `pcm.size > warmupSamples` | weaker than plan's 0.5 s — Chromium's `@moq/lite` 0.2.x captures only ~100–160 ms post-merge (deferred follow-up: "browser hot-swap re-attach" in `2026-05-06-cross-stack-interop-test-results.md`) |
|
||||
| **I5 hot-swap** | **soft-pass kept** | The `pcm.size > warmupSamples` floor flaked 3/5 in a follow-up sweep — empirical samples vary 0–7.7 k right at the warmup threshold. Reverted to a soft-pass with the printed-to-stderr explanation; T12 protection is asserted by the hang-tier counterpart. Tracked as deferred follow-up "browser hot-swap re-attach" in `2026-05-06-cross-stack-interop-test-results.md`. |
|
||||
| **I9 packet-loss** | `≥ 0.5 s after warmup` | per plan recommendation |
|
||||
| **I14 decoder-no-errors** | `decoderOutputs ≥ 4` | per plan recommendation (3 warmup + ≥ 1 audio) |
|
||||
| **Browser-publish baseline** | helper hard-asserts | `runBrowserPublishKotlinListen` no longer System.err-prints + returns; uses caller-supplied floor |
|
||||
|
||||
+36
-26
@@ -485,37 +485,47 @@ class BrowserInteropTest {
|
||||
)
|
||||
val pcm = readFloat32Pcm(out.pcmFile)
|
||||
val warmupSamples = AudioFormat.SAMPLE_RATE_HZ / 10
|
||||
// Hard floor: SOMETHING must arrive past the warmup
|
||||
// window. The 7 s broadcast hot-swaps at T+2.5 s.
|
||||
// SOFT-PASS — kept intentionally on this one scenario.
|
||||
//
|
||||
// Steady-state empirical sample count post-`:quic`-merge
|
||||
// is ~100–160 ms — Chromium's `@moq/lite` 0.2.x client
|
||||
// appears to tear down its catalog/audio subscriptions
|
||||
// when it sees `Announce::Ended → Active` in rapid
|
||||
// succession (T+2.5 s + ~ms swap window) instead of
|
||||
// re-attaching to the new broadcast cycle. Tracked as
|
||||
// a follow-up in
|
||||
// `2026-05-07-cross-stack-interop-test-results.md`'s
|
||||
// "browser hot-swap re-attach" deferred item.
|
||||
// Empirical post-`:quic`-merge sample counts vary from
|
||||
// 0 to ~7.7 k samples (≤ 160 ms TOTAL) across sweeps,
|
||||
// straddling the 4.8 k warmupSamples threshold. The
|
||||
// browser path's hot-swap response is fundamentally
|
||||
// unreliable: Chromium's `@moq/lite` 0.2.x client tears
|
||||
// down its catalog/audio subscriptions when it sees
|
||||
// `Announce::Ended → Active` in rapid succession
|
||||
// (T+2.5 s + ~ms swap window) instead of re-attaching
|
||||
// to the new broadcast cycle. ANY hard floor on
|
||||
// `pcm.size` that excludes the regression mode
|
||||
// ("swap crashed the WT session entirely") fail-flakes
|
||||
// because the steady state itself sits at the warmup
|
||||
// window edge.
|
||||
//
|
||||
// The hang-tier counterpart (`speaker_hot_swap_does_not_crash`
|
||||
// in HangInteropTest) hard-asserts the full post-swap
|
||||
// window decodes the 440 Hz peak — that's the place to
|
||||
// assert T12 didn't regress. Here we only assert the
|
||||
// listener's WT session survived the swap (pcm.size >
|
||||
// warmupSamples means at least one full Opus frame
|
||||
// landed past the warmup), so a regression to "swap
|
||||
// crashes the WT connection entirely" still trips.
|
||||
assertTrue(
|
||||
pcm.size > warmupSamples,
|
||||
"hot-swap captured ${pcm.size} samples (≤ warmupSamples=$warmupSamples) — " +
|
||||
"the swap appears to have killed the listener session entirely.\n" +
|
||||
"playwright stdout:\n${out.stdout}",
|
||||
// window decodes the 440 Hz peak — that's the place
|
||||
// T12 (group sequence carry across hot-swaps)
|
||||
// regressions are caught. The browser tier here only
|
||||
// verifies the harness boots, the publisher hot-swaps
|
||||
// without crashing the test process, and Chromium
|
||||
// doesn't fire a decoder error.
|
||||
//
|
||||
// Tracked in
|
||||
// `2026-05-06-cross-stack-interop-test-results.md`'s
|
||||
// "browser hot-swap re-attach" deferred item. Resolution
|
||||
// requires a fix in `@moq/lite` / `@moq/hang`'s subscribe
|
||||
// re-attach path — out of scope for this branch.
|
||||
if (pcm.size <= warmupSamples) {
|
||||
System.err.println(
|
||||
"Browser hot-swap: captured ${pcm.size} samples (≤ warmupSamples). " +
|
||||
"Soft-pass per documented browser-side @moq/lite re-attach " +
|
||||
"limitation; T12 is asserted by the hang-tier counterpart.",
|
||||
)
|
||||
// FFT assertion is intentionally skipped here. The captured
|
||||
// window is too short post-merge (~60 ms after warmup) for
|
||||
// the FFT to resolve a 440 Hz peak with halfWindowHz=5.
|
||||
// The hang-tier counterpart asserts the FFT.
|
||||
return@runBlocking
|
||||
}
|
||||
// If we DID get past warmup, still skip the FFT — the
|
||||
// post-warmup window may be too short (60 ms typical) to
|
||||
// resolve a 440 Hz peak with halfWindowHz=5.
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user