Skip to content

Speed up Rust E2E tests with shared clients - #2250

Merged
SteveSandersonMS merged 11 commits into
mainfrom
stevesa/rust-ci-speedup
Aug 4, 2026
Merged

Speed up Rust E2E tests with shared clients#2250
SteveSandersonMS merged 11 commits into
mainfrom
stevesa/rust-ci-speedup

Conversation

@SteveSandersonMS

Copy link
Copy Markdown
Contributor

Summary

  • reuse one Copilot client/runtime per eligible Rust E2E source group, following the Node file-scoped pattern
  • keep tests with custom startup, cross-process resume, shutdown, pristine-client, or multi-client semantics on dedicated clients
  • run shared client/router tasks on a persistent Tokio runtime and reset sessions, work directories, proxy snapshots, and user configuration between tests
  • bound proxy startup, socket I/O, cleanup, and subprocess shutdown so failures cannot hang CI indefinitely

Why

Rust was starting an expensive Copilot runtime and replay proxy for almost every E2E test. Equivalent Node tests reuse the client within a file and complete dramatically faster.

Focused validation

  • session::: 37 passed in 16.12s
  • canvas::: 4 passed with reuse in 3.84s
  • commands::: 4 passed in 1.84s
  • elicitation:: plus related tests: 21 passed in 7.30s
  • rpc_event_log::: 4 passed in 2.15s
  • rpc_queue::: 2 passed in 2.15s
  • multi_turn::: 2 passed in 2.27s
  • mode_empty::: 6 passed in 2.66s
  • session_fs_sqlite::: 2 passed in 12.74s
  • dedicated abort::: 2 passed in 4.04s
  • cargo fmt --check passes

A full local E2E rerun was intentionally not performed after the focused validation because it previously exceeded the useful local feedback window; this draft is intended to collect complete CI timing and correctness data.

Reuse file-scoped Copilot clients for eligible Rust E2E tests while retaining dedicated clients for startup and lifecycle-sensitive cases. Keep shared router tasks on a persistent runtime and bound harness cleanup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings August 4, 2026 12:03
Comment thread rust/tests/e2e/rpc_server.rs Fixed
Comment thread rust/tests/e2e/tools.rs Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces reusable Rust E2E clients and proxies to reduce runtime startup overhead while retaining dedicated clients for lifecycle-sensitive tests.

Changes:

  • Adds shared E2E group lifecycle, isolation cleanup, persistent Tokio runtime, and bounded I/O/process handling.
  • Migrates eligible E2E suites to shared clients with custom group options where needed.
  • Adds test-support client hooks for router startup and session cleanup.
Show a summary per file
File Description
rust/src/lib.rs Adds test-only router and session-cleanup APIs.
rust/tests/e2e/support.rs Implements shared E2E groups, cleanup, and bounded proxy operations.
rust/tests/e2e/abort.rs Explicitly retains dedicated clients.
rust/tests/e2e/ask_user.rs Shares the client across ask-user tests.
rust/tests/e2e/builtin_tools.rs Shares built-in tool test infrastructure.
rust/tests/e2e/canvas.rs Reuses one client for canvas tests.
rust/tests/e2e/client_api.rs Shares eligible client API tests.
rust/tests/e2e/commands.rs Reuses a client for command tests.
rust/tests/e2e/compaction.rs Shares compaction test infrastructure.
rust/tests/e2e/elicitation.rs Reuses one client for elicitation tests.
rust/tests/e2e/event_fidelity.rs Shares event-fidelity tests.
rust/tests/e2e/hooks.rs Shares basic hook tests.
rust/tests/e2e/hooks_extended.rs Shares extended hook tests.
rust/tests/e2e/mode_empty.rs Adds a shared empty-mode client configuration.
rust/tests/e2e/mode_handlers.rs Shares mode-handler tests.
rust/tests/e2e/multi_provider_registry.rs Shares provider-routing tests.
rust/tests/e2e/multi_turn.rs Shares multi-turn tests.
rust/tests/e2e/permissions.rs Shares eligible permission tests.
rust/tests/e2e/pre_mcp_tool_call_hook.rs Shares pre-MCP hook tests.
rust/tests/e2e/rpc_additional_edge_cases.rs Shares additional RPC edge cases.
rust/tests/e2e/rpc_agent.rs Shares agent RPC tests.
rust/tests/e2e/rpc_event_log.rs Shares event-log tests.
rust/tests/e2e/rpc_event_side_effects.rs Shares event side-effect tests.
rust/tests/e2e/rpc_mcp_and_skills.rs Shares MCP and skills RPC tests.
rust/tests/e2e/rpc_mcp_config.rs Shares MCP configuration tests.
rust/tests/e2e/rpc_mcp_lifecycle.rs Shares MCP lifecycle tests.
rust/tests/e2e/rpc_queue.rs Shares queue tests.
rust/tests/e2e/rpc_remote.rs Shares remote-session tests.
rust/tests/e2e/rpc_schedule.rs Shares scheduling tests.
rust/tests/e2e/rpc_server.rs Shares eligible server RPC tests.
rust/tests/e2e/rpc_server_misc.rs Shares eligible miscellaneous server tests.
rust/tests/e2e/rpc_server_plugins.rs Shares plugin and marketplace tests.
rust/tests/e2e/rpc_server_remote_control.rs Shares remote-control tests.
rust/tests/e2e/rpc_session_state.rs Shares session-state RPC tests.
rust/tests/e2e/rpc_session_state_extras.rs Shares supplementary session-state tests.
rust/tests/e2e/rpc_shell_and_fleet.rs Shares shell and fleet tests.
rust/tests/e2e/rpc_shell_edge_cases.rs Shares shell edge-case tests.
rust/tests/e2e/rpc_shell_user_requested.rs Shares user-requested shell tests.
rust/tests/e2e/rpc_tasks_and_handlers.rs Shares task and handler tests.
rust/tests/e2e/rpc_ui_ephemeral_query.rs Shares the ephemeral-query test.
rust/tests/e2e/rpc_workspace_checkpoints.rs Shares workspace-checkpoint tests.
rust/tests/e2e/session.rs Migrates eligible session tests to reuse.
rust/tests/e2e/session_config.rs Shares eligible session configuration tests.
rust/tests/e2e/session_fs_sqlite.rs Adds shared SQLite client options.
rust/tests/e2e/session_lifecycle.rs Shares session lifecycle tests.
rust/tests/e2e/session_todos_changed.rs Shares the todos-changed test.
rust/tests/e2e/skills.rs Shares skills tests.
rust/tests/e2e/streaming_fidelity.rs Shares eligible streaming tests.
rust/tests/e2e/system_message_sections.rs Shares system-message section tests.
rust/tests/e2e/tool_results.rs Shares tool-result tests.
rust/tests/e2e/tools.rs Shares tool invocation tests.

Review details

  • Files reviewed: 51/51 changed files
  • Comments generated: 4
  • Review effort level: Balanced

Comment thread rust/tests/e2e/rpc_server_plugins.rs
Comment thread rust/src/lib.rs Outdated
Comment thread rust/tests/e2e/support.rs
Comment thread rust/tests/e2e/rpc_workspace_checkpoints.rs Outdated
Keep runtime-global plugin state, model-catalog changes, and Windows-locked config directories on dedicated clients. Match nightly formatting and resolve the new cleanup helper's clippy warning.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 4, 2026 12:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (4)

rust/src/lib.rs:2203

  • This doc comment describes cleanup_sessions_for_test, but it is attached to start_router_for_test, which only starts router tasks. Update it so callers of the test-support API are not told that invoking this method deletes sessions.
    /// Disconnect and delete every session owned by this test client's isolated
    /// runtime. This is test-harness plumbing, not part of the supported SDK API.
    #[cfg(feature = "test-support")]
    #[doc(hidden)]
    pub fn start_router_for_test(&self) {

rust/tests/e2e/support.rs:230

  • A timed-out or panicking test only tears down the shared client when cleanup also fails or this happens to be the final invocation. Successful session/work-dir cleanup cannot reset arbitrary connection-level state left by the failed test, so later tests can inherit corrupted state; an early-aborted run can also leave the proxy/client alive. Treat every non-success result as a teardown condition before propagating the failure.
    rust/tests/e2e/rpc_session_state.rs:58
  • This model-switch test must remain isolated. The runtime caches /models for 30 minutes per auth/base URL; almost every snapshot in this shared group exposes only claude-sonnet-4.5, while this snapshot also requires gpt-5.4. Whichever test primes the shared cache determines whether this switch succeeds. Use the dedicated context and adjust the group's expected shared invocation count; the Node, .NET, and Python versions already isolate this case for the same reason.
    rust/tests/e2e/session.rs:1006
  • This reasoning-effort test needs a dedicated runtime because its snapshot uniquely adds gpt-5.4; the other shared session snapshots can prime the runtime's 30-minute /models cache with only claude-sonnet-4.5. That makes the outcome order-dependent. Keep this test isolated, as the Node, .NET, and Python equivalents do, and decrement the shared invocation count.
  • Files reviewed: 51/51 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread rust/tests/e2e/multi_provider_registry.rs Outdated
SteveSandersonMS and others added 2 commits August 4, 2026 12:52
Preserve skipped shared-group accounting without configuring snapshots, reap failed proxy startups, correct test-support docs, and avoid CodeQL binding ambiguity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 4, 2026 12:55
Comment thread rust/tests/e2e/rpc_server.rs
Comment thread rust/tests/e2e/tools.rs
Keep model-catalog-dependent tests on dedicated clients, correct provider group teardown accounting, and discard shared connection state after any failed test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (2)

rust/tests/e2e/multi_provider_registry.rs:243

  • assert_routing is invoked by three separate #[tokio::test] functions, in addition to the registration test, so this group runs four times. With expected_invocations set to 2, the group tears down after the second test, then creates a new client/proxy for the remaining tests and never reaches the teardown condition again (completed becomes 3 and 4). This defeats reuse and leaks the final runtime/proxy in a full unfiltered run. Set the expected count to 4.
static E2E: super::support::SharedE2eGroup = super::support::SharedE2eGroup::standard(CATEGORY, 4);

rust/tests/e2e/support.rs:728

  • This cleanup path is not actually bounded: it ignores a failed kill() and then calls blocking wait(). If the startup-timeout/error path cannot terminate the proxy, the test process can still hang indefinitely, contradicting the PR's bounded-shutdown goal. Poll try_wait() only until the cleanup deadline (and report/return after it) instead of using an unconditional wait.
  • Files reviewed: 51/51 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Copilot AI review requested due to automatic review settings August 4, 2026 13:00
Replace blocking waits after proxy termination with deadline-bounded try_wait polling so startup and shutdown failures cannot hang the test process.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SteveSandersonMS
SteveSandersonMS marked this pull request as ready for review August 4, 2026 13:06
@SteveSandersonMS
SteveSandersonMS requested a review from a team as a code owner August 4, 2026 13:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (2)

rust/tests/e2e/support.rs:1283

  • The new startup cleanup covers timeout, channel, and regex-shape failures, but a line matching the regex can still exit through serde_json::from_str(...)? without killing or waiting for child; missing metadata fields also panic via expect with the child still alive. Parse and validate the metadata before transferring the child, and on every parse/validation error call kill_and_wait_child before returning.
    rust/tests/e2e/support.rs:344
  • This skipped path bypasses E2E_CONCURRENCY, even though that semaphore is what guarantees all in-process operations are serialized. If this skipped invocation is the group's last one, shutdown_bounded() can run concurrently with another in-process test while that test has process-wide environment/CWD overrides active. Acquire the same permit before updating the count and possibly tearing down the shared in-process client.
  • Files reviewed: 51/51 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Copilot AI review requested due to automatic review settings August 4, 2026 13:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 51/51 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread rust/tests/e2e/support.rs Outdated
The in-process client helper is now used by the E2E target, so its dead-code expectation is unfulfilled under the CI clippy feature set.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 4, 2026 13:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (1)

rust/tests/e2e/support.rs:170

  • Client startup is outside every timeout in this helper. Client::start awaits unbounded JSON-RPC handshakes (and sessionFs.setProvider for the SQLite group), so a runtime that starts but stops responding can hang the test binary indefinitely; the dedicated helper previously bounded this call because it ran inside the test closure. Bound this startup too, and ensure the spawned startup task/partially started client is aborted or force-stopped on expiry rather than detaching it.
  • Files reviewed: 51/51 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Time out and abort shared client handshakes, serialize skipped in-process teardown, and terminate the proxy on every startup metadata validation failure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 4, 2026 13:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (1)

rust/tests/e2e/support.rs:174

  • If shared client startup fails or times out here, the function panics before completed_invocations is incremented. The remaining tests can then only reach expected_invocations - 1, so their recreated shared client/proxy is never shut down and the proxy can be orphaned when the test binary exits. Advance invocation accounting on every exit path (for example with a guard established before initialization), or explicitly account for startup/setup failures before panicking.
  • Files reviewed: 51/51 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Advance shared-group accounting before context and client initialization so failed startup paths cannot prevent a recreated group from reaching final teardown.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 4, 2026 13:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (1)

rust/tests/e2e/support.rs:711

  • This always allows the replay proxy to write its current capture, including when with_shared_e2e_context is tearing the group down after a timeout, panic, setup failure, or cleanup failure. In local recording mode (GITHUB_ACTIONS != true), that replaces the test snapshot with a partial failing capture; the dedicated path and E2eContext::drop avoid this by passing true. Thread a skip_writing_cache flag into shutdown_bounded and set it whenever the test or cleanup did not succeed.
  • Files reviewed: 51/51 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 4, 2026 13:47
@SteveSandersonMS

Copy link
Copy Markdown
Contributor Author

Addressed in 8ea6fb1: shared teardown now skips replay-cache writes whenever the test or cleanup fails, while preserving cache writes after successful teardown.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 51/51 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread rust/tests/e2e/support.rs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 4, 2026 13:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 51/51 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@SteveSandersonMS
SteveSandersonMS merged commit 4405f45 into main Aug 4, 2026
32 checks passed
@SteveSandersonMS
SteveSandersonMS deleted the stevesa/rust-ci-speedup branch August 4, 2026 14:10
edburns pushed a commit that referenced this pull request Aug 4, 2026
Reuse file-scoped Copilot clients for eligible Rust E2E tests while retaining dedicated clients for startup and lifecycle-sensitive cases. Keep shared router tasks on a persistent runtime and bound harness cleanup.
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.

3 participants