From 039d68650cab53501e06fffc81a240148fcf1cbc Mon Sep 17 00:00:00 2001 From: Vyncint Ng <115854244+vyncint@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:20:35 +0700 Subject: [PATCH] fix(tests): make the resize waits one-directional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stress gate caught this on 2.0.0's own change, at 16 threads, iteration 10 of 25 — which is what the gate is for. `resize_relayouts_the_frame` waited for `ready` before resizing, and `ready` is true of every frame. `wait_frame` returns the earliest frame nobody has looked at, so on a loaded runner that first call could hand back the frame the resize had already produced, leaving no later frame for the second wait and a ten-second timeout. termlens said as much: "the application has not completed a repaint since the frame this terminal last returned (2 complete frames in total)". Both waits are now one-directional, which is what makes the test deterministic rather than lucky. 2.0.0 gave the discriminator for free: the chosen line has room for its confidence interval at 110 columns and not at 80, so the interval's absence identifies a pre-resize frame and its presence a post-resize one. Neither predicate can be satisfied by a frame belonging to the other side of the resize. Audited the rest while here. Every other follow-up predicate is already one-directional — `ranking (`, `all refused configurations:`, `measured 11 of` and the scrolled row are each only true after their keypress — and the two bare `wait_frame(ready)` calls are the sole wait on a freshly spawned terminal. Resize was the only place two predicates could match the same frame. Twenty-five local iterations at 16 threads found no flake, which is not proof: the CI failure was at iteration 10. The stress matrix is. Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com> --- crates/launchbound-tui/tests/tui.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/crates/launchbound-tui/tests/tui.rs b/crates/launchbound-tui/tests/tui.rs index f38b9be..1b2ade5 100644 --- a/crates/launchbound-tui/tests/tui.rs +++ b/crates/launchbound-tui/tests/tui.rs @@ -102,11 +102,23 @@ fn overview_at_80x24() { #[test] fn resize_relayouts_the_frame() { let mut t = spawn((80, 24)); - t.wait_frame(ready).expect("the first complete frame"); + // Both waits are one-directional, which is what makes this test + // deterministic rather than a race. The chosen line has room for its + // interval at 110 columns and not at 80, so the interval's *absence* + // identifies a pre-resize frame and its presence a post-resize one. + // + // Waiting on `ready` here instead — a predicate true of every frame — + // is what flaked: it returns the earliest frame nobody has looked at, + // so on a loaded runner the frame it handed back could already be the + // one the resize produced, leaving nothing for the second wait and a + // ten-second timeout. termlens says so in as many words ("has not + // completed a repaint since the frame this terminal last returned"). + t.wait_frame(|s| { + let frame = s.to_string(); + frame.contains("q quit") && !frame.contains("[0.0398, 0.0402]") + }) + .expect("the 80-column frame"); t.resize(110, 32).expect("resize"); - // The wider panel is what the frame is waited on, not a duration: the - // chosen line only has room for its interval at this geometry, so the - // interval's presence *is* the relayout having happened. let frame = t .wait_frame(|s| s.to_string().contains("[0.0398, 0.0402]")) .expect("the relaid-out frame");