Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/java-sdk-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ jobs:
mvn -B -pl copilot-native deploy -Prelease -DskipTests \
-Dcopilot.native.libc=glibc \
-Dcopilot.native.test.local.publication=true \
-DskipPublishing=true \
"-Dcopilot.native.external.linux.arm64.classifier.path=$LINUX_ARM64_JAR" \
"-Dcopilot.native.external.win32.classifier.path=$WINDOWS_JAR" \
"-Dcopilot.native.external.win32.arm64.classifier.path=$WINDOWS_ARM64_JAR" \
Expand Down
16 changes: 10 additions & 6 deletions go/internal/e2e/rpc_shell_and_fleet_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@ func TestRPCShellAndFleetE2E(t *testing.T) {
t.Fatalf("Failed to create session: %v", err)
}

markerPath := filepath.Join(ctx.WorkDir, "shell-rpc-"+randomHex(t)+".txt")
commandDir := filepath.Join(ctx.WorkDir, "shell-rpc-"+randomHex(t))
if err := os.Mkdir(commandDir, 0755); err != nil {
t.Fatalf("Failed to create shell command directory: %v", err)
}
markerPath := filepath.Join(commandDir, "marker.txt")
const marker = "copilot-sdk-shell-rpc"

cwd := ctx.WorkDir
cwd := commandDir
result, err := session.RPC.Shell.Exec(t.Context(), &rpc.ShellExecRequest{
Command: writeFileCommand(markerPath, marker),
Command: writeFileCommand(filepath.Base(markerPath), marker),
Cwd: &cwd,
})
if err != nil {
Expand Down Expand Up @@ -174,11 +178,11 @@ func randomHex(t *testing.T) string {
return hex.EncodeToString(buf[:])
}

func writeFileCommand(markerPath, marker string) string {
func writeFileCommand(markerName, marker string) string {
if runtime.GOOS == "windows" {
return fmt.Sprintf("powershell -NoLogo -NoProfile -Command \"Set-Content -LiteralPath '%s' -Value '%s'\"", markerPath, marker)
return fmt.Sprintf("echo %s>\"%s\"", marker, markerName)
}
return fmt.Sprintf("sh -c \"printf '%%s' '%s' > '%s'\"", marker, markerPath)
return fmt.Sprintf("sh -c \"printf '%%s' '%s' > '%s'\"", marker, markerName)
}

func waitForFileText(t *testing.T, path, expected string) {
Expand Down
9 changes: 9 additions & 0 deletions go/internal/e2e/testharness/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,15 @@ func NewTestContext(t *testing.T) *TestContext {
os.RemoveAll(workDir)
t.Fatalf("Failed to start proxy: %v", err)
}
// Initialize the proxy before any client can start runtime requests. Tests that
// use a snapshot replace this empty configuration before model traffic begins.
dummySnapshotPath := filepath.Join(workDir, "__no_snapshot__.yaml")
if err := proxy.Configure(dummySnapshotPath, workDir); err != nil {
proxy.StopWithOptions(true)
os.RemoveAll(homeDir)
os.RemoveAll(workDir)
t.Fatalf("Failed to initialize proxy: %v", err)
}
if err := proxy.SetCopilotUserByToken(defaultGitHubToken, map[string]interface{}{
"login": "e2e-test-user",
"copilot_plan": "individual_pro",
Expand Down
8 changes: 5 additions & 3 deletions python/copilot/_cli_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import tempfile
import time
import zipfile
from http.client import IncompleteRead
from pathlib import Path, PurePosixPath
from urllib.error import HTTPError, URLError
from urllib.request import urlopen
Expand All @@ -43,6 +44,7 @@

_CACHE_DIR_NAME = "github-copilot-sdk"
_MAX_RETRIES = 3
_RETRIABLE_DOWNLOAD_ERRORS = (HTTPError, URLError, IncompleteRead)


def _sanitize_version(version: str) -> str:
Expand Down Expand Up @@ -128,7 +130,7 @@ def _fetch_checksums(version: str) -> dict[str, str]:
with urlopen(url, timeout=30) as response:
text = response.read().decode("utf-8")
break
except (HTTPError, URLError) as exc:
except _RETRIABLE_DOWNLOAD_ERRORS as exc:
last_exc = exc
if attempt < _MAX_RETRIES - 1:
time.sleep(2**attempt)
Expand Down Expand Up @@ -255,7 +257,7 @@ def download_cli(version: str | None = None, *, force: bool = False) -> str:
with urlopen(url, timeout=120) as response:
data = response.read()
break
except (HTTPError, URLError) as exc:
except _RETRIABLE_DOWNLOAD_ERRORS as exc:
last_exc = exc
if attempt < _MAX_RETRIES - 1:
time.sleep(2**attempt)
Expand Down Expand Up @@ -315,7 +317,7 @@ def _fetch_url_bytes(url: str, *, timeout: int) -> bytes:
try:
with urlopen(url, timeout=timeout) as response:
return response.read()
except (HTTPError, URLError) as exc:
except _RETRIABLE_DOWNLOAD_ERRORS as exc:
last_exc = exc
if attempt < _MAX_RETRIES - 1:
time.sleep(2**attempt)
Expand Down
23 changes: 22 additions & 1 deletion python/test_cli_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import io
import os
import tarfile
from unittest.mock import patch
from http.client import IncompleteRead
from unittest.mock import MagicMock, patch

import pytest

Expand Down Expand Up @@ -41,6 +42,26 @@ def _runtime_package(npm_platform: str) -> bytes:
return buffer.getvalue()


def test_fetch_url_bytes_retries_truncated_response():
truncated_response = MagicMock()
truncated_response.__enter__.return_value.read.side_effect = IncompleteRead(b"partial", 4)
complete_response = MagicMock()
complete_response.__enter__.return_value.read.return_value = b"complete"

with (
patch.object(
_cli_download,
"urlopen",
side_effect=[truncated_response, complete_response],
) as urlopen,
patch.object(_cli_download.time, "sleep") as sleep,
):
assert _cli_download._fetch_url_bytes("https://example/runtime", timeout=30) == b"complete"

assert urlopen.call_count == 2
sleep.assert_called_once_with(1)


class TestVerifyIntegrity:
def test_accepts_matching_checksum(self):
data = b"native-library-bytes"
Expand Down
5 changes: 2 additions & 3 deletions test/harness/replayingCapiProxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,7 @@ Always include PINEAPPLE_COCONUT_42.
}
});

test("matches semantically equivalent interrupted shell results", async () => {
test("matches semantically equivalent interrupted tool results", async () => {
const originalShellConfig =
process.platform === "win32"
? ShellConfig.powerShell
Expand Down Expand Up @@ -1056,8 +1056,7 @@ Always include PINEAPPLE_COCONUT_42.
{
role: "tool",
tool_call_id: "runtime-call-id",
content:
"<shell context is being reconfigured; retry the command>",
content: "Session aborted",
},
],
},
Expand Down
2 changes: 1 addition & 1 deletion test/harness/replayingCapiProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1556,7 +1556,7 @@ function normalizeAvailableToolNames(result: string): string {

function normalizeInterruptedToolResult(result: string): string {
return result.replace(
/^(?:Failed to execute `[^`]+` tool(?: with arguments: [\s\S]*?)? due to error: (?:Error: )?Session aborted|<shell context is being reconfigured; retry the command>|unknown attachedShellSession handle \d+)$/,
/^(?:Session aborted|Failed to execute `[^`]+` tool(?: with arguments: [\s\S]*?)? due to error: (?:Error: )?Session aborted|<shell context is being reconfigured; retry the command>|unknown attachedShellSession handle \d+)$/,
"The execution of this tool, or a previous tool was interrupted.",
);
}
Expand Down
Loading