Repository navigation
TUI lib tests share process-global state, so they only pass serially #596
Description
Follow-up to #592, which enabled the TUI library tests in CI. That gate has to run with --test-threads=1 because the suite is not parallel-safe.
Evidence
cargo test -p jcode-tui --lib -- --test-threads=1→ 1977 passed, 0 failed.- Default (parallel) → 2-4 failures per run, a different set each run, and every failing test passes when run alone.
Observed failing across runs: overscroll_reveal_does_not_relayout_transcript, test_remote_mixed_catalog_keeps_jcode_subscription_separate_from_other_providers, test_tui_cerebras_paste_key_lifecycle_has_no_degraded_success_messages, test_save_and_restore_reload_state_preserves_split_view_mode, test_tui_openai_compatible_empty_catalog_does_not_switch_to_profile_default, test_changelog_overlay_repeated_renders_are_stable, gather_ambient_info_filters_to_session_reminders_when_ambient_disabled.
Contended state
- the process-global model catalog / route memo
AMBIENT_INFO_CACHE, whose stale-while-revalidate path spawns a background thread that reloads under whateverJCODE_HOMEis current when it runs, not when it was queued- render state (partly addressed already by the shared
render_state_test_lock) JCODE_HOMEitself, when a test sets it without holding the shared env lock
helpers_tests.rs already documents this: an "unrelated in-flight cache refresh can otherwise overwrite the cleared process-global cache with data loaded under another test's JCODE_HOME".
What worked in jcode-base
Two tests with exactly this shape were fixed by scoping, not redesign:
- wrap the body in
with_clean_provider_test_env - hold the shared test-env lock plus a private
JCODE_HOME(matching the neighbouring test that already did)
cargo test -p jcode-base went from 1-2 intermittent failures per run to 1130 passing across three consecutive runs.
Why it is worth doing
- Ordering-dependent tests hide real regressions: a genuine break looks like the usual flake.
- Serial costs ~40s in CI where parallel would be ~15s.
- The
--skiplist in the CI step is blunt. Sixcopy_badge_shortcuttests are skipped because they drive a real clipboard; if they were injectable behind a clipboard trait they could run headlessly instead of being excluded.
Suggested order
- Scope the catalog/ambient tests the way the
jcode-baseones were. - Make the ambient cache refresh capture its
JCODE_HOMEat queue time, or skip the background path entirely undercfg(test). - Abstract the clipboard so the copy-badge tests can drop out of the CI skip list.
- Only then drop
--test-threads=1.
Activity
test(tui): capture clipboard writes so copy tests pass headless
1jehuang commentedon Jul 27, 2026
Labeled
tech-debt/triage: reproducibleand cross-linked with #595, which covers the same parallel-isolation problem and already has measurements: full serialization costs about 14 seconds, and the mechanical "blanket the lock" approach self-deadlocks becauserender_state_test_lockis non-reentrant and several shared helpers already acquire it.No automated fix attempted: the suggested order here (scope the catalog/ambient tests, make the ambient cache capture
JCODE_HOMEat queue time, abstract the clipboard, then drop--test-threads=1) is a test-architecture change, and step 3 in particular needs a decision on the clipboard trait boundary.--- — Jcode agent (automated triage), on behalf of @1jehuang
test(tui): capture clipboard writes so copy tests pass headless
test(tui): cover the auto-poke session default that 1jehuang#664's fi…
test(tui): cover the auto-poke session default that 1jehuang#664's fi…
test(tui): capture clipboard writes so copy tests pass headless
test(tui): cover the auto-poke session default that 1jehuang#664's fi…
test(tui): cover the auto-poke session default that 1jehuang#664's fi…
test(tui): cover the auto-poke session default that 1jehuang#664's fi…
test(tui): capture clipboard writes so copy tests pass headless
test(tui): cover the auto-poke session default that 1jehuang#664's fi…