chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import types
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
|
||||
from agents.sandbox import Manifest
|
||||
from agents.sandbox.entries import RcloneMountPattern, S3Mount
|
||||
from agents.sandbox.errors import MountConfigError
|
||||
from agents.sandbox.session.base_sandbox_session import BaseSandboxSession
|
||||
from agents.sandbox.types import ExecResult
|
||||
|
||||
|
||||
class _FakeRunloopMountSession(BaseSandboxSession):
|
||||
def __init__(self, results: list[ExecResult] | None = None) -> None:
|
||||
self.state = cast(
|
||||
Any,
|
||||
types.SimpleNamespace(
|
||||
session_id=uuid.uuid4(),
|
||||
manifest=Manifest(root="/workspace"),
|
||||
),
|
||||
)
|
||||
self._results = list(results or [])
|
||||
self.exec_calls: list[str] = []
|
||||
|
||||
async def _exec_internal(
|
||||
self,
|
||||
*command: str | Path,
|
||||
timeout: float | None = None,
|
||||
) -> ExecResult:
|
||||
_ = timeout
|
||||
cmd_str = " ".join(str(c) for c in command)
|
||||
self.exec_calls.append(cmd_str)
|
||||
if self._results:
|
||||
return self._results.pop(0)
|
||||
return ExecResult(stdout=b"", stderr=b"", exit_code=0)
|
||||
|
||||
async def read(self, path: Path, *, user: object = None) -> io.IOBase:
|
||||
_ = (path, user)
|
||||
return io.BytesIO(b"")
|
||||
|
||||
async def write(self, path: Path, data: io.IOBase, *, user: object = None) -> None:
|
||||
_ = (path, data, user)
|
||||
|
||||
async def persist_workspace(self) -> io.IOBase:
|
||||
raise AssertionError("not expected")
|
||||
|
||||
async def hydrate_workspace(self, data: io.IOBase) -> None:
|
||||
_ = data
|
||||
raise AssertionError("not expected")
|
||||
|
||||
async def running(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
_FakeRunloopMountSession.__name__ = "RunloopSandboxSession"
|
||||
|
||||
|
||||
def _exec_ok(stdout: bytes = b"") -> ExecResult:
|
||||
return ExecResult(stdout=stdout, stderr=b"", exit_code=0)
|
||||
|
||||
|
||||
def _exec_fail() -> ExecResult:
|
||||
return ExecResult(stdout=b"", stderr=b"", exit_code=1)
|
||||
|
||||
|
||||
def test_runloop_package_re_exports_cloud_bucket_strategy() -> None:
|
||||
package_module = __import__(
|
||||
"agents.extensions.sandbox.runloop",
|
||||
fromlist=["RunloopCloudBucketMountStrategy"],
|
||||
)
|
||||
|
||||
assert hasattr(package_module, "RunloopCloudBucketMountStrategy")
|
||||
|
||||
|
||||
def test_runloop_extension_re_exports_cloud_bucket_strategy() -> None:
|
||||
package_module = __import__(
|
||||
"agents.extensions.sandbox",
|
||||
fromlist=["RunloopCloudBucketMountStrategy"],
|
||||
)
|
||||
|
||||
assert hasattr(package_module, "RunloopCloudBucketMountStrategy")
|
||||
|
||||
|
||||
def test_runloop_mount_strategy_type_and_default_pattern() -> None:
|
||||
from agents.extensions.sandbox.runloop.mounts import RunloopCloudBucketMountStrategy
|
||||
|
||||
strategy = RunloopCloudBucketMountStrategy()
|
||||
|
||||
assert strategy.type == "runloop_cloud_bucket"
|
||||
assert isinstance(strategy.pattern, RcloneMountPattern)
|
||||
assert strategy.pattern.mode == "fuse"
|
||||
|
||||
|
||||
def test_runloop_mount_strategy_round_trips_through_manifest() -> None:
|
||||
from agents.extensions.sandbox.runloop.mounts import RunloopCloudBucketMountStrategy
|
||||
|
||||
manifest = Manifest.model_validate(
|
||||
{
|
||||
"root": "/workspace",
|
||||
"entries": {
|
||||
"bucket": {
|
||||
"type": "s3_mount",
|
||||
"bucket": "my-bucket",
|
||||
"mount_strategy": {"type": "runloop_cloud_bucket"},
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
mount = manifest.entries["bucket"]
|
||||
assert isinstance(mount, S3Mount)
|
||||
assert isinstance(mount.mount_strategy, RunloopCloudBucketMountStrategy)
|
||||
|
||||
|
||||
def test_runloop_session_guard_rejects_wrong_type() -> None:
|
||||
from agents.extensions.sandbox.runloop.mounts import _assert_runloop_session
|
||||
|
||||
class _WrongSession:
|
||||
pass
|
||||
|
||||
with pytest.raises(MountConfigError, match="RunloopSandboxSession"):
|
||||
_assert_runloop_session(_WrongSession()) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_runloop_session_guard_accepts_correct_type() -> None:
|
||||
from agents.extensions.sandbox.runloop.mounts import _assert_runloop_session
|
||||
|
||||
_assert_runloop_session(_FakeRunloopMountSession())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_runloop_ensure_rclone_installs_with_root_apt() -> None:
|
||||
from agents.extensions.sandbox._rclone import ensure_rclone
|
||||
|
||||
session = _FakeRunloopMountSession(
|
||||
[
|
||||
_exec_fail(),
|
||||
_exec_ok(),
|
||||
_exec_ok(),
|
||||
_exec_ok(),
|
||||
_exec_ok(),
|
||||
]
|
||||
)
|
||||
|
||||
await ensure_rclone(session)
|
||||
|
||||
assert session.exec_calls[:2] == [
|
||||
"sh -lc command -v rclone >/dev/null 2>&1 || test -x /usr/local/bin/rclone",
|
||||
"sh -lc command -v apt-get >/dev/null 2>&1",
|
||||
]
|
||||
assert session.exec_calls[2] == (
|
||||
"sudo -u root -- sh -lc DEBIAN_FRONTEND=noninteractive "
|
||||
"DEBCONF_NOWARNINGS=yes apt-get -o Dpkg::Use-Pty=0 update -qq"
|
||||
)
|
||||
assert session.exec_calls[3] == (
|
||||
"sudo -u root -- sh -lc DEBIAN_FRONTEND=noninteractive "
|
||||
"DEBCONF_NOWARNINGS=yes apt-get -o Dpkg::Use-Pty=0 install -y -qq "
|
||||
"curl unzip ca-certificates"
|
||||
)
|
||||
assert (
|
||||
session.exec_calls[4]
|
||||
== "sudo -u root -- sh -lc curl -fsSL https://rclone.org/install.sh | bash"
|
||||
)
|
||||
assert session.exec_calls[5] == (
|
||||
"sh -lc command -v rclone >/dev/null 2>&1 || test -x /usr/local/bin/rclone"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_runloop_ensure_fuse_installs_missing_fusermount() -> None:
|
||||
from agents.extensions.sandbox.runloop.mounts import _ensure_fuse_support
|
||||
|
||||
session = _FakeRunloopMountSession(
|
||||
[
|
||||
_exec_ok(),
|
||||
_exec_ok(),
|
||||
_exec_fail(),
|
||||
_exec_ok(),
|
||||
_exec_ok(),
|
||||
_exec_ok(),
|
||||
_exec_ok(),
|
||||
_exec_ok(),
|
||||
]
|
||||
)
|
||||
|
||||
await _ensure_fuse_support(session)
|
||||
|
||||
assert session.exec_calls == [
|
||||
"sh -lc test -c /dev/fuse",
|
||||
"sh -lc grep -qw fuse /proc/filesystems",
|
||||
"sh -lc command -v fusermount3 >/dev/null 2>&1 || command -v fusermount >/dev/null 2>&1",
|
||||
"sh -lc command -v apt-get >/dev/null 2>&1",
|
||||
(
|
||||
"sudo -u root -- sh -lc DEBIAN_FRONTEND=noninteractive "
|
||||
"DEBCONF_NOWARNINGS=yes apt-get -o Dpkg::Use-Pty=0 update -qq"
|
||||
),
|
||||
(
|
||||
"sudo -u root -- sh -lc DEBIAN_FRONTEND=noninteractive "
|
||||
"DEBCONF_NOWARNINGS=yes apt-get -o Dpkg::Use-Pty=0 install -y -qq fuse3"
|
||||
),
|
||||
"sh -lc command -v fusermount3 >/dev/null 2>&1 || command -v fusermount >/dev/null 2>&1",
|
||||
(
|
||||
"sudo -u root -- sh -lc chmod a+rw /dev/fuse && "
|
||||
"touch /etc/fuse.conf && "
|
||||
"(grep -qxF user_allow_other /etc/fuse.conf || "
|
||||
"printf '\\nuser_allow_other\\n' >> /etc/fuse.conf)"
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_runloop_rclone_pattern_adds_fuse_access_args() -> None:
|
||||
from agents.extensions.sandbox._rclone import rclone_pattern_for_session
|
||||
|
||||
session = _FakeRunloopMountSession([_exec_ok(stdout=b"1000\n1000\n")])
|
||||
|
||||
pattern = await rclone_pattern_for_session(session, RcloneMountPattern(mode="fuse"))
|
||||
|
||||
assert pattern.extra_args == ["--allow-other", "--uid", "1000", "--gid", "1000"]
|
||||
Reference in New Issue
Block a user