Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

182 lines
6.1 KiB
Python

"""Tests for the Python sandbox runner."""
from __future__ import annotations
import os
import tempfile
from config.constants import OPENSRE_TMP_DIR, ensure_opensre_tmp_dir
from platform.sandbox.runner import (
MAX_TIMEOUT,
SandboxResult,
run_python_sandbox,
)
class TestSandboxRunnerBasicExecution:
def test_runs_simple_code(self) -> None:
result = run_python_sandbox("print('hello')")
assert result.success
assert result.stdout.strip() == "hello"
assert result.stderr == ""
assert result.exit_code == 0
assert not result.timed_out
def test_captures_stderr(self) -> None:
result = run_python_sandbox("import sys; sys.stderr.write('err\\n')")
assert result.success
assert "err" in result.stderr
def test_captures_exit_code_on_failure(self) -> None:
result = run_python_sandbox("raise ValueError('boom')")
assert not result.success
assert result.exit_code != 0
assert "ValueError" in result.stderr
def test_empty_code_succeeds(self) -> None:
result = run_python_sandbox("")
assert result.success
assert result.stdout == ""
def test_result_stores_original_code(self) -> None:
code = "x = 1 + 1\nprint(x)"
result = run_python_sandbox(code)
assert result.code == code
def test_result_stores_inputs(self) -> None:
inputs = {"threshold": 42}
result = run_python_sandbox("print(inputs['threshold'])", inputs=inputs)
assert result.success
assert result.inputs == inputs
assert "42" in result.stdout
def test_no_inputs_stores_empty_dict(self) -> None:
result = run_python_sandbox("pass")
assert result.inputs == {}
class TestSandboxRunnerInputInjection:
def test_inputs_injected_as_variable(self) -> None:
code = "print(inputs['key'])"
result = run_python_sandbox(code, inputs={"key": "value123"})
assert result.success
assert "value123" in result.stdout
def test_inputs_supports_nested_structures(self) -> None:
code = "print(inputs['data'][0])"
result = run_python_sandbox(code, inputs={"data": [99, 100]})
assert result.success
assert "99" in result.stdout
def test_none_inputs_not_injected(self) -> None:
result = run_python_sandbox("x = 1", inputs=None)
assert result.success
assert result.inputs == {}
class TestSandboxNetworkRestrictions:
def test_socket_creation_blocked(self) -> None:
code = "import socket; socket.socket()"
result = run_python_sandbox(code)
assert not result.success
assert "PermissionError" in result.stderr or "PermissionError" in result.stdout
def test_create_connection_blocked(self) -> None:
code = "import socket; socket.create_connection(('localhost', 80))"
result = run_python_sandbox(code)
assert not result.success
assert "PermissionError" in result.stderr or "PermissionError" in result.stdout
def test_getaddrinfo_blocked(self) -> None:
code = "import socket; socket.getaddrinfo('localhost', 80)"
result = run_python_sandbox(code)
assert not result.success
assert "PermissionError" in result.stderr or "PermissionError" in result.stdout
class TestSandboxFilesystemRestrictions:
def test_read_allowed(self) -> None:
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
f.write("data")
path = f.name
try:
result = run_python_sandbox(f"open({path!r}).read()")
assert result.success
finally:
os.unlink(path)
def test_write_outside_tmp_blocked(self) -> None:
code = "open('/etc/sandbox_test_file', 'w').write('x')"
result = run_python_sandbox(code)
assert not result.success
assert "PermissionError" in result.stderr or "PermissionError" in result.stdout
def test_write_inside_opensre_tmp_allowed(self) -> None:
ensure_opensre_tmp_dir()
target = os.path.join(os.fspath(OPENSRE_TMP_DIR), "sandbox_write_test.txt")
code = f"open({target!r}, 'w').write('ok')"
result = run_python_sandbox(code)
assert result.success
if os.path.exists(target):
os.unlink(target)
def test_append_outside_tmp_blocked(self) -> None:
code = "open('/etc/sandbox_append_test', 'a').write('x')"
result = run_python_sandbox(code)
assert not result.success
assert "PermissionError" in result.stderr or "PermissionError" in result.stdout
class TestSandboxTimeout:
def test_timeout_enforced(self) -> None:
result = run_python_sandbox("import time; time.sleep(10)", timeout=1)
assert not result.success
assert result.timed_out
assert result.exit_code == -1
assert result.error is not None
assert "timed out" in result.error.lower()
def test_timeout_capped_at_max(self) -> None:
result = run_python_sandbox("pass", timeout=MAX_TIMEOUT + 9999)
assert result.success
def test_fast_code_does_not_time_out(self) -> None:
result = run_python_sandbox("print('fast')", timeout=30)
assert result.success
assert not result.timed_out
class TestSandboxResultModel:
def test_success_property_true_on_zero_exit(self) -> None:
r = SandboxResult(
code="",
inputs={},
stdout="",
stderr="",
exit_code=0,
timed_out=False,
)
assert r.success is True
def test_success_property_false_on_nonzero_exit(self) -> None:
r = SandboxResult(
code="",
inputs={},
stdout="",
stderr="",
exit_code=1,
timed_out=False,
)
assert r.success is False
def test_success_property_false_when_timed_out(self) -> None:
r = SandboxResult(
code="",
inputs={},
stdout="",
stderr="",
exit_code=0,
timed_out=True,
)
assert r.success is False