from __future__ import annotations import asyncio from collections.abc import Awaitable, Callable, Sequence from pathlib import Path import pytest import agents.sandbox.session.manifest_application as manifest_application_module from agents.sandbox.entries import ( Dir, File, GCSMount, InContainerMountStrategy, MountpointMountPattern, ) from agents.sandbox.errors import ExecNonZeroError from agents.sandbox.manifest import Manifest from agents.sandbox.materialization import MaterializedFile from agents.sandbox.session.manifest_application import ManifestApplier from agents.sandbox.types import ExecResult, Group, User def _materialized(dest: Path) -> list[MaterializedFile]: return [MaterializedFile(path=dest, sha256=dest.as_posix())] @pytest.mark.asyncio async def test_manifest_applier_only_applies_ephemeral_entries_without_account_provisioning() -> ( None ): mkdir_calls: list[Path] = [] exec_calls: list[tuple[str, ...]] = [] apply_calls: list[tuple[str, Path, Path]] = [] async def mkdir(path: Path) -> None: mkdir_calls.append(path) async def exec_checked_nonzero(*command: str) -> ExecResult: exec_calls.append(command) return ExecResult(stdout=b"", stderr=b"", exit_code=0) async def apply_entry(entry: object, dest: Path, base_dir: Path) -> list[MaterializedFile]: apply_calls.append((type(entry).__name__, dest, base_dir)) return _materialized(dest) applier = ManifestApplier( mkdir=mkdir, exec_checked_nonzero=exec_checked_nonzero, apply_entry=apply_entry, ) manifest = Manifest( root="/workspace", entries={ "keep.txt": File(content=b"keep"), "tmp.txt": File(content=b"tmp", ephemeral=True), }, users=[User(name="alice")], groups=[Group(name="dev", users=[User(name="alice")])], ) result = await applier.apply_manifest(manifest, only_ephemeral=True) assert mkdir_calls == [Path("/workspace")] assert exec_calls == [] assert apply_calls == [("File", Path("/workspace/tmp.txt"), Path("/"))] assert result.files == _materialized(Path("/workspace/tmp.txt")) @pytest.mark.asyncio async def test_manifest_applier_only_ephemeral_reapplies_nested_ephemeral_children() -> None: apply_calls: list[tuple[str, Path, Path]] = [] async def mkdir(_path: Path) -> None: return None async def exec_checked_nonzero(*_command: str) -> ExecResult: return ExecResult(stdout=b"", stderr=b"", exit_code=0) async def apply_entry(entry: object, dest: Path, base_dir: Path) -> list[MaterializedFile]: apply_calls.append((type(entry).__name__, dest, base_dir)) return _materialized(dest) applier = ManifestApplier( mkdir=mkdir, exec_checked_nonzero=exec_checked_nonzero, apply_entry=apply_entry, ) manifest = Manifest( root="/workspace", entries={ "dir": Dir( children={ "keep.txt": File(content=b"keep"), "tmp.txt": File(content=b"tmp", ephemeral=True), } ) }, ) result = await applier.apply_manifest(manifest, only_ephemeral=True) assert apply_calls == [("File", Path("/workspace/dir/tmp.txt"), Path("/"))] assert result.files == _materialized(Path("/workspace/dir/tmp.txt")) @pytest.mark.asyncio async def test_manifest_applier_only_ephemeral_reapplies_full_ephemeral_directories() -> None: applied_entries: list[tuple[object, Path, Path]] = [] async def mkdir(_path: Path) -> None: return None async def exec_checked_nonzero(*_command: str) -> ExecResult: return ExecResult(stdout=b"", stderr=b"", exit_code=0) async def apply_entry(entry: object, dest: Path, base_dir: Path) -> list[MaterializedFile]: applied_entries.append((entry, dest, base_dir)) return _materialized(dest) applier = ManifestApplier( mkdir=mkdir, exec_checked_nonzero=exec_checked_nonzero, apply_entry=apply_entry, ) manifest = Manifest( root="/workspace", entries={ "tmp": Dir( ephemeral=True, children={ "keep.txt": File(content=b"keep"), "nested": Dir(children={"child.txt": File(content=b"child")}), "tmp.txt": File(content=b"tmp", ephemeral=True), }, ) }, ) result = await applier.apply_manifest(manifest, only_ephemeral=True) assert len(applied_entries) == 1 entry, dest, base_dir = applied_entries[0] assert isinstance(entry, Dir) assert dest == Path("/workspace/tmp") assert base_dir == Path("/") assert set(entry.children) == {"keep.txt", "nested", "tmp.txt"} assert result.files == _materialized(Path("/workspace/tmp")) @pytest.mark.asyncio async def test_manifest_applier_respects_explicit_base_dir() -> None: apply_calls: list[tuple[str, Path, Path]] = [] async def mkdir(_path: Path) -> None: return None async def exec_checked_nonzero(*_command: str) -> ExecResult: return ExecResult(stdout=b"", stderr=b"", exit_code=0) async def apply_entry(entry: object, dest: Path, base_dir: Path) -> list[MaterializedFile]: apply_calls.append((type(entry).__name__, dest, base_dir)) return _materialized(dest) applier = ManifestApplier( mkdir=mkdir, exec_checked_nonzero=exec_checked_nonzero, apply_entry=apply_entry, ) manifest = Manifest(entries={"file.txt": File(content=b"hello")}) result = await applier.apply_manifest(manifest, base_dir=Path("/tmp/project")) assert apply_calls == [("File", Path("/workspace/file.txt"), Path("/tmp/project"))] assert result.files == _materialized(Path("/workspace/file.txt")) @pytest.mark.asyncio async def test_manifest_applier_caps_parallel_entry_batch( monkeypatch: pytest.MonkeyPatch, ) -> None: observed_limits: list[int | None] = [] async def gather_with_limit_recording( task_factories: Sequence[Callable[[], Awaitable[list[MaterializedFile]]]], *, max_concurrency: int | None = None, ) -> list[list[MaterializedFile]]: observed_limits.append(max_concurrency) return [await factory() for factory in task_factories] async def mkdir(_path: Path) -> None: return None async def exec_checked_nonzero(*_command: str) -> ExecResult: return ExecResult(stdout=b"", stderr=b"", exit_code=0) async def apply_entry(_entry: object, dest: Path, _base_dir: Path) -> list[MaterializedFile]: return _materialized(dest) monkeypatch.setattr( manifest_application_module, "gather_in_order", gather_with_limit_recording, ) applier = ManifestApplier( mkdir=mkdir, exec_checked_nonzero=exec_checked_nonzero, apply_entry=apply_entry, max_entry_concurrency=2, ) result = await applier.apply_manifest( Manifest(entries={"a.txt": File(content=b"a"), "b.txt": File(content=b"b")}) ) assert observed_limits == [2] assert result.files == [ MaterializedFile(path=Path("/workspace/a.txt"), sha256="/workspace/a.txt"), MaterializedFile(path=Path("/workspace/b.txt"), sha256="/workspace/b.txt"), ] @pytest.mark.asyncio async def test_manifest_applier_provisions_groups_and_unique_users_before_entries() -> None: exec_calls: list[tuple[str, ...]] = [] async def mkdir(_path: Path) -> None: return None async def exec_checked_nonzero(*command: str) -> ExecResult: exec_calls.append(command) return ExecResult(stdout=b"", stderr=b"", exit_code=0) async def apply_entry(_entry: object, _dest: Path, _base_dir: Path) -> list[MaterializedFile]: return [] applier = ManifestApplier( mkdir=mkdir, exec_checked_nonzero=exec_checked_nonzero, apply_entry=apply_entry, ) manifest = Manifest( users=[User(name="alice")], groups=[Group(name="dev", users=[User(name="alice"), User(name="bob")])], ) result = await applier.apply_manifest(manifest) assert result.files == [] assert exec_calls[0] == ("groupadd", "dev") assert exec_calls.count(("groupadd", "alice")) == 0 assert exec_calls.count(("groupadd", "bob")) == 0 assert ("useradd", "-U", "-M", "-s", "/usr/sbin/nologin", "alice") in exec_calls assert ("useradd", "-U", "-M", "-s", "/usr/sbin/nologin", "bob") in exec_calls assert ("usermod", "-aG", "dev", "alice") in exec_calls assert ("usermod", "-aG", "dev", "bob") in exec_calls @pytest.mark.asyncio async def test_manifest_applier_can_apply_full_manifest_without_account_provisioning() -> None: exec_calls: list[tuple[str, ...]] = [] apply_calls: list[tuple[str, Path, Path]] = [] async def mkdir(_path: Path) -> None: return None async def exec_checked_nonzero(*command: str) -> ExecResult: exec_calls.append(command) return ExecResult(stdout=b"", stderr=b"", exit_code=0) async def apply_entry(entry: object, dest: Path, base_dir: Path) -> list[MaterializedFile]: apply_calls.append((type(entry).__name__, dest, base_dir)) return _materialized(dest) applier = ManifestApplier( mkdir=mkdir, exec_checked_nonzero=exec_checked_nonzero, apply_entry=apply_entry, ) manifest = Manifest( entries={"file.txt": File(content=b"hello")}, users=[User(name="alice")], groups=[Group(name="dev", users=[User(name="alice")])], ) result = await applier.apply_manifest(manifest, provision_accounts=False) assert exec_calls == [] assert apply_calls == [("File", Path("/workspace/file.txt"), Path("/"))] assert result.files == _materialized(Path("/workspace/file.txt")) @pytest.mark.asyncio async def test_manifest_applier_raises_with_command_stdout_and_stderr_on_provision_failure() -> ( None ): async def mkdir(_path: Path) -> None: return None async def exec_checked_nonzero(*command: str) -> ExecResult: raise ExecNonZeroError( ExecResult(stdout=b"groupadd output", stderr=b"groupadd failed", exit_code=9), command=command, ) async def apply_entry(_entry: object, _dest: Path, _base_dir: Path) -> list[MaterializedFile]: return [] applier = ManifestApplier( mkdir=mkdir, exec_checked_nonzero=exec_checked_nonzero, apply_entry=apply_entry, ) manifest = Manifest(groups=[Group(name="dev", users=[])]) with pytest.raises(ExecNonZeroError) as exc_info: await applier.apply_manifest(manifest) assert exc_info.value.context["command"] == ("groupadd", "dev") assert exc_info.value.context["command_str"] == "groupadd dev" assert exc_info.value.context["stdout"] == "groupadd output" assert exc_info.value.context["stderr"] == "groupadd failed" assert exc_info.value.message == "stdout: groupadd output\nstderr: groupadd failed" @pytest.mark.asyncio async def test_manifest_applier_raises_without_stream_labels_when_only_stdout_is_present() -> None: async def mkdir(_path: Path) -> None: return None async def exec_checked_nonzero(*command: str) -> ExecResult: raise ExecNonZeroError( ExecResult(stdout=b"useradd unavailable", stderr=b"", exit_code=127), command=command, ) async def apply_entry(_entry: object, _dest: Path, _base_dir: Path) -> list[MaterializedFile]: return [] applier = ManifestApplier( mkdir=mkdir, exec_checked_nonzero=exec_checked_nonzero, apply_entry=apply_entry, ) manifest = Manifest(users=[User(name="sandbox-user")]) with pytest.raises(ExecNonZeroError) as exc_info: await applier.apply_manifest(manifest) assert exc_info.value.context["command_str"] == ( "useradd -U -M -s /usr/sbin/nologin sandbox-user" ) assert exc_info.value.context["stdout"] == "useradd unavailable" assert exc_info.value.context["stderr"] == "" assert exc_info.value.message == "useradd unavailable" @pytest.mark.asyncio async def test_apply_entry_batch_flushes_parallel_work_before_overlapping_paths() -> None: events: list[tuple[str, Path]] = [] async def mkdir(_path: Path) -> None: return None async def exec_checked_nonzero(*_command: str) -> ExecResult: return ExecResult(stdout=b"", stderr=b"", exit_code=0) async def apply_entry(_entry: object, dest: Path, _base_dir: Path) -> list[MaterializedFile]: events.append(("start", dest)) await asyncio.sleep(0) events.append(("end", dest)) return _materialized(dest) applier = ManifestApplier( mkdir=mkdir, exec_checked_nonzero=exec_checked_nonzero, apply_entry=apply_entry, ) destinations = [ Path("/workspace/alpha.txt"), Path("/workspace/beta.txt"), Path("/workspace/nested"), Path("/workspace/nested/child.txt"), ] files = await applier._apply_entry_batch( [ (destinations[0], File(content=b"a")), (destinations[1], File(content=b"b")), (destinations[2], Dir()), (destinations[3], File(content=b"c")), ], base_dir=Path("/"), ) assert [file.path for file in files] == destinations child_start = events.index(("start", destinations[3])) assert events.index(("end", destinations[0])) < child_start assert events.index(("end", destinations[1])) < child_start assert events.index(("end", destinations[2])) < child_start @pytest.mark.asyncio async def test_apply_entry_batch_flushes_before_and_after_mount_entries() -> None: events: list[tuple[str, Path]] = [] async def mkdir(_path: Path) -> None: return None async def exec_checked_nonzero(*_command: str) -> ExecResult: return ExecResult(stdout=b"", stderr=b"", exit_code=0) async def apply_entry(_entry: object, dest: Path, _base_dir: Path) -> list[MaterializedFile]: events.append(("start", dest)) await asyncio.sleep(0) events.append(("end", dest)) return _materialized(dest) applier = ManifestApplier( mkdir=mkdir, exec_checked_nonzero=exec_checked_nonzero, apply_entry=apply_entry, ) destinations = [ Path("/workspace/alpha.txt"), Path("/workspace/beta.txt"), Path("/workspace/mount"), Path("/workspace/gamma.txt"), ] files = await applier._apply_entry_batch( [ (destinations[0], File(content=b"a")), (destinations[1], File(content=b"b")), ( destinations[2], GCSMount( bucket="sandbox-bucket", mount_strategy=InContainerMountStrategy(pattern=MountpointMountPattern()), ), ), (destinations[3], File(content=b"c")), ], base_dir=Path("/"), ) assert [file.path for file in files] == destinations mount_start = events.index(("start", destinations[2])) gamma_start = events.index(("start", destinations[3])) assert events.index(("end", destinations[0])) < mount_start assert events.index(("end", destinations[1])) < mount_start assert events.index(("end", destinations[2])) < gamma_start