Skip to content

Fix .NET in-process E2E transport coverage - #1986

Merged
roji merged 2 commits into
mainfrom
roji-fix-inprocess-e2e-transport
Jul 30, 2026
Merged

Fix .NET in-process E2E transport coverage#1986
roji merged 2 commits into
mainfrom
roji-fix-inprocess-e2e-transport

Conversation

@roji

@roji roji commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

The .NET E2E fixture explicitly selected TCP even in the in-process matrix, so those tests never exercised the FFI transport and hid FFI-specific behavior.

This change:

  • Allows resuming the same session in .NET SDK, aligning it with the other SDK's behavior (.NET specifically had a check against doing that). This was a blocker for some .NET E2E tests in inproc mode.
  • selects RuntimeConnection.ForInProcess() for the in-process matrix while preserving TCP coverage in the other matrix
  • restores the native host environment before each test because the shared fixture outlives per-test environment isolation
  • aligns same-client session resume with the other SDKs by replacing the routed session wrapper before the resume RPC, without adding operation-wide locking or concurrent-resume rejection
  • keeps TCP-only multi-client behavior intact and adds fixture policy and session lifetime coverage
  • uses a checkpoint number valid for the native runtime's u32 domain

The complete .NET in-process suite passes through FFI with 672 tests passed and 3 existing skips. Targeted TCP resume coverage and all .NET target-framework builds also pass.

Copilot AI review requested due to automatic review settings July 14, 2026 16:44
@roji
roji requested a review from a team as a code owner July 14, 2026 16:44
@github-actions

This comment has been minimized.

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

Updates .NET E2E coverage to exercise the FFI transport and supports same-client session resumption.

Changes:

  • Selects in-process or TCP transport according to the test matrix.
  • Restores host environment state between in-process tests.
  • Adds same-client resume replacement behavior and related coverage.
Show a summary per file
File Description
dotnet/src/Client.cs Replaces tracked wrappers during resume.
dotnet/test/Harness/E2ETestFixture.cs Selects matrix-appropriate transport.
dotnet/test/Harness/E2ETestContext.cs Restores in-process environment state.
dotnet/test/Harness/E2ETestBase.cs Prepares tests and chooses resume client.
dotnet/test/E2E/SessionE2ETests.cs Covers same-client resume replacement.
dotnet/test/E2E/RpcWorkspaceCheckpointsE2ETests.cs Uses a native-compatible checkpoint value.
dotnet/test/Unit/E2ETestFixtureTests.cs Tests fixture transport selection.
dotnet/test/Unit/ClientSessionLifetimeTests.cs Tests replacement and failure lifetimes.

Review details

  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Medium

Comment thread dotnet/src/Client.cs Outdated
@github-actions

This comment has been minimized.

@roji
roji enabled auto-merge July 14, 2026 17:13
@roji

roji commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

@SteveSandersonMS noticed this while working on other stuff, take a look - we weren't doing actual FFI in the .NET SDK, plus the .NET SDK was unique in not allowing the same session ID to be resumed with a new session object (which obstructed fixing the former).

@roji
roji force-pushed the roji-fix-inprocess-e2e-transport branch 2 times, most recently from ce76fb0 to e3f21ae Compare July 17, 2026 06:46
@github-actions

This comment has been minimized.

Make the in-process E2E matrix use the FFI runtime, restore the native host environment between tests, and align same-client session resume with the other SDKs while preserving routing after failed resumes.

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

Copilot-Session: c71188a1-1445-46aa-9faf-3b73cf6a6dd9
@roji
roji force-pushed the roji-fix-inprocess-e2e-transport branch from e3f21ae to 2cf5855 Compare July 29, 2026 19:59
Copilot AI review requested due to automatic review settings July 29, 2026 19:59
@github-actions

Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review ✅

This PR aligns .NET with the behavior of all other SDK implementations.

Key behavioral change: .NET previously threw InvalidOperationException when ResumeSessionAsync was called on the same client that already tracked the session. This PR replaces that rejection with the replace-existing semantics that every other SDK already implements:

SDK Same-client resume behavior (before this PR)
Node.js Replaces the tracked session (map assignment)
Go Replaces the tracked session (c.sessions[sessionID] = session)
Python Replaces the tracked session
Rust Replaces the tracked session
Java Replaces the tracked session
.NET (before) ❌ Threw InvalidOperationException
.NET (after) ✅ Replaces the tracked session — now consistent

The implementation is careful to restore the previous session on failure, which is a correctness improvement over the implicit behavior in simpler SDKs.

No other SDKs need updates. The change is a .NET-only catch-up to the already-established cross-SDK contract.

Generated by SDK Consistency Review Agent for #1986 · sonnet46 34.9 AIC · ⌖ 5.43 AIC · ⊞ 6.6K ·

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

Comments suppressed due to low confidence (3)

dotnet/test/Unit/ClientSessionLifetimeTests.cs:1

  • These tests manually dispose sessions at the end, but if an assertion fails mid-test the sessions may not be disposed, which can leak tracked sessions and make later tests flaky. Prefer await using var session = ... / await using var resumedSession = ... (or a try/finally) so cleanup happens even on test failure.
/*---------------------------------------------------------------------------------------------

dotnet/src/Client.cs:826

  • ConcurrentDictionary.AddOrUpdate may invoke the update delegate multiple times under contention, so capturing displacedSession via a closure can yield an indeterminate replacedSession (not necessarily the instance that was ultimately replaced). Consider using a retry loop with TryGetValue + TryUpdate (or TryAdd) to deterministically capture and return the actual replaced session.
        if (replaceExisting)
        {
            CopilotSession? displacedSession = null;
            _sessions.AddOrUpdate(
                session.SessionId,
                session,
                (_, current) =>
                {
                    displacedSession = current;
                    return session;
                });
            replacedSession = displacedSession;
        }

dotnet/test/Unit/ClientSessionLifetimeTests.cs:485

  • This test helper relies on reflection and a hard-coded private method name (GetSession), which is brittle and can break on refactors unrelated to behavior. If feasible, expose a minimal internal test hook (e.g., internal accessor with InternalsVisibleTo for the test assembly) so the tests verify tracking without reflection.
    private static CopilotSession? GetTrackedSession(CopilotClient client, string sessionId)
    {
        var method = typeof(CopilotClient).GetMethod("GetSession", BindingFlags.Instance | BindingFlags.NonPublic)
            ?? throw new InvalidOperationException("GetSession method was not found.");
        return (CopilotSession?)method.Invoke(client, [sessionId]);
    }
  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment thread dotnet/src/Client.cs Outdated
Preserve the existing .NET session ownership model while allowing resume E2E coverage to run through the in-process transport. Suspend and locally untrack the original test wrapper before resuming so the runtime session remains available without introducing replacement semantics.

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

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

Comments suppressed due to low confidence (2)

dotnet/test/E2E/SessionE2ETests.cs:231

  • This test still requires same-client resume to throw, which contradicts the new contract that replaces the tracked wrapper before session.resume. With that behavior enabled, Assert.ThrowsAsync fails and the new path remains unverified. Update this test to expect a successful resume on Client and assert that the returned session has the same ID (and receives routing intended for the replacement).
        await using var session1 = await CreateSessionAsync();

dotnet/test/Harness/E2ETestBase.cs:123

  • This reflection bypasses the public same-client replacement behavior that this PR is intended to cover and makes the E2E suite depend on the private CopilotSession.RemoveFromClient implementation. Keep the suspended wrapper tracked and resume through Ctx.ResumeSessionAsync; then rename this helper and update its callers. That both exercises the real SDK contract and keeps .NET tests limited to public SDK APIs.
        var removeFromClient = typeof(CopilotSession).GetMethod(
            "RemoveFromClient",
            BindingFlags.Instance | BindingFlags.NonPublic)
            ?? throw new InvalidOperationException("CopilotSession.RemoveFromClient was not found.");
        removeFromClient.Invoke(session, null);
  • Files reviewed: 9/9 changed files
  • Comments generated: 0 new
  • Review effort level: Medium

@github-actions github-actions Bot mentioned this pull request Jul 30, 2026
@roji
roji added this pull request to the merge queue Jul 30, 2026
Merged via the queue into main with commit 95ba623 Jul 30, 2026
55 of 57 checks passed
@roji
roji deleted the roji-fix-inprocess-e2e-transport branch July 30, 2026 15:32
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