jcode-tui render tests share process-global state with almost no locking (parallel-only failures)
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 19.9k
- Forks
- 2.3k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 30
Description
cargo test -p jcode-tui --lib intermittently fails 2 to 6 tests, always a shifting subset. --test-threads=1 is reliably green: 1974 passed / 0 failed. So these are isolation failures, not real regressions.
Cause
Rendering reaches process-global state: render snapshots, scroll metrics, flicker history, prompt positions, and the thread-locals cleared by clear_test_render_state_for_tests in crates/jcode-tui/src/tui/ui.rs. Any two tests that render concurrently can therefore observe each other's state.
I already fixed one layer of this (5b38b2d1a): viewport_snapshot_test_lock and scroll_render_test_lock each defined their own private mutex, so they serialized within each helper but not against each other, which is the same defect as #593. Both now delegate to a single ui::render_state_test_lock(). That took the parallel failure count from 6 down to 2 to 4.
The remaining problem is coverage rather than correctness of the lock. Most rendering tests never take it:
| file | tests | locked |
|---|---|---|
ui_tests/tools.rs |
47 | 0 |
ui_tests/rendering.rs |
25 | 0 |
ui_tests/input_layout.rs |
25 | 0 |
ui_tests/body_cache.rs |
24 | 0 |
ui_tests/prepare.rs |
23 | 0 |
ui_tests/basic/frame_flicker.rs |
17 | 10 |
ui_tests/image_regions.rs |
7 | 0 |
That is roughly 180 rendering tests contending on shared globals with essentially no mutual exclusion. test_changelog_overlay_repeated_renders_are_stable illustrates it: it does hold the shared lock and still fails in parallel, because unlocked renderers mutate the same globals underneath it.
Options
- Blanket the lock. Mechanical: take
render_state_test_lock()at the top of every rendering test. Correct but ~180 edits, and it silently serializes a large slice of the suite, so wall-clock cost goes up and the requirement is easy for future tests to forget. - Make the state injectable. Thread a render-context value through instead of reaching for globals, so tests are hermetic by construction and can run in parallel. Bigger change, removes the class permanently.
- Interim: run this crate single-threaded in CI while option 2 lands.
I deliberately did not do option 1 unilaterally: a change that large to test structure, with a real throughput cost, is a call for whoever owns this area. Option 2 is the same recommendation I made on #593, and the recurrence across two crates suggests the underlying pattern of tests reaching global state is worth addressing directly rather than serializing around each time.
Not affected
jcode-app-core is stable at 1024 passed / 0 failed across 14 consecutive parallel runs after #593.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with render_state_test_lock and clear_test_render_state_for_tests in crates/jcode-tui/src/tui/ui.rs, then inspect the unlocked tests in the listed ui_tests files. Run cargo test -p jcode-tui --lib with parallel test threads to reproduce the failures. Done means the rendering tests are isolated and the crate passes reliably in parallel, without relying on single-threaded execution.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- cli, testing
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100