From 42f6c89b34dadfbfe6fa2840a7e16c8d1b9173bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:20:25 +0000 Subject: [PATCH 1/3] Initial plan From a7880ac4885221f8ed616c60571c9a947c58bac5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:24:54 +0000 Subject: [PATCH 2/3] Achieve InProcess/out-of-process Java SDK test parity Co-authored-by: edburns <75821+edburns@users.noreply.github.com> --- java/sdk/pom.xml | 13 +++--- .../github/copilot/ClientOptionsE2ETest.java | 5 ++ .../com/github/copilot/CopilotClientTest.java | 9 ++++ .../com/github/copilot/E2ETestContext.java | 46 ++++++++++++++++--- .../github/copilot/PerSessionAuthTest.java | 3 ++ .../github/copilot/RpcServerMiscE2ETest.java | 3 ++ .../com/github/copilot/SlashCommandsIT.java | 3 ++ .../github/copilot/e2e/RequireInProcess.java | 2 +- .../com/github/copilot/e2e/SkipInProcess.java | 2 +- 9 files changed, 72 insertions(+), 14 deletions(-) diff --git a/java/sdk/pom.xml b/java/sdk/pom.xml index 9bfe76a4b8..7f11023d63 100644 --- a/java/sdk/pom.xml +++ b/java/sdk/pom.xml @@ -265,7 +265,7 @@ alphabetical - ${testExecutionAgentArgs} ${surefire.jvm.args} + ${testExecutionAgentArgs} ${surefire.jvm.args} --add-opens com.github.copilot.java/com.github.copilot.e2e=ALL-UNNAMED false + 0 + none + + inprocess + org.apache.maven.plugins maven-failsafe-plugin - 1 + 0 none - - **/InProcessTransportIT.java - ${copilot.inprocess.cli.path} inprocess diff --git a/java/sdk/src/test/java/com/github/copilot/ClientOptionsE2ETest.java b/java/sdk/src/test/java/com/github/copilot/ClientOptionsE2ETest.java index 45056afdb4..f6ff46a144 100644 --- a/java/sdk/src/test/java/com/github/copilot/ClientOptionsE2ETest.java +++ b/java/sdk/src/test/java/com/github/copilot/ClientOptionsE2ETest.java @@ -15,6 +15,8 @@ import org.junit.jupiter.api.Test; +import com.github.copilot.e2e.SkipInProcess; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.copilot.generated.rpc.SessionLimitsConfig; @@ -29,6 +31,7 @@ class ClientOptionsE2ETest { private static final ObjectMapper MAPPER = new ObjectMapper(); @Test + @SkipInProcess("Exercises direct CLI argument and working-directory forwarding to a spawned stdio subprocess") void testShouldForwardAdvancedSessionCreationOptionsToTheCli() throws Exception { try (var fake = FakeStdioCli.create()) { var workDir = fake.path("create-work"); @@ -95,6 +98,7 @@ void testShouldForwardAdvancedSessionCreationOptionsToTheCli() throws Exception } @Test + @SkipInProcess("Exercises direct CLI argument and working-directory forwarding to a spawned stdio subprocess") void testShouldForwardSingularProviderConfigurationOnSessionCreation() throws Exception { try (var fake = FakeStdioCli.create()) { try (var client = fake.createClient()) { @@ -123,6 +127,7 @@ void testShouldForwardSingularProviderConfigurationOnSessionCreation() throws Ex } @Test + @SkipInProcess("Exercises direct CLI argument and working-directory forwarding to a spawned stdio subprocess") void testShouldForwardAdvancedSessionResumeOptionsToTheCli() throws Exception { try (var fake = FakeStdioCli.create()) { var workDir = fake.path("resume-work"); diff --git a/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java b/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java index 067571df13..6cfc985fd5 100644 --- a/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java +++ b/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import com.github.copilot.e2e.SkipInProcess; + import com.github.copilot.rpc.CopilotClientOptions; import com.github.copilot.rpc.PermissionHandler; import com.github.copilot.rpc.PingResponse; @@ -119,6 +121,7 @@ void testClientConstruction() { } @Test + @SkipInProcess("Constructs the client with CliPath explicitly, which the in-process transport does not accept") void testClientConstructionWithOptions() { var options = new CopilotClientOptions().setCliPath("/path/to/cli").setLogLevel("debug").setAutoStart(false); @@ -149,6 +152,7 @@ void testCliUrlOnlyConstruction() { } @Test + @SkipInProcess("Validates CliPath and CliUrl subprocess transport conflicts, which are not applicable in in-process mode") void testCliUrlMutualExclusionWithCliPath() { var options = new CopilotClientOptions().setCliUrl("localhost:3000").setCliPath("/path/to/cli"); @@ -156,6 +160,7 @@ void testCliUrlMutualExclusionWithCliPath() { } @Test + @SkipInProcess("Verifies spawning and talking to a child stdio process instead of the in-process runtime") void testStartAndConnectUsingStdio() throws Exception { assertNotNull(cliPath, "Copilot CLI not found in PATH or COPILOT_CLI_PATH"); @@ -173,6 +178,7 @@ void testStartAndConnectUsingStdio() throws Exception { } @Test + @SkipInProcess("Asserts child-process startup stderr reporting for invalid CLI arguments") void testShouldReportErrorWithStderrWhenCliFailsToStart() throws Exception { assertNotNull(cliPath, "Copilot CLI not found in PATH or COPILOT_CLI_PATH"); @@ -190,6 +196,7 @@ void testShouldReportErrorWithStderrWhenCliFailsToStart() throws Exception { } @Test + @SkipInProcess("Verifies spawning and talking to a child TCP process instead of the in-process runtime") void testStartAndConnectUsingTcp() throws Exception { assertNotNull(cliPath, "Copilot CLI not found in PATH or COPILOT_CLI_PATH"); @@ -205,6 +212,7 @@ void testStartAndConnectUsingTcp() throws Exception { } @Test + @SkipInProcess("Exercises lifecycle of a spawned CLI process selected via CliPath") void testForceStopWithoutCleanup() throws Exception { assertNotNull(cliPath, "Copilot CLI not found in PATH or COPILOT_CLI_PATH"); @@ -497,6 +505,7 @@ void testForceStopWithNoConnectionCompletes() throws Exception { } @Test + @SkipInProcess("Exercises session shutdown after stopping a spawned CLI process selected via CliPath") void testCloseSessionAfterStoppingClientDoesNotThrow() throws Exception { assertNotNull(cliPath, "Copilot CLI not found in PATH or COPILOT_CLI_PATH"); diff --git a/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java b/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java index f524b33dab..f22392f548 100644 --- a/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java +++ b/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java @@ -18,7 +18,10 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.github.copilot.ffi.InProcessEnvGuard; import com.github.copilot.rpc.CopilotClientOptions; +import com.github.copilot.rpc.InProcessRuntimeConnection; +import com.github.copilot.rpc.RuntimeConnection; /** * E2E test context that manages the test environment including the CapiProxy, @@ -71,6 +74,7 @@ public class E2ETestContext implements AutoCloseable { private String proxyUrl; private final CapiProxy proxy; private final Path repoRoot; + private final List inProcessEnvGuards = new ArrayList<>(); private Path currentSnapshotFile; private E2ETestContext(String cliPath, Path homeDir, Path workDir, String proxyUrl, CapiProxy proxy, @@ -322,9 +326,8 @@ public Map getEnvironment() { * @return a new CopilotClient */ public CopilotClient createClient() { - CopilotClientOptions options = new CopilotClientOptions().setCliPath(cliPath).setCwd(workDir.toString()) - .setEnvironment(getEnvironment()).setGitHubToken(DEFAULT_GITHUB_TOKEN); - + CopilotClientOptions options = new CopilotClientOptions().setGitHubToken(DEFAULT_GITHUB_TOKEN); + applyContextOptions(options); return new CopilotClient(options); } @@ -338,6 +341,19 @@ public CopilotClient createClient() { * @return a new CopilotClient */ public CopilotClient createClient(CopilotClientOptions options) { + applyContextOptions(options); + if (options.getGitHubToken() == null) { + options.setGitHubToken(DEFAULT_GITHUB_TOKEN); + } + + return new CopilotClient(options); + } + + private void applyContextOptions(CopilotClientOptions options) { + if (isInProcessMode(options)) { + inProcessEnvGuards.add(new InProcessEnvGuard(buildInProcessEnvironment(options))); + return; + } if (options.getCliPath() == null) { options.setCliPath(cliPath); } @@ -347,11 +363,26 @@ public CopilotClient createClient(CopilotClientOptions options) { if (options.getEnvironment() == null || options.getEnvironment().isEmpty()) { options.setEnvironment(getEnvironment()); } - if (options.getGitHubToken() == null) { - options.setGitHubToken(DEFAULT_GITHUB_TOKEN); + } + + private boolean isInProcessMode(CopilotClientOptions options) { + RuntimeConnection connection = options.getConnection(); + if (connection instanceof InProcessRuntimeConnection) { + return true; } + String defaultConnection = System.getenv("COPILOT_SDK_DEFAULT_CONNECTION"); + return defaultConnection != null && "inprocess".equalsIgnoreCase(defaultConnection.trim()); + } - return new CopilotClient(options); + private Map buildInProcessEnvironment(CopilotClientOptions options) { + Map env = new HashMap<>(getEnvironment()); + Map optionEnvironment = options.getEnvironment(); + if (optionEnvironment != null && !optionEnvironment.isEmpty()) { + env.putAll(optionEnvironment); + options.setEnvironment(null); + } + env.put("COPILOT_CLI_PATH", cliPath); + return env; } /** @@ -428,6 +459,9 @@ public void initializeProxy() throws IOException, InterruptedException { @Override public void close() throws Exception { + for (int i = inProcessEnvGuards.size() - 1; i >= 0; i--) { + inProcessEnvGuards.get(i).close(); + } proxy.stop(); // Clean up temp directories (best effort) diff --git a/java/sdk/src/test/java/com/github/copilot/PerSessionAuthTest.java b/java/sdk/src/test/java/com/github/copilot/PerSessionAuthTest.java index 9e5cd1b324..974536300c 100644 --- a/java/sdk/src/test/java/com/github/copilot/PerSessionAuthTest.java +++ b/java/sdk/src/test/java/com/github/copilot/PerSessionAuthTest.java @@ -13,6 +13,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import com.github.copilot.e2e.SkipInProcess; + import com.github.copilot.generated.rpc.SessionGitHubAuthGetStatusResult; import com.github.copilot.rpc.CopilotClientOptions; import com.github.copilot.rpc.PermissionHandler; @@ -110,6 +112,7 @@ void shouldIsolateAuthBetweenSessions() throws Exception { } @Test + @SkipInProcess("Builds a client with per-client environment, cwd, and logged-in-user overrides that the shared in-process runtime cannot isolate") void shouldBeUnauthenticatedWithoutToken() throws Exception { Map env = new HashMap<>(ctx.getEnvironment()); env.put("COPILOT_DEBUG_GITHUB_API_URL", ctx.getProxyUrl()); diff --git a/java/sdk/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java b/java/sdk/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java index 1db801d841..21dfb9c08f 100644 --- a/java/sdk/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java +++ b/java/sdk/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java @@ -14,6 +14,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import com.github.copilot.e2e.SkipInProcess; + import com.github.copilot.generated.rpc.AccountAllUsers; import com.github.copilot.generated.rpc.AccountLoginParams; import com.github.copilot.generated.rpc.AccountLogoutParams; @@ -70,6 +72,7 @@ void testShouldGetSetAndClearUserSettings() throws Exception { } @Test + @SkipInProcess("Builds a client with per-client environment and cwd overrides to test account login/logout flows") void testShouldLoginListGetCurrentAuthAndLogoutAccount() throws Exception { ctx.configureForTest("rpc_server_misc", "should_login_list_getcurrentauth_and_logout_account"); var token = "java-account-token"; diff --git a/java/sdk/src/test/java/com/github/copilot/SlashCommandsIT.java b/java/sdk/src/test/java/com/github/copilot/SlashCommandsIT.java index 634c0bad9b..5dec064644 100644 --- a/java/sdk/src/test/java/com/github/copilot/SlashCommandsIT.java +++ b/java/sdk/src/test/java/com/github/copilot/SlashCommandsIT.java @@ -22,6 +22,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import com.github.copilot.e2e.SkipInProcess; + import com.github.copilot.generated.rpc.SessionCommandsListResult; import com.github.copilot.generated.rpc.SessionCommandsInvokeParams; import com.github.copilot.generated.rpc.SlashCommandAgentPromptResult; @@ -41,6 +43,7 @@ * Requires the CLI to be installed and the user to be signed in. Uses * {@link TestUtil#findCliPath()} so the test harness binary is found in CI. */ +@SkipInProcess("Requires a live signed-in CLI subprocess and logged-in-user transport behavior rather than the replayed in-process harness") class SlashCommandsIT { private static CopilotClient client; diff --git a/java/sdk/src/test/java/com/github/copilot/e2e/RequireInProcess.java b/java/sdk/src/test/java/com/github/copilot/e2e/RequireInProcess.java index ef85f261ae..12de4e5b73 100644 --- a/java/sdk/src/test/java/com/github/copilot/e2e/RequireInProcess.java +++ b/java/sdk/src/test/java/com/github/copilot/e2e/RequireInProcess.java @@ -44,7 +44,7 @@ /** * JUnit 5 execution condition backing {@link RequireInProcess}. */ - final class Condition implements org.junit.jupiter.api.extension.ExecutionCondition { + public static final class Condition implements org.junit.jupiter.api.extension.ExecutionCondition { private static final String DEFAULT_CONNECTION_ENV_VAR = "COPILOT_SDK_DEFAULT_CONNECTION"; diff --git a/java/sdk/src/test/java/com/github/copilot/e2e/SkipInProcess.java b/java/sdk/src/test/java/com/github/copilot/e2e/SkipInProcess.java index b1a27ad025..3f626e133f 100644 --- a/java/sdk/src/test/java/com/github/copilot/e2e/SkipInProcess.java +++ b/java/sdk/src/test/java/com/github/copilot/e2e/SkipInProcess.java @@ -44,7 +44,7 @@ /** * JUnit 5 execution condition backing {@link SkipInProcess}. */ - final class Condition implements org.junit.jupiter.api.extension.ExecutionCondition { + public static final class Condition implements org.junit.jupiter.api.extension.ExecutionCondition { private static final String DEFAULT_CONNECTION_ENV_VAR = "COPILOT_SDK_DEFAULT_CONNECTION"; From f36371ac769752424a9b84e409559404758247db Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Wed, 5 Aug 2026 21:44:14 +0000 Subject: [PATCH 3/3] Address Copilot review findings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- java/sdk/pom.xml | 4 ++-- .../com/github/copilot/CopilotClient.java | 9 ++++++++ .../com/github/copilot/CopilotClientTest.java | 7 ++++++ .../com/github/copilot/E2ETestContext.java | 22 ++++++++++++++----- .../github/copilot/ExecutorWiringTest.java | 15 ++++++------- .../com/github/copilot/MetadataApiTest.java | 4 ++++ .../github/copilot/ffi/InProcessEnvGuard.java | 7 +++++- 7 files changed, 51 insertions(+), 17 deletions(-) diff --git a/java/sdk/pom.xml b/java/sdk/pom.xml index 7f11023d63..12cb42a3ee 100644 --- a/java/sdk/pom.xml +++ b/java/sdk/pom.xml @@ -617,7 +617,7 @@ did not produce the multi-release output. Re-build on JDK 25+ and verify the maven-surefire-plugin false - 0 + 1 none inprocess @@ -628,7 +628,7 @@ did not produce the multi-release output. Re-build on JDK 25+ and verify the org.apache.maven.plugins maven-failsafe-plugin - 0 + 1 none ${copilot.inprocess.cli.path} diff --git a/java/sdk/src/main/java/com/github/copilot/CopilotClient.java b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java index d53be740cf..f420585be7 100644 --- a/java/sdk/src/main/java/com/github/copilot/CopilotClient.java +++ b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java @@ -123,6 +123,7 @@ public final class CopilotClient implements AutoCloseable { private final Integer optionsPort; private final RuntimeConnection runtimeConnection; private final String effectiveConnectionToken; + private final Runnable closeHook; private volatile List modelsCache; private final Object modelsCacheLock = new Object(); @@ -142,7 +143,12 @@ public CopilotClient() { * if mutually exclusive options are provided */ public CopilotClient(CopilotClientOptions options) { + this(options, null); + } + + CopilotClient(CopilotClientOptions options, Runnable closeHook) { this.options = options != null ? options : new CopilotClientOptions(); + this.closeHook = closeHook; // Resolve the transport: an explicit RuntimeConnection wins; otherwise the // COPILOT_SDK_DEFAULT_CONNECTION env var, or the individual transport options. @@ -1663,6 +1669,9 @@ public void close() { LOG.log(Level.FINE, "Error during close", e); } finally { shutdownOwnedExecutor(); + if (closeHook != null) { + closeHook.run(); + } } } diff --git a/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java b/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java index 6cfc985fd5..7fad694558 100644 --- a/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java +++ b/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java @@ -131,6 +131,7 @@ void testClientConstructionWithOptions() { } @Test + @SkipInProcess("Validates external CLI URL transport options") void testCliUrlAutoCorrectsUseStdio() { var options = new CopilotClientOptions().setCliUrl("localhost:3000").setUseStdio(true); @@ -141,6 +142,7 @@ void testCliUrlAutoCorrectsUseStdio() { } @Test + @SkipInProcess("Validates external CLI URL transport options") void testCliUrlOnlyConstruction() { var options = new CopilotClientOptions().setCliUrl("localhost:4321"); @@ -289,6 +291,7 @@ void testTcpConnectionTokenWithUseStdioThrows() { } @Test + @SkipInProcess("Validates TCP transport options") void testTcpConnectionTokenAcceptedInTcpMode() { var options = new CopilotClientOptions().setUseStdio(false).setTcpConnectionToken("my-token"); @@ -409,6 +412,7 @@ void testOnLifecycleMultipleHandlers() throws Exception { // ===== getState() coverage ===== @Test + @SkipInProcess("Validates subprocess CLI path failure handling") void testGetStateErrorAfterFailedStart() throws Exception { // Use a non-existent CLI path to trigger a startup failure var options = new CopilotClientOptions().setCliPath("/nonexistent/path/to/cli").setAutoStart(false); @@ -429,6 +433,7 @@ void testGetStateErrorAfterFailedStart() throws Exception { } @Test + @SkipInProcess("Validates subprocess CLI path failure handling") void testGetStateConnectingDuringStart() throws Exception { // Use a non-existent CLI path; the future won't complete immediately var options = new CopilotClientOptions().setCliPath("/nonexistent/path/to/cli").setAutoStart(false); @@ -469,6 +474,7 @@ void testCloseIsIdempotent() { } @Test + @SkipInProcess("Validates subprocess CLI path failure handling") void testCloseAfterFailedStart() throws Exception { var options = new CopilotClientOptions().setCliPath("/nonexistent/path/to/cli").setAutoStart(false); var client = new CopilotClient(options); @@ -528,6 +534,7 @@ void testCloseSessionAfterStoppingClientDoesNotThrow() throws Exception { // ===== start() idempotency ===== @Test + @SkipInProcess("Validates subprocess CLI path failure handling") void testStartIsIdempotentSingleConnectionAttempt() throws Exception { var options = new CopilotClientOptions().setCliPath("/nonexistent/path/to/cli").setAutoStart(false); diff --git a/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java b/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java index f22392f548..f040607353 100644 --- a/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java +++ b/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java @@ -327,8 +327,7 @@ public Map getEnvironment() { */ public CopilotClient createClient() { CopilotClientOptions options = new CopilotClientOptions().setGitHubToken(DEFAULT_GITHUB_TOKEN); - applyContextOptions(options); - return new CopilotClient(options); + return createClient(options); } /** @@ -341,7 +340,10 @@ public CopilotClient createClient() { * @return a new CopilotClient */ public CopilotClient createClient(CopilotClientOptions options) { - applyContextOptions(options); + CopilotClient client = applyContextOptions(options); + if (client != null) { + return client; + } if (options.getGitHubToken() == null) { options.setGitHubToken(DEFAULT_GITHUB_TOKEN); } @@ -349,10 +351,17 @@ public CopilotClient createClient(CopilotClientOptions options) { return new CopilotClient(options); } - private void applyContextOptions(CopilotClientOptions options) { + private CopilotClient applyContextOptions(CopilotClientOptions options) { if (isInProcessMode(options)) { - inProcessEnvGuards.add(new InProcessEnvGuard(buildInProcessEnvironment(options))); - return; + InProcessEnvGuard guard = new InProcessEnvGuard(buildInProcessEnvironment(options)); + inProcessEnvGuards.add(guard); + try { + options.setEnvironment(null); + return new CopilotClient(options, guard::close); + } catch (RuntimeException e) { + guard.close(); + throw e; + } } if (options.getCliPath() == null) { options.setCliPath(cliPath); @@ -363,6 +372,7 @@ private void applyContextOptions(CopilotClientOptions options) { if (options.getEnvironment() == null || options.getEnvironment().isEmpty()) { options.setEnvironment(getEnvironment()); } + return null; } private boolean isInProcessMode(CopilotClientOptions options) { diff --git a/java/sdk/src/test/java/com/github/copilot/ExecutorWiringTest.java b/java/sdk/src/test/java/com/github/copilot/ExecutorWiringTest.java index 78764db0fb..a8319475c6 100644 --- a/java/sdk/src/test/java/com/github/copilot/ExecutorWiringTest.java +++ b/java/sdk/src/test/java/com/github/copilot/ExecutorWiringTest.java @@ -86,8 +86,7 @@ int getTaskCount() { } private CopilotClientOptions createOptionsWithExecutor(TrackingExecutor executor) { - CopilotClientOptions options = new CopilotClientOptions().setCliPath(ctx.getCliPath()) - .setCwd(ctx.getWorkDir().toString()).setEnvironment(ctx.getEnvironment()).setExecutor(executor) + CopilotClientOptions options = new CopilotClientOptions().setExecutor(executor) .setGitHubToken("fake-token-for-e2e-tests"); return options; } @@ -111,7 +110,7 @@ void testClientStartUsesProvidedExecutor() throws Exception { TrackingExecutor trackingExecutor = new TrackingExecutor(ForkJoinPool.commonPool()); int beforeStart = trackingExecutor.getTaskCount(); - try (CopilotClient client = new CopilotClient(createOptionsWithExecutor(trackingExecutor))) { + try (CopilotClient client = ctx.createClient(createOptionsWithExecutor(trackingExecutor))) { client.start().get(30, TimeUnit.SECONDS); assertTrue(trackingExecutor.getTaskCount() > beforeStart, @@ -156,7 +155,7 @@ void testToolCallDispatchUsesProvidedExecutor() throws Exception { }); // Reset count after client construction to isolate tool-call dispatch - try (CopilotClient client = new CopilotClient(createOptionsWithExecutor(trackingExecutor))) { + try (CopilotClient client = ctx.createClient(createOptionsWithExecutor(trackingExecutor))) { CopilotSession session = client.createSession(new SessionConfig().setTools(List.of(encryptTool)) .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); @@ -198,7 +197,7 @@ void testPermissionDispatchUsesProvidedExecutor() throws Exception { var config = new SessionConfig().setOnPermissionRequest((request, invocation) -> CompletableFuture .completedFuture(new PermissionRequestResult().setKind(PermissionRequestResultKind.APPROVED))); - try (CopilotClient client = new CopilotClient(createOptionsWithExecutor(trackingExecutor))) { + try (CopilotClient client = ctx.createClient(createOptionsWithExecutor(trackingExecutor))) { CopilotSession session = client.createSession(config).get(); Path testFile = ctx.getWorkDir().resolve("test.txt"); @@ -247,7 +246,7 @@ void testUserInputDispatchUsesProvidedExecutor() throws Exception { .completedFuture(new UserInputResponse().setAnswer(answer).setWasFreeform(wasFreeform)); }); - try (CopilotClient client = new CopilotClient(createOptionsWithExecutor(trackingExecutor))) { + try (CopilotClient client = ctx.createClient(createOptionsWithExecutor(trackingExecutor))) { CopilotSession session = client.createSession(config).get(); int beforeSend = trackingExecutor.getTaskCount(); @@ -286,7 +285,7 @@ void testHooksDispatchUsesProvidedExecutor() throws Exception { .setHooks(new SessionHooks().setOnPreToolUse( (input, invocation) -> CompletableFuture.completedFuture(PreToolUseHookOutput.allow()))); - try (CopilotClient client = new CopilotClient(createOptionsWithExecutor(trackingExecutor))) { + try (CopilotClient client = ctx.createClient(createOptionsWithExecutor(trackingExecutor))) { CopilotSession session = client.createSession(config).get(); Path testFile = ctx.getWorkDir().resolve("hello.txt"); @@ -342,7 +341,7 @@ void testClientStopUsesProvidedExecutor() throws Exception { return CompletableFuture.completedFuture(input.toUpperCase()); }); - CopilotClient client = new CopilotClient(createOptionsWithExecutor(trackingExecutor)); + CopilotClient client = ctx.createClient(createOptionsWithExecutor(trackingExecutor)); client.createSession(new SessionConfig().setTools(List.of(encryptTool)) .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); diff --git a/java/sdk/src/test/java/com/github/copilot/MetadataApiTest.java b/java/sdk/src/test/java/com/github/copilot/MetadataApiTest.java index ec3b9ea707..4f06176d93 100644 --- a/java/sdk/src/test/java/com/github/copilot/MetadataApiTest.java +++ b/java/sdk/src/test/java/com/github/copilot/MetadataApiTest.java @@ -10,6 +10,7 @@ import com.github.copilot.generated.rpc.ModelBillingTokenPrices; import com.github.copilot.generated.rpc.ModelBillingTokenPricesLongContext; import com.github.copilot.rpc.*; +import com.github.copilot.e2e.SkipInProcess; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -271,6 +272,7 @@ void testGetModelsResponseDeserialization() throws Exception { // ===== Integration Tests (require CLI) ===== @Test + @SkipInProcess("Uses explicit CLI stdio transport") void testGetStatus() throws Exception { assertNotNull(cliPath, "Copilot CLI not found in PATH or COPILOT_CLI_PATH"); @@ -287,6 +289,7 @@ void testGetStatus() throws Exception { } @Test + @SkipInProcess("Uses explicit CLI stdio transport") void testGetAuthStatus() throws Exception { assertNotNull(cliPath, "Copilot CLI not found in PATH or COPILOT_CLI_PATH"); @@ -302,6 +305,7 @@ void testGetAuthStatus() throws Exception { } @Test + @SkipInProcess("Uses explicit CLI stdio transport") void testListModels() throws Exception { assertNotNull(cliPath, "Copilot CLI not found in PATH or COPILOT_CLI_PATH"); diff --git a/java/sdk/src/test/java/com/github/copilot/ffi/InProcessEnvGuard.java b/java/sdk/src/test/java/com/github/copilot/ffi/InProcessEnvGuard.java index 12062ada6a..43df713715 100644 --- a/java/sdk/src/test/java/com/github/copilot/ffi/InProcessEnvGuard.java +++ b/java/sdk/src/test/java/com/github/copilot/ffi/InProcessEnvGuard.java @@ -83,6 +83,7 @@ private interface LibcEnv extends Library { * name -> previous value ({@code null} means the variable was not set before). */ private final List> saved = new ArrayList<>(); + private boolean closed; /** * Applies {@code applyEnv} to the native process environment block, saving the @@ -116,7 +117,11 @@ private void apply(String name, String value) { * before construction. */ @Override - public void close() { + public synchronized void close() { + if (closed) { + return; + } + closed = true; List> reversed = new ArrayList<>(saved); Collections.reverse(reversed); for (Map.Entry entry : reversed) {