1jehuang / 1jehuang/jcode

jcode-tui render tests share process-global state with almost no locking (parallel-only failures)

Open
#595 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug priority: medium tech-debt triage: reproducible
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

  1. 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.
  2. 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.
  3. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.