Fix flaky diff_pipeline_computes_added_lines_and_ghost_blocks TUI test#13680
Draft
warp-dev-github-integration[bot] wants to merge 1 commit into
Draft
Fix flaky diff_pipeline_computes_added_lines_and_ghost_blocks TUI test#13680warp-dev-github-integration[bot] wants to merge 1 commit into
warp-dev-github-integration[bot] wants to merge 1 commit into
Conversation
The test was flaky because its synchronization could not deterministically wait for two background-thread hops: 1. Stale first DiffUpdated: reset_content seeds the diff base and triggers a base==new diff (task A) on the background executor; the subsequent apply_diffs aborts A and spawns the real diff (task B). The abort is best-effort (only effective on the next poll), and A's body has no yield_now (zero diff ops), so A can complete on its first poll before the abort is issued. When that happens A's callback fires a spurious first CodeEditorModelEvent::DiffUpdated for an *empty* diff. The test's oneshot fired on that first event, so expand_diffs ran before task B landed. 2. Background layout hop: ghost blocks are delivered via the render state's async layout channel, which spawn_stream_local polls on a *background* thread and forwards to a foreground task. The test's fixed 100x yield_now ghost poll only drives the foreground executor, so under load it could not reliably wait for the background diff round-trip (task B) plus the background layout hop. Fix (test-only synchronization): - Fire the oneshot only once the diff state actually reflects a change (added_or_changed_lines non-empty), checked inside the subscription callback. This ignores the stale empty DiffUpdated and signals on the real post-edit diff, so expand_diffs runs after task B. - Replace the fixed-iteration ghost poll with RenderState::layout_complete(), which awaits the outstanding_layouts counter until every submitted layout action (including the LayoutTemporaryBlock from expand_diffs) has been processed, deterministically waiting for the background layout hop. - Enable warp_editor/test-util in warp_tui dev-dependencies so layout_complete()/outstanding_layouts are available. Co-Authored-By: Oz <oz-agent@warp.dev>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes the flaky test
diff_pipeline_computes_added_lines_and_ghost_blocksincrates/warp_tui/src/tui_file_edits_view_tests.rs(intermittent CI failures, e.g. on a run for #13486). The fix is test-only synchronization plus one dev-dependency feature; no product code changes.Root cause
The test stacked two races on top of each other:
Stale first
DiffUpdated(abort race).reset_contentseeds the diff base and kicks off abase == newdiff computation (task A), whichDiffModel::compute_diffspawns on the background tokio thread pool (ModelContext::spawnruns futures off the main thread and marshals the callback back to the foreground executor). The subsequentapply_diffsaborts task A and spawns the real diff (task B) — but the abort is best-effort (it only takes effect on the task's next poll), and task A's body completes on its first poll since the identical texts produce no diff ops. When task A wins that race, its foreground callback installs an empty diff status and emits the firstCodeEditorModelEvent::DiffUpdated. The test's oneshot fired on that first event, soexpand_diffsran (and rendered zero temporary blocks) before the real post-edit diff had landed.Bounded foreground poll vs. background layout hop. Ghost blocks are delivered via
RenderState's async layout channel:add_temporary_blocksonly enqueues aLayoutTemporaryBlockaction;App::spawn_stream_localpolls that channel on a background OS thread and forwards items to a foreground task (handle_layout_action) which finally stores the blocks onCharCellState. The test polledghosts()for a fixed 100 ×futures_lite::future::yield_now()iterations — butyield_nowonly drives the single-threaded foreground test executor and places no bound at all on when the background thread gets scheduled. Under CI load, the 100 foreground turns burn through in microseconds while the background diff round-trip (task B) plus the background layout hop are still pending, leavingghosts()empty and failingassert_eq!(ghosts.len(), 1).Reproduction on the base commit confirmed the flake at roughly 6 failures per 30 runs under CPU contention.
Fix
DiffUpdatedsubscription now fires the oneshot only once the installed diff status actually reflects a change (added_or_changed_lines()non-empty). This ignores the spurious empty-diff event from the abortedbase == newcomputation and releases the test only after the real post-edit diff has landed, soexpand_diffsalways runs against the correct diff state.RenderState::layout_complete().await— the existing test utility that waits on theoutstanding_layoutscounter (incremented synchronously when a layout action is submitted, decremented afterhandle_layout_actionprocesses it). This deterministically waits out the background layout hop regardless of OS scheduling, with no sleeps and no arbitrary iteration bounds.crates/warp_tui/Cargo.tomlenableswarp_editor'stest-utilfeature in dev-dependencies, which gateslayout_complete().Together these make the test's synchronization match the pipeline's actual async structure instead of guessing at scheduling, which is why the flake is eliminated rather than just made rarer.
Linked Issue
N/A — CI flake fix.
Testing
cargo test -p warp_tui --lib diff_pipeline_computes_added_lines_and_ghost_blocks: 30/30 consecutive passes.Same test under CPU saturation (4×
yes > /dev/null): 20/20 passes. (Workers' independent validation: 40/40, 30/30 under load, and 25/25 under load; the unfixed test failed ~6/30 under the same conditions.)Full
cargo test -p warp_tui --lib: 102/102 passed../script/format: no changes;cargo clippy -p warp_tui --all-targets -- -D warnings: clean.Automated test coverage (this PR fixes the test itself); not manually testable via
./script/run.Agent Mode
CHANGELOG-NONE
Co-Authored-By: Oz oz-agent@warp.dev
Conversation: https://staging.warp.dev/conversation/90e7d2e2-1f45-4bb5-a8ce-3d79ac277b4c
Run: https://oz.staging.warp.dev/runs/019f5d5a-a061-7720-ba72-ef7af6dafb13
This PR was generated with Oz.