Files
wehub-resource-sync 0418dc5cf9
CI / Python tests (3.10) (push) Has been cancelled
CI / Python tests (3.11) (push) Has been cancelled
CI / Python quality (push) Has been cancelled
CI / Frontend typecheck (push) Has been cancelled
Autopilot Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:45:00 +08:00

294 lines
9.7 KiB
Python

"""Tests for the Docker sandbox backend."""
from __future__ import annotations
import asyncio
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock
import pytest
from openharness.config.settings import (
DockerSandboxSettings,
SandboxNetworkSettings,
SandboxSettings,
Settings,
)
from openharness.sandbox.docker_backend import (
DockerSandboxSession,
get_docker_availability,
)
# ---------------------------------------------------------------------------
# get_docker_availability
# ---------------------------------------------------------------------------
def test_docker_availability_disabled_when_backend_is_srt():
settings = Settings(sandbox=SandboxSettings(enabled=True, backend="srt"))
result = get_docker_availability(settings)
assert result.available is False
assert result.enabled is False
def test_docker_availability_disabled_when_sandbox_off():
settings = Settings(sandbox=SandboxSettings(enabled=False, backend="docker"))
result = get_docker_availability(settings)
assert result.available is False
def test_docker_availability_when_not_installed(monkeypatch):
settings = Settings(sandbox=SandboxSettings(enabled=True, backend="docker"))
monkeypatch.setattr("openharness.sandbox.docker_backend.get_platform", lambda: "linux")
monkeypatch.setattr("openharness.sandbox.docker_backend.shutil.which", lambda name: None)
result = get_docker_availability(settings)
assert result.available is False
assert "not found" in (result.reason or "")
def test_docker_availability_when_daemon_not_running(monkeypatch):
settings = Settings(sandbox=SandboxSettings(enabled=True, backend="docker"))
monkeypatch.setattr("openharness.sandbox.docker_backend.get_platform", lambda: "linux")
monkeypatch.setattr(
"openharness.sandbox.docker_backend.shutil.which",
lambda name: "/usr/bin/docker",
)
import subprocess
monkeypatch.setattr(
"openharness.sandbox.docker_backend.subprocess.run",
MagicMock(side_effect=subprocess.CalledProcessError(1, "docker info")),
)
result = get_docker_availability(settings)
assert result.available is False
assert "not running" in (result.reason or "")
def test_docker_availability_when_platform_unsupported(monkeypatch):
settings = Settings(sandbox=SandboxSettings(enabled=True, backend="docker"))
monkeypatch.setattr("openharness.sandbox.docker_backend.get_platform", lambda: "windows")
result = get_docker_availability(settings)
assert result.available is False
assert "not supported" in (result.reason or "")
def test_docker_availability_when_all_ok(monkeypatch):
settings = Settings(sandbox=SandboxSettings(enabled=True, backend="docker"))
monkeypatch.setattr("openharness.sandbox.docker_backend.get_platform", lambda: "linux")
monkeypatch.setattr(
"openharness.sandbox.docker_backend.shutil.which",
lambda name: "/usr/bin/docker",
)
monkeypatch.setattr(
"openharness.sandbox.docker_backend.subprocess.run",
MagicMock(return_value=MagicMock(returncode=0)),
)
result = get_docker_availability(settings)
assert result.available is True
assert result.enabled is True
# ---------------------------------------------------------------------------
# DockerSandboxSession._build_run_argv
# ---------------------------------------------------------------------------
def test_container_start_builds_correct_docker_args(monkeypatch):
monkeypatch.setattr(
"openharness.sandbox.docker_backend.shutil.which",
lambda name: "/usr/bin/docker",
)
settings = Settings(sandbox=SandboxSettings(enabled=True, backend="docker"))
session = DockerSandboxSession(settings=settings, session_id="abc123", cwd=Path("/repo"))
argv = session._build_run_argv()
assert argv[0] == "/usr/bin/docker"
assert "run" in argv
assert "--rm" in argv
assert "--name" in argv
name_idx = argv.index("--name")
assert argv[name_idx + 1] == "openharness-sandbox-abc123"
assert "tail" in argv
assert "-f" in argv
assert "/dev/null" in argv
def test_network_none_by_default(monkeypatch):
monkeypatch.setattr(
"openharness.sandbox.docker_backend.shutil.which",
lambda name: "/usr/bin/docker",
)
settings = Settings(sandbox=SandboxSettings(enabled=True, backend="docker"))
session = DockerSandboxSession(settings=settings, session_id="abc", cwd=Path("/repo"))
argv = session._build_run_argv()
net_idx = argv.index("--network")
assert argv[net_idx + 1] == "none"
def test_network_none_and_warning_when_domain_policy_is_configured(monkeypatch, caplog):
monkeypatch.setattr(
"openharness.sandbox.docker_backend.shutil.which",
lambda name: "/usr/bin/docker",
)
settings = Settings(
sandbox=SandboxSettings(
enabled=True,
backend="docker",
network=SandboxNetworkSettings(
allowed_domains=["github.com"],
denied_domains=["example.com"],
),
)
)
session = DockerSandboxSession(settings=settings, session_id="abc", cwd=Path("/repo"))
argv = session._build_run_argv()
net_idx = argv.index("--network")
assert argv[net_idx + 1] == "none"
assert "does not enforce allowed_domains/denied_domains yet" in caplog.text
def test_resource_limits_applied(monkeypatch):
monkeypatch.setattr(
"openharness.sandbox.docker_backend.shutil.which",
lambda name: "/usr/bin/docker",
)
settings = Settings(
sandbox=SandboxSettings(
enabled=True,
backend="docker",
docker=DockerSandboxSettings(cpu_limit=2.0, memory_limit="4g"),
)
)
session = DockerSandboxSession(settings=settings, session_id="abc", cwd=Path("/repo"))
argv = session._build_run_argv()
cpus_idx = argv.index("--cpus")
assert argv[cpus_idx + 1] == "2.0"
mem_idx = argv.index("--memory")
assert argv[mem_idx + 1] == "4g"
def test_resource_limits_omitted_when_zero(monkeypatch):
monkeypatch.setattr(
"openharness.sandbox.docker_backend.shutil.which",
lambda name: "/usr/bin/docker",
)
settings = Settings(sandbox=SandboxSettings(enabled=True, backend="docker"))
session = DockerSandboxSession(settings=settings, session_id="abc", cwd=Path("/repo"))
argv = session._build_run_argv()
assert "--cpus" not in argv
assert "--memory" not in argv
def test_bind_mount_uses_same_path(monkeypatch):
monkeypatch.setattr(
"openharness.sandbox.docker_backend.shutil.which",
lambda name: "/usr/bin/docker",
)
settings = Settings(sandbox=SandboxSettings(enabled=True, backend="docker"))
cwd = Path("/home/user/project")
session = DockerSandboxSession(settings=settings, session_id="abc", cwd=cwd)
argv = session._build_run_argv()
resolved = str(cwd.resolve())
assert f"{resolved}:{resolved}" in argv
# ---------------------------------------------------------------------------
# exec_command
# ---------------------------------------------------------------------------
async def test_exec_command_delegates_to_docker_exec(monkeypatch):
monkeypatch.setattr(
"openharness.sandbox.docker_backend.shutil.which",
lambda name: "/usr/bin/docker",
)
settings = Settings(sandbox=SandboxSettings(enabled=True, backend="docker"))
session = DockerSandboxSession(settings=settings, session_id="abc", cwd=Path("/repo"))
session._running = True
captured_args: list[str] = []
async def fake_create_subprocess_exec(*args, **kwargs):
captured_args.extend(args)
mock_proc = MagicMock()
mock_proc.returncode = 0
return mock_proc
monkeypatch.setattr(asyncio, "create_subprocess_exec", fake_create_subprocess_exec)
await session.exec_command(
["bash", "-lc", "echo hello"],
cwd="/repo",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
assert captured_args[0] == "/usr/bin/docker"
assert captured_args[1] == "exec"
assert "openharness-sandbox-abc" in captured_args
assert "bash" in captured_args
async def test_exec_command_raises_when_not_running(monkeypatch):
monkeypatch.setattr(
"openharness.sandbox.docker_backend.shutil.which",
lambda name: "/usr/bin/docker",
)
settings = Settings(sandbox=SandboxSettings(enabled=True, backend="docker"))
session = DockerSandboxSession(settings=settings, session_id="abc", cwd=Path("/repo"))
# _running is False by default
from openharness.sandbox.adapter import SandboxUnavailableError
with pytest.raises(SandboxUnavailableError):
await session.exec_command(["echo", "hi"], cwd="/repo")
# ---------------------------------------------------------------------------
# stop
# ---------------------------------------------------------------------------
async def test_stop_calls_docker_stop(monkeypatch):
monkeypatch.setattr(
"openharness.sandbox.docker_backend.shutil.which",
lambda name: "/usr/bin/docker",
)
settings = Settings(sandbox=SandboxSettings(enabled=True, backend="docker"))
session = DockerSandboxSession(settings=settings, session_id="abc", cwd=Path("/repo"))
session._running = True
captured: list[str] = []
async def fake_create_subprocess_exec(*args, **kwargs):
captured.extend(args)
mock_proc = MagicMock()
mock_proc.communicate = AsyncMock(return_value=(b"", b""))
mock_proc.returncode = 0
return mock_proc
monkeypatch.setattr(asyncio, "create_subprocess_exec", fake_create_subprocess_exec)
await session.stop()
assert "stop" in captured
assert "openharness-sandbox-abc" in captured
assert session.is_running is False