Skip to content

Fix flaky TUI diff-pipeline test (await layout_complete instead of fixed-iteration poll)#13676

Draft
warp-dev-github-integration[bot] wants to merge 1 commit into
masterfrom
fix-tui-file-edits-flaky-test
Draft

Fix flaky TUI diff-pipeline test (await layout_complete instead of fixed-iteration poll)#13676
warp-dev-github-integration[bot] wants to merge 1 commit into
masterfrom
fix-tui-file-edits-flaky-test

Conversation

@warp-dev-github-integration

Copy link
Copy Markdown
Contributor

Description

Fixes the flaky TUI test diff_pipeline_computes_added_lines_and_ghost_blocks (flaked in CI, e.g. on a run for PR #13486), failing with:

assertion `left == right` failed
 left: 0
 right: 1

at the assert_eq!(ghosts.len(), 1) check.

Root cause

After expand_diffs, the removed-line ghost blocks are not stored synchronously. CodeEditorModel::refresh_diff_state calls RenderState::add_temporary_blocks, which only try_sends a LayoutAction::LayoutTemporaryBlock onto the render state's async layout channel. That action is applied by a spawned foreground handler that only runs after a background→foreground hop: App::spawn_stream_local polls layout_rx on the (tokio) background executor and forwards each item to a foreground task that calls handle_layout_actionCharCellState::set_temporary_blocks. Until that handler runs, temporary_blocks is empty, so display_lattice(&[]).ghosts() returns no blocks.

The test waited for the ghosts with a fixed for _ in 0..100 { …; yield_now().await } loop. yield_now only yields on the foreground executor; it does not drive the background thread that must first forward the queued action. Under CI CPU contention the 100 fast foreground yield cycles can elapse before the background thread is scheduled and the foreground handler stores the blocks, so the loop exits with ghosts empty and the assertion fails. This is a timing race, not a logic bug — under load it reproduces ~50% of the time (40 parallel runs of the unmodified test: 20 pass / 20 fail).

Fix

Replace the fixed-iteration poll with the render state's layout_complete() future — the deterministic sync primitive already used by the GUI editor tests (app/src/code/editor/model_tests.rs). submit_layout_action increments an outstanding_layouts counter for every queued action and handle_layout_action decrements it as each is applied; layout_complete() awaits until that counter reaches zero, i.e. until every in-flight layout action — including the ghost-block one — has been applied. It loops without a cap, so it waits however long the background→foreground hop takes instead of racing it.

RenderState::layout_complete() is gated behind the editor crate's test-util feature, so this enables warp_editor/test-util in warp_tui's [dev-dependencies] (mirroring how the app crate wires it). That feature is empty apart from gating test-only sync primitives, so it has no effect on non-test builds.

Linked Issue

N/A — flaky-test fix, not tied to a spec/issue.

Testing

  • Reproduced the flake first: 40 parallel runs of the unmodified test (CPU oversubscription to emulate CI contention) → 20 pass / 20 fail, every failure at tui_file_edits_view_tests.rs:127 with left: 0, right: 1 (empty ghosts).

  • After the fix: 160/160 pass across two rounds of 80 parallel runs under the same load.

  • Full cargo test -p warp_tui --lib suite green (102 passed, 0 failed).

  • cargo clippy -p warp_tui --all-targets --tests -- -D warnings and cargo fmt -p warp_tui -- --check clean.

  • I have manually tested my changes locally with ./script/run
    — N/A: test-only change to a headless TUI unit test; verified via cargo test/cargo clippy/cargo fmt.

Screenshots / Videos

N/A — no UI change.

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode

CHANGELOG-NONE


Conversation: https://staging.warp.dev/conversation/ff08fdaf-28c9-4593-bd89-d0cc11d2de95
Run: https://oz.staging.warp.dev/runs/019f5d5a-48e7-7930-8f52-158e9f67161a
This PR was generated with Oz.

`diff_pipeline_computes_added_lines_and_ghost_blocks` flaked in CI
(e.g. PR #13486) with `assertion left == right failed: left: 0, right: 1`
at the `ghosts.len() == 1` check.

Root cause: after `expand_diffs`, the removed-line ghost blocks are not
stored synchronously. `refresh_diff_state` calls
`RenderState::add_temporary_blocks`, which only `try_send`s a
`LayoutAction::LayoutTemporaryBlock` onto the render state's async layout
channel. That action is applied by a spawned foreground handler that only
runs after a background->foreground hop: `App::spawn_stream_local` polls
`layout_rx` on the (tokio) background executor and forwards each item to a
foreground task that calls `handle_layout_action` -> `set_temporary_blocks`.
Until that handler runs, `CharCellState::temporary_blocks` is empty, so
`display_lattice(&[]).ghosts()` returns no blocks.

The test polled for the ghosts with a fixed `for _ in 0..100 { ...;
yield_now().await }` loop. `yield_now` only yields on the foreground
executor; it does not drive the background thread that must first forward
the queued action. Under CI CPU contention the 100 fast foreground yield
cycles can elapse before the background thread is scheduled and the
foreground handler stores the blocks, so the loop exits with `ghosts` empty
and the assertion fails. This is a timing race, not a logic bug -- under
load it reproduces ~50% of the time (40 parallel runs: 20 pass / 20 fail
on the unmodified test).

Fix: replace the fixed-iteration poll with the render state's
`layout_complete()` future, the deterministic sync primitive already used
by the GUI editor tests (`app/src/code/editor/model_tests.rs`).
`submit_layout_action` increments an `outstanding_layouts` counter for
every queued action and `handle_layout_action` decrements it as each is
applied; `layout_complete()` awaits until that counter reaches zero, i.e.
until every in-flight layout action -- including the ghost-block one -- has
been applied. It loops without a cap, so it waits however long the
background->foreground hop takes instead of racing it.

`layout_complete()` is gated behind the editor crate's `test-util`
feature, so enable `warp_editor/test-util` in `warp_tui`'s
`[dev-dependencies]` (mirroring how the `app` crate wires it). The feature
is empty apart from gating test-only sync primitives.

Verified: 160/160 passes under 2x80 parallel CPU-oversubscribed runs
(versus 20/20 failures at 40 parallel before); full `warp_tui --lib` suite
green (102 tests); `cargo clippy -p warp_tui --all-targets --tests` and
`cargo fmt -p warp_tui --check` clean.

Co-Authored-By: Oz <oz-agent@warp.dev>
@cla-bot cla-bot Bot added the cla-signed label Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant