mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 05:43:14 +00:00
Applies the plan's C5 step: git mv every test per the domain-reorg
move-map (movemap-m50.txt) into tests/{agent,analyzer,auth,dashboard,
github,middleware,models,reviewer,sandbox,slack,tools,webhooks}/, plus
the 13 fork-only placements from the scoping report §2c (Atlassian
webhook tests -> tests/webhooks/, test_atlassian_connect.py and
test_auth_error_leak.py -> tests/auth/, jira/confluence util tests ->
tests/tools/, test_repo_binding_isolation.py -> tests/sandbox/,
bot-identity/autofix tests -> tests/github/).
Path-only move: the only content edits are parents[1] -> parents[2]
fixes in test_e2b_integration.py and test_daytona_integration.py,
required because their __file__-relative ROOT path gained one more
directory level in the move.
Monkeypatch retargets for these files were already completed in C4;
none remained outstanding here.
151 lines
5.2 KiB
Python
151 lines
5.2 KiB
Python
"""Client-side execution deadline for the LangSmith sandbox backend."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from types import SimpleNamespace
|
|
from typing import Any
|
|
|
|
import pytest
|
|
from langsmith.sandbox import CommandTimeoutError, SandboxConnectionError
|
|
|
|
from agent.integrations.langsmith import TimeoutLangSmithSandbox
|
|
|
|
|
|
class _FakeHandle:
|
|
def __init__(self, *, sleep: float = 0.0, result: Any = None, raises: Exception | None = None):
|
|
self._sleep = sleep
|
|
self._result = result
|
|
self._raises = raises
|
|
self.killed = False
|
|
|
|
@property
|
|
def result(self) -> Any:
|
|
if self._sleep:
|
|
time.sleep(self._sleep)
|
|
if self._raises is not None:
|
|
raise self._raises
|
|
return self._result
|
|
|
|
def kill(self) -> None:
|
|
self.killed = True
|
|
|
|
|
|
class _FakeSandbox:
|
|
def __init__(self, handle: _FakeHandle, *, run_raises: Exception | None = None):
|
|
self._handle = handle
|
|
self._run_raises = run_raises
|
|
self.run_calls: list[dict[str, Any]] = []
|
|
|
|
def run(self, command: str, *, timeout: int, wait: bool) -> _FakeHandle:
|
|
self.run_calls.append({"command": command, "timeout": timeout, "wait": wait})
|
|
if self._run_raises is not None:
|
|
raise self._run_raises
|
|
return self._handle
|
|
|
|
|
|
def _backend(
|
|
handle: _FakeHandle, *, run_raises: Exception | None = None
|
|
) -> TimeoutLangSmithSandbox:
|
|
sb = TimeoutLangSmithSandbox.__new__(TimeoutLangSmithSandbox)
|
|
sb._sandbox = _FakeSandbox(handle, run_raises=run_raises)
|
|
sb._default_timeout = 30 * 60
|
|
return sb
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _no_grace(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("SANDBOX_EXECUTE_CLIENT_GRACE_SECONDS", "0")
|
|
|
|
|
|
async def test_aexecute_kills_on_client_timeout() -> None:
|
|
handle = _FakeHandle(sleep=5.0)
|
|
sb = _backend(handle)
|
|
start = time.monotonic()
|
|
resp = await sb.aexecute("sleep 999", timeout=1)
|
|
assert time.monotonic() - start < 3.0
|
|
assert resp.exit_code == 124
|
|
assert "killed" in resp.output
|
|
assert handle.killed
|
|
assert sb._sandbox.run_calls[0]["wait"] is False
|
|
|
|
|
|
async def test_aexecute_success_combines_streams() -> None:
|
|
handle = _FakeHandle(result=SimpleNamespace(stdout="out", stderr="err", exit_code=0))
|
|
sb = _backend(handle)
|
|
resp = await sb.aexecute("echo hi", timeout=5)
|
|
assert resp.exit_code == 0
|
|
assert resp.output == "out\nerr"
|
|
assert not handle.killed
|
|
|
|
|
|
async def test_aexecute_server_timeout_not_killed() -> None:
|
|
handle = _FakeHandle(raises=CommandTimeoutError("server enforced"))
|
|
sb = _backend(handle)
|
|
resp = await sb.aexecute("make hang", timeout=2)
|
|
assert resp.exit_code == 124
|
|
assert "on the sandbox" in resp.output
|
|
assert not handle.killed
|
|
|
|
|
|
def test_execute_kills_on_client_timeout() -> None:
|
|
handle = _FakeHandle(sleep=5.0)
|
|
sb = _backend(handle)
|
|
start = time.monotonic()
|
|
resp = sb.execute("sleep 999", timeout=1)
|
|
assert time.monotonic() - start < 3.0
|
|
assert resp.exit_code == 124
|
|
assert handle.killed
|
|
|
|
|
|
def _patch_base_execute(monkeypatch: pytest.MonkeyPatch, sink: dict[str, Any]) -> None:
|
|
def fake_base_execute(self: Any, command: str, *, timeout: int | None = None) -> Any:
|
|
sink["command"] = command
|
|
sink["timeout"] = timeout
|
|
return SimpleNamespace(output="via-http", exit_code=0, truncated=False)
|
|
|
|
monkeypatch.setattr("agent.integrations.langsmith.LangSmithSandbox.execute", fake_base_execute)
|
|
|
|
|
|
async def test_aexecute_ws_connect_failure_falls_back_to_base(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
# run(wait=False) connects eagerly, so a connect failure raises from run().
|
|
sb = _backend(_FakeHandle(), run_raises=SandboxConnectionError("no ws"))
|
|
called: dict[str, Any] = {}
|
|
_patch_base_execute(monkeypatch, called)
|
|
resp = await sb.aexecute("git status", timeout=5)
|
|
assert called == {"command": "git status", "timeout": 5}
|
|
assert resp.output == "via-http"
|
|
|
|
|
|
async def test_aexecute_ws_connect_timeout_falls_back_to_base(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
sb = _backend(_FakeHandle(), run_raises=TimeoutError("connect timed out"))
|
|
called: dict[str, Any] = {}
|
|
_patch_base_execute(monkeypatch, called)
|
|
resp = await sb.aexecute("git status", timeout=5)
|
|
assert called["command"] == "git status"
|
|
assert resp.output == "via-http"
|
|
|
|
|
|
def test_execute_ws_connect_failure_falls_back_to_base(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
sb = _backend(_FakeHandle(), run_raises=SandboxConnectionError("no ws"))
|
|
called: dict[str, Any] = {}
|
|
_patch_base_execute(monkeypatch, called)
|
|
resp = sb.execute("git status", timeout=5)
|
|
assert called == {"command": "git status", "timeout": 5}
|
|
assert resp.output == "via-http"
|
|
|
|
|
|
async def test_aexecute_midstream_ws_drop_falls_back_to_base(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
# Connect succeeds (run returns a handle) but the stream drops while draining.
|
|
sb = _backend(_FakeHandle(raises=SandboxConnectionError("dropped")))
|
|
called: dict[str, Any] = {}
|
|
_patch_base_execute(monkeypatch, called)
|
|
resp = await sb.aexecute("git status", timeout=5)
|
|
assert called["command"] == "git status"
|
|
assert resp.output == "via-http"
|