This commit is contained in:
+55
-65
@@ -9,7 +9,7 @@ from agentci.integrations.development import (
|
||||
)
|
||||
|
||||
|
||||
def environment(
|
||||
def development_environment(
|
||||
tmp_path: Path, scripts: list[str], *, timeout_seconds: int = 5
|
||||
) -> DevelopmentEnvironment:
|
||||
scripts_dir = tmp_path / "scripts"
|
||||
@@ -24,22 +24,29 @@ def environment(
|
||||
)
|
||||
|
||||
|
||||
def script(path: Path, body: str) -> None:
|
||||
def shell_script(path: Path, body: str, *, executable: bool = True) -> None:
|
||||
path.write_text(f"#!/bin/sh\nset -eu\n{body}\n")
|
||||
path.chmod(0o755)
|
||||
path.chmod(0o755 if executable else 0o644)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def workspace(tmp_path: Path) -> Path:
|
||||
path = tmp_path / "workspace"
|
||||
path.mkdir()
|
||||
return path
|
||||
|
||||
|
||||
async def test_runs_custom_scripts_in_order_with_sanitized_environment(
|
||||
tmp_path, monkeypatch
|
||||
tmp_path: Path,
|
||||
workspace: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
workspace = tmp_path / "workspace"
|
||||
workspace.mkdir()
|
||||
development = environment(tmp_path, ["first", "second"])
|
||||
script(
|
||||
development = development_environment(tmp_path, ["first", "second"])
|
||||
shell_script(
|
||||
development.scripts_dir / "first",
|
||||
'printf \'first:%s:%s\\n\' "$DEV_TOOLS_DIR" "${AGENTCI_SECRET-unset}" >> order',
|
||||
)
|
||||
script(development.scripts_dir / "second", "printf 'second\\n' >> order")
|
||||
shell_script(development.scripts_dir / "second", "printf 'second\\n' >> order")
|
||||
monkeypatch.setenv("AGENTCI_SECRET", "must-not-leak")
|
||||
|
||||
await development.prepare(workspace)
|
||||
@@ -51,24 +58,16 @@ async def test_runs_custom_scripts_in_order_with_sanitized_environment(
|
||||
assert (tmp_path / "tools" / "bin").is_dir()
|
||||
|
||||
|
||||
async def test_supplied_script_names_use_the_same_directory(tmp_path) -> None:
|
||||
workspace = tmp_path / "workspace"
|
||||
workspace.mkdir()
|
||||
development = environment(tmp_path, ["python"])
|
||||
script(development.scripts_dir / "python", "printf 'python\\n' > selected")
|
||||
|
||||
await development.prepare(workspace)
|
||||
|
||||
assert (workspace / "selected").read_text() == "python\n"
|
||||
|
||||
|
||||
async def test_runs_non_executable_shell_script_from_bind_mount(tmp_path) -> None:
|
||||
workspace = tmp_path / "workspace"
|
||||
workspace.mkdir()
|
||||
development = environment(tmp_path, ["mounted"])
|
||||
mounted = development.scripts_dir / "mounted"
|
||||
mounted.write_text("#!/bin/sh\nprintf 'mounted\\n' > selected\n")
|
||||
mounted.chmod(0o644)
|
||||
async def test_runs_non_executable_shell_script_from_bind_mount(
|
||||
tmp_path: Path,
|
||||
workspace: Path,
|
||||
) -> None:
|
||||
development = development_environment(tmp_path, ["mounted"])
|
||||
shell_script(
|
||||
development.scripts_dir / "mounted",
|
||||
"printf 'mounted\\n' > selected",
|
||||
executable=False,
|
||||
)
|
||||
|
||||
await development.prepare(workspace)
|
||||
|
||||
@@ -76,8 +75,8 @@ async def test_runs_non_executable_shell_script_from_bind_mount(tmp_path) -> Non
|
||||
|
||||
|
||||
async def test_serializes_concurrent_preparation(tmp_path, monkeypatch) -> None:
|
||||
development = environment(tmp_path, ["shared"])
|
||||
script(development.scripts_dir / "shared", "true")
|
||||
development = development_environment(tmp_path, ["shared"])
|
||||
shell_script(development.scripts_dir / "shared", "true")
|
||||
started = asyncio.Event()
|
||||
release = asyncio.Event()
|
||||
active = 0
|
||||
@@ -102,20 +101,8 @@ async def test_serializes_concurrent_preparation(tmp_path, monkeypatch) -> None:
|
||||
assert maximum_active == 1
|
||||
|
||||
|
||||
async def test_reports_script_failure_output(tmp_path) -> None:
|
||||
workspace = tmp_path / "workspace"
|
||||
workspace.mkdir()
|
||||
development = environment(tmp_path, ["broken"])
|
||||
script(development.scripts_dir / "broken", "printf 'failed detail' >&2; exit 7")
|
||||
|
||||
with pytest.raises(DevelopmentEnvironmentError, match="exited with 7: failed detail"):
|
||||
await development.prepare(workspace)
|
||||
|
||||
|
||||
async def test_reports_missing_install_script(tmp_path: Path) -> None:
|
||||
workspace = tmp_path / "workspace"
|
||||
workspace.mkdir()
|
||||
development = environment(tmp_path, ["missing"])
|
||||
async def test_reports_missing_install_script(tmp_path: Path, workspace: Path) -> None:
|
||||
development = development_environment(tmp_path, ["missing"])
|
||||
|
||||
with pytest.raises(
|
||||
DevelopmentEnvironmentError,
|
||||
@@ -124,25 +111,30 @@ async def test_reports_missing_install_script(tmp_path: Path) -> None:
|
||||
await development.prepare(workspace)
|
||||
|
||||
|
||||
async def test_stops_before_second_script_after_failure(tmp_path: Path) -> None:
|
||||
workspace = tmp_path / "workspace"
|
||||
workspace.mkdir()
|
||||
development = environment(tmp_path, ["first", "second"])
|
||||
script(development.scripts_dir / "first", "printf 'first\n' > first-ran; exit 3")
|
||||
script(development.scripts_dir / "second", "printf 'second\n' > second-ran")
|
||||
async def test_stops_after_failure_and_reports_output(
|
||||
tmp_path: Path,
|
||||
workspace: Path,
|
||||
) -> None:
|
||||
development = development_environment(tmp_path, ["first", "second"])
|
||||
shell_script(
|
||||
development.scripts_dir / "first",
|
||||
"printf 'first\\n' > first-ran; printf 'failed detail' >&2; exit 7",
|
||||
)
|
||||
shell_script(development.scripts_dir / "second", "printf 'second\\n' > second-ran")
|
||||
|
||||
with pytest.raises(DevelopmentEnvironmentError, match="exited with 3"):
|
||||
with pytest.raises(DevelopmentEnvironmentError, match="exited with 7: failed detail"):
|
||||
await development.prepare(workspace)
|
||||
|
||||
assert (workspace / "first-ran").read_text() == "first\n"
|
||||
assert not (workspace / "second-ran").exists()
|
||||
|
||||
|
||||
async def test_failure_output_keeps_only_bounded_tail(tmp_path: Path) -> None:
|
||||
workspace = tmp_path / "workspace"
|
||||
workspace.mkdir()
|
||||
development = environment(tmp_path, ["verbose"])
|
||||
script(
|
||||
async def test_failure_output_keeps_only_bounded_tail(
|
||||
tmp_path: Path,
|
||||
workspace: Path,
|
||||
) -> None:
|
||||
development = development_environment(tmp_path, ["verbose"])
|
||||
shell_script(
|
||||
development.scripts_dir / "verbose",
|
||||
"printf 'discarded-prefix' >&2; "
|
||||
'i=0; while [ "$i" -lt 2100 ]; do printf x >&2; i=$((i + 1)); done; '
|
||||
@@ -158,12 +150,12 @@ async def test_failure_output_keeps_only_bounded_tail(tmp_path: Path) -> None:
|
||||
|
||||
|
||||
async def test_wraps_subprocess_start_error(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
tmp_path: Path,
|
||||
workspace: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
workspace = tmp_path / "workspace"
|
||||
workspace.mkdir()
|
||||
development = environment(tmp_path, ["broken"])
|
||||
script(development.scripts_dir / "broken", "true")
|
||||
development = development_environment(tmp_path, ["broken"])
|
||||
shell_script(development.scripts_dir / "broken", "true")
|
||||
|
||||
async def create_subprocess_exec(*_args, **_kwargs):
|
||||
raise OSError("exec unavailable")
|
||||
@@ -182,11 +174,9 @@ async def test_wraps_subprocess_start_error(
|
||||
assert isinstance(raised.value.__cause__, OSError)
|
||||
|
||||
|
||||
async def test_times_out_install_script(tmp_path) -> None:
|
||||
workspace = tmp_path / "workspace"
|
||||
workspace.mkdir()
|
||||
development = environment(tmp_path, ["slow"], timeout_seconds=1)
|
||||
script(development.scripts_dir / "slow", "exec sleep 10")
|
||||
async def test_times_out_install_script(tmp_path: Path, workspace: Path) -> None:
|
||||
development = development_environment(tmp_path, ["slow"], timeout_seconds=1)
|
||||
shell_script(development.scripts_dir / "slow", "exec sleep 10")
|
||||
|
||||
with pytest.raises(DevelopmentEnvironmentError, match="exceeded 1 seconds"):
|
||||
await development.prepare(workspace)
|
||||
|
||||
Reference in New Issue
Block a user