Skip to content

test(pty): reuse a single shell session across PTY tests - #439

Merged
Ziinc merged 11 commits into
mainfrom
claude/windows-tests-slow-flaky-97pv3q
Sep 13, 2026
Merged

test(pty): reuse a single shell session across PTY tests#439
Ziinc merged 11 commits into
mainfrom
claude/windows-tests-slow-flaky-97pv3q

Conversation

@Ziinc

@Ziinc Ziinc commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Core no longer spawns processes directly, so the PTY integration suite is
now the dominant source of subprocess spawns in CI — and each spawned shell
(PowerShell on Windows) costs multiple seconds under CI contention. Merge
tests that only needed "a working shell" onto one shared session each,
cutting spawns in this file from 17 down to 8:

  • test_session_lifecycle_and_io replaces 7 single-assertion tests (create,
    write, resize, utf8 output x3, set_auto_command, close,
    close-terminates-process).
  • test_echo_suppression_filtering replaces 3 filter-behavior tests, each
    stage resolving its filter state before the next stage runs.
    Tests that need genuinely separate or concurrent processes (multi-session,
    isolation, creation-time params) are left untouched.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Core no longer spawns processes directly, so the PTY integration suite is
now the dominant source of subprocess spawns in CI — and each spawned shell
(PowerShell on Windows) costs multiple seconds under CI contention. Merge
tests that only needed "a working shell" onto one shared session each,
cutting spawns in this file from 17 down to 8:

- test_session_lifecycle_and_io replaces 7 single-assertion tests (create,
  write, resize, utf8 output x3, set_auto_command, close,
  close-terminates-process).
- test_echo_suppression_filtering replaces 3 filter-behavior tests, each
  stage resolving its filter state before the next stage runs.

Tests that need genuinely separate or concurrent processes (multi-session,
isolation, creation-time params) are left untouched.
remote_commit_file/remote_commit_on_branch used to clone the bare remote,
checkout/add/commit/push through 5 git subprocesses per call — real spawns
across ~30 call sites in the test suite. The "remote" in these tests is
always a local bare repo on the same filesystem, so there's no need to
clone at all: write the commit straight into its object database and refs
via gix (new set_path_in_tree + gix_commit_file_on_ref), based on the
branch's current tip if it exists there already.

Also drops the last two non-network git subprocess calls from
with_remote_create: `git remote set-head` only ever writes a local
symbolic ref, so it becomes a gix ref-transaction (set_symbolic_ref,
generalized from the existing set_head_branch), and the bare remote gets
its own git user config so gix's repo.commit() can resolve author/
committer without falling back to global git config.

The two `git push` calls remain as subprocesses — gix 0.81 has no push
support — as does the real `push_branch` push-from-local-repo helper,
which is unrelated to the removed clone-and-push pattern.

Verified with a throwaway test asserting `git fsck` cleanliness and
correct tree/commit structure against the real git binary (not part of
this commit); full jj-backed test run wasn't possible in this sandbox
(no jj binary available), but the 12 unrelated failures observed were all
"jj: No such file or directory", including one from a test that calls
remote_commit_on_branch and only failed on its later jj-dependent step.
Mirrors the earlier git-to-gix migration: TestRepo::run_jj and the direct
Command::new("jj") calls scattered across ~15 test files shelled out to the
jj CLI for operations (new, describe, commit, status, log with custom
templates, sparse list, file list, bookmark set, workspace update-stale/
forget, git init) that mostly already have jj-lib-backed equivalents in
src/jj.rs — reuse those directly instead of a subprocess round trip.

New thin wrappers in e2e_test_helpers.rs (jj_new, jj_describe, jj_commit,
jj_change_id, jj_update_stale, jj_working_copy_is_clean, jj_snapshot,
jj_sparse_patterns, jj_files_at_revision, jj_git_init, jj_set_bookmark,
jj_bookmarks_on_revision, jj_change_ids_in_revset/jj_commit_ids_in_revset,
jj_log_entries/jj_log_descriptions, jj_has_revert_hunk_for_line) delegate to
existing treq_lib::jj functions per call. Three genuinely missing read
accessors were added to src/jj.rs: jj_get_sparse_patterns,
jj_list_files_at_revision, jj_git_init_bare (jj_lib's `jj git init` without
--colocate), plus jj_log_revset_change_ids/jj_log_entries for the log-with-
custom-template call sites and list_all_workspace_names for JjVerifier's
unfiltered `jj workspace list` (list_jj_workspaces deliberately hides
`default`, which some of these tests specifically assert on).

Resolving a bookmark/branch-name revision needs a git-ref import first
(the real `jj` CLI does this on every invocation; raw jj-lib calls don't) —
handled via a best-effort jj_util_import_git_refs call in the wrappers that
take arbitrary revisions/revsets.

TestRepo::run_jj is renamed run_jj_cli and kept for exactly one purpose: a
new #[cfg(unix)] jj_lib_vs_cli_test.rs differential-tests the wrappers above
against the real CLI's own view of a repo, when jj is on PATH (skips
otherwise). Unix-only because CI doesn't reliably have the jj CLI on
Windows runners (see the earlier Windows-test-flakiness investigation).
…ture

file_indexer_test.rs's setup_jj_repo builds a bare, non-colocated jj repo
(no .git directory at all — jj_git_init_bare, i.e. plain jj-lib "jj git
init" without --colocate). TestRepo::jj_commit wraps treq's production
jj_commit, which resolves a git branch as part of treq's normal
colocated-repo flow and fails with "Failed to read .git/HEAD" here since
there's no .git to read. describe-then-new is what `jj commit -m` does
under the hood and needs no git-branch concept, so it works on this
fixture the same way the original `jj commit -m` CLI call did.

Caught by actually running the test suite after the previous commit's jj
CLI-to-jj-lib migration (this sandbox lacks a jj CLI, but a build+test run
of the affected files was still possible and caught this one).
…etup

The repo-wide lint (RUST_DIRS = src-tauri/src only) flags pub functions
with no caller in production code, since tests/ isn't scanned. The 6
jj-lib accessors added for the CLI-to-jj-lib test migration had no such
caller. tauri_test_bridge.rs is the established exemption for test-only
pub surface, but it's gated behind the "tauri-test" feature (only built
for the NAPI addon) and not linked into the plain `cargo test`/nextest
crate the tests/*.rs integration tests actually compile against — moving
them there would make them uncallable from tests/e2e_test_helpers.rs.
So instead: add direct unit tests for each in jj.rs's own #[cfg(test)]
module (src-tauri/src, real coverage, satisfies the lint honestly rather
than working around it).

Also fixes jj_lib_vs_cli_test.rs: several of its differential tests used
TestRepo::new_without_init(), which skips core::init() entirely — no .jj
directory ever gets created — so every jj-lib call failed with "There is
no Jujutsu repo". This sandbox has no jj CLI to catch it locally; CI's
jj-CLI-backed run surfaced it. Switched those to TestRepo::new().
@Ziinc
Ziinc force-pushed the claude/windows-tests-slow-flaky-97pv3q branch from c3624a7 to bff9e7c Compare September 10, 2026 09:19
import_git_refs_best_effort (the "make a git-only bookmark resolvable as
a jj revision" best-effort call added to several test wrappers) called
jj_util_import_git_refs, which is really reconcile_colocated_home_repo —
full git-HEAD/working-copy reconciliation for the home repo, not a plain
git-refs-into-bookmarks import. Harmless before, but main's rebase fixed
a bug in that reconciliation's working-copy-reset condition, and running
it as a side effect of every change-id/revset resolution now disturbs
home-repo working-copy state before conflict-producing jj_new calls could
run — core_changes_test.rs's conflict tests were resolving to no
conflicts at all.

Added jj_import_remaining_git_refs: the narrow git::import_refs step
alone (imports branch refs into jj bookmarks, no git-HEAD/working-copy
touching) — the operation the "best effort" comment actually described —
and switched the test wrapper to use it instead.
Five tests failed once run against a real jj CLI (ubuntu/macos rust jobs;
this sandbox has none):

- jj_get_sparse_patterns: full checkout is represented as a single root
  pattern (internal path ""), not an empty pattern list. Test assumption
  was wrong; fixed the expectation, not the production accessor.

- jj_import_remaining_git_refs unit test: compared the imported git-only
  branch against jj's own working-copy commit (@), but `jj git init
  --colocate` doesn't move git HEAD to jj's new empty @ until something
  reconciles them — a plain `git branch` (which points at HEAD) still
  resolves to the pre-init commit. Compare against that commit instead.

- jj_lib_vs_cli_test.rs (3 tests): a jj-lib write followed immediately by
  a real `jj` CLI read of the same symbolic revision could disagree,
  because the CLI's first-ever touch on one of these repos performs its
  own git-HEAD/jj-@ reconciliation as a side effect (the same class of
  desync as above) — shifting @ between the wrapper's return value and
  the CLI's later read of "@". Added a settle_cli() warm-up call right
  after repo creation in every test, before any state gets captured for
  comparison, so that one-time reconciliation happens harmlessly upfront.

Ziinc commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed fixes for the 5 rust-test failures from the last run (all caught once tests actually ran against a real jj CLI on ubuntu/macos — this environment has none, so they only surfaced in CI):

  • jj_get_sparse_patterns unit test: full checkout is a single root pattern (""), not an empty list — fixed the test expectation, not the accessor.
  • jj_import_remaining_git_refs unit test: jj git init --colocate doesn't move git HEAD to jj's new empty @ until something reconciles them, so a plain git branch (points at HEAD) still resolves to the pre-init commit — compared against the right commit now.
  • 3 tests in jj_lib_vs_cli_test.rs: the real jj CLI's first-ever touch on one of these repos performs its own git-HEAD/jj-@ reconciliation as a side effect, which could shift @ between a jj-lib write and a later CLI read of the same symbolic revision. Added a warm-up CLI call right after repo creation in every test, before anything gets captured for comparison.

Two failures on the last run are not from this PR and I'm not touching them here:

  • verify / ubuntu-22.04: test/integration/workspace/create-pr.test.tsx is 583 lines on main itself (checked via git show <main-sha>:...), over the max-lines: 500 eslint rule — pre-existing, unrelated to this diff (frontend test file this PR never touches).
  • rust / test / windows-latest: 3 pre-existing failures unrelated to this diff — cli::tests::dispatch_agent_request_* (2), commands::pty_commands::tests::resolves_workspace_id_to_absolute_working_directory, and 4 jj::tests::reconcile_* tests — all authored by prior main commits (#441, #436), not this PR.

Watching for the next run.


Generated by Claude Code

…atches_cli

The CLI's log query for the description was itself a subprocess touch that
snapshots/reconciles the colocated repo, shifting @ before the wrapper's own
clean-check ran. Capture the wrapper's post-describe state before any further
CLI subprocess call.
… workspace

The home-repo-root colocated path triggers reconcile_colocated_home_repo on
every jj_get_changed_files call, which silently checks @ back out to the
stale Git HEAD when the wrapper's raw jj_new/jj_describe calls (unlike
jj_commit) never exported to Git HEAD, discarding the just-described commit.
Run the lifecycle against a created workspace instead, which isn't subject
to that home-repo reconciliation and matches how the app actually drives
these primitives.

Ziinc commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

js / test:integration / ubuntu-22.04 has failed twice on this head (b84d098), each time on a different test in the parallel integration suite:

  • run 1: test/integration/filepicker.test.tsxtoHaveTextContent() received null (a waitFor resolved before the DOM settled)
  • run 2 (re-run): test/integration/filepicker.test.tsx — a different assertion, Test timed out in 5000ms right at render
  • (an earlier CI run on a prior commit also failed on test/integration/remote-workspace-ui.test.tsx with the same waitFor/timeout shape)

None of these files are touched by this PR (Rust-test-only: src-tauri/tests/*.rs, src-tauri/src/jj.rs). A different test failing each run with a timing/waitFor timeout is a flake signature in the parallel test runner, not a regression from this diff. I've already used my one re-run for this job; not spending more re-runs chasing it. Flagging it here rather than continuing to re-run — if it recurs across pushes it likely needs testTimeout/parallelism tuning independent of this PR.


Generated by Claude Code

Ziinc commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

I looked into the CI failure on this PR (run 34487910076, attempt 2), specifically the rust / test / windows-latest job.

The 5 failing tests are:

  • commands::pty_commands::tests::resolves_workspace_id_to_absolute_working_directory — panics on a path-separator mismatch (...\workspaces\feature-one vs ...\workspaces/feature-one), a mixed-separator bug in resolve_local_working_dir's test assertion on Windows.
  • jj::tests::reconcile_detached_external_checkout_reparents_home
  • jj::tests::reconcile_external_checkout_preserves_real_local_edit
  • jj::tests::reconcile_external_checkout_reparents_home_without_false_changes
  • jj::tests::reconcile_reparents_when_git_head_was_already_imported

None of these live in files this PR touches — the diff only changes src-tauri/src/jj.rs by adding new code/tests, pty_tests.rs, and various test helpers, but these 5 failing tests are pre-existing and unmodified by this branch. I confirmed pty_commands.rs isn't touched by the diff at all, and the failing jj.rs reconcile tests aren't part of this PR's added/changed lines.

I also checked recent CI runs on main (e.g. runs for #483, #480, #478, #474, #469) and they are failing too (conclusion: failure), which confirms these Windows-specific test failures are pre-existing breakage on main, not something introduced by this branch's PTY-test-sequencing changes.

Given that, I'm not pushing a fix here since it would be unrelated to this PR's purpose (deflaking/reusing PTY shell sessions in tests) and to keep the diff focused. Someone should file/fix these separately as a Windows path-normalization and Git-checkout-reconcile issue on main.


Generated by Claude Code

…slow-flaky-97pv3q

# Conflicts:
#	src-tauri/tests/pty_tests.rs
@Ziinc
Ziinc merged commit c3451b7 into main Sep 13, 2026
7 checks passed
@Ziinc
Ziinc deleted the claude/windows-tests-slow-flaky-97pv3q branch September 13, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants