Ship trust-first CLI and runtime defaults
This commit is contained in:
parent
fb718af154
commit
5d63e4c16e
26 changed files with 894 additions and 134 deletions
|
|
@ -25,6 +25,7 @@ def test_pyro_run_in_vm_delegates_to_manager(tmp_path: Path) -> None:
|
|||
timeout_seconds=30,
|
||||
ttl_seconds=600,
|
||||
network=False,
|
||||
allow_host_compat=True,
|
||||
)
|
||||
assert int(result["exit_code"]) == 0
|
||||
assert str(result["stdout"]) == "ok\n"
|
||||
|
|
@ -74,12 +75,30 @@ def test_pyro_vm_run_tool_executes(tmp_path: Path) -> None:
|
|||
{
|
||||
"environment": "debian:12-base",
|
||||
"command": "printf 'ok\\n'",
|
||||
"vcpu_count": 1,
|
||||
"mem_mib": 512,
|
||||
"network": False,
|
||||
"allow_host_compat": True,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
result = asyncio.run(_run())
|
||||
assert int(result["exit_code"]) == 0
|
||||
|
||||
|
||||
def test_pyro_create_vm_defaults_sizing_and_host_compat(tmp_path: Path) -> None:
|
||||
pyro = Pyro(
|
||||
manager=VmManager(
|
||||
backend_name="mock",
|
||||
base_dir=tmp_path / "vms",
|
||||
network_manager=TapNetworkManager(enabled=False),
|
||||
)
|
||||
)
|
||||
|
||||
created = pyro.create_vm(
|
||||
environment="debian:12-base",
|
||||
allow_host_compat=True,
|
||||
)
|
||||
|
||||
assert created["vcpu_count"] == 1
|
||||
assert created["mem_mib"] == 1024
|
||||
assert created["allow_host_compat"] is True
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
|
@ -29,6 +30,8 @@ def test_cli_run_prints_json(
|
|||
timeout_seconds=30,
|
||||
ttl_seconds=600,
|
||||
network=True,
|
||||
allow_host_compat=False,
|
||||
json=True,
|
||||
command_args=["--", "echo", "hi"],
|
||||
)
|
||||
|
||||
|
|
@ -44,7 +47,7 @@ def test_cli_doctor_prints_json(
|
|||
) -> None:
|
||||
class StubParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(command="doctor", platform="linux-x86_64")
|
||||
return argparse.Namespace(command="doctor", platform="linux-x86_64", json=True)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
|
||||
monkeypatch.setattr(
|
||||
|
|
@ -93,7 +96,7 @@ def test_cli_env_list_prints_json(
|
|||
|
||||
class StubParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(command="env", env_command="list")
|
||||
return argparse.Namespace(command="env", env_command="list", json=True)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
|
||||
monkeypatch.setattr(cli, "Pyro", StubPyro)
|
||||
|
|
@ -102,6 +105,372 @@ def test_cli_env_list_prints_json(
|
|||
assert output["environments"][0]["name"] == "debian:12"
|
||||
|
||||
|
||||
def test_cli_run_prints_human_output(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
class StubPyro:
|
||||
def run_in_vm(self, **kwargs: Any) -> dict[str, Any]:
|
||||
assert kwargs["vcpu_count"] == 1
|
||||
assert kwargs["mem_mib"] == 1024
|
||||
return {
|
||||
"environment": kwargs["environment"],
|
||||
"execution_mode": "guest_vsock",
|
||||
"exit_code": 0,
|
||||
"duration_ms": 12,
|
||||
"stdout": "hi\n",
|
||||
"stderr": "",
|
||||
}
|
||||
|
||||
class StubParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
command="run",
|
||||
environment="debian:12",
|
||||
vcpu_count=1,
|
||||
mem_mib=1024,
|
||||
timeout_seconds=30,
|
||||
ttl_seconds=600,
|
||||
network=False,
|
||||
allow_host_compat=False,
|
||||
json=False,
|
||||
command_args=["--", "echo", "hi"],
|
||||
)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
|
||||
monkeypatch.setattr(cli, "Pyro", StubPyro)
|
||||
|
||||
cli.main()
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert captured.out == "hi\n"
|
||||
assert "[run] environment=debian:12 execution_mode=guest_vsock exit_code=0" in captured.err
|
||||
|
||||
|
||||
def test_cli_run_exits_with_command_status(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
class StubPyro:
|
||||
def run_in_vm(self, **kwargs: Any) -> dict[str, Any]:
|
||||
del kwargs
|
||||
return {
|
||||
"environment": "debian:12",
|
||||
"execution_mode": "guest_vsock",
|
||||
"exit_code": 7,
|
||||
"duration_ms": 5,
|
||||
"stdout": "",
|
||||
"stderr": "bad\n",
|
||||
}
|
||||
|
||||
class StubParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
command="run",
|
||||
environment="debian:12",
|
||||
vcpu_count=1,
|
||||
mem_mib=1024,
|
||||
timeout_seconds=30,
|
||||
ttl_seconds=600,
|
||||
network=False,
|
||||
allow_host_compat=False,
|
||||
json=False,
|
||||
command_args=["--", "false"],
|
||||
)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
|
||||
monkeypatch.setattr(cli, "Pyro", StubPyro)
|
||||
|
||||
with pytest.raises(SystemExit, match="7"):
|
||||
cli.main()
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "bad\n" in captured.err
|
||||
|
||||
|
||||
def test_cli_requires_run_command() -> None:
|
||||
with pytest.raises(ValueError, match="command is required"):
|
||||
cli._require_command([])
|
||||
|
||||
|
||||
def test_print_env_helpers_render_human_output(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
cli._print_env_list_human(
|
||||
{
|
||||
"catalog_version": "2.0.0",
|
||||
"environments": [
|
||||
{"name": "debian:12", "installed": True, "description": "Git environment"},
|
||||
"ignored",
|
||||
],
|
||||
}
|
||||
)
|
||||
cli._print_env_detail_human(
|
||||
{
|
||||
"name": "debian:12",
|
||||
"version": "1.0.0",
|
||||
"distribution": "debian",
|
||||
"distribution_version": "12",
|
||||
"installed": True,
|
||||
"cache_dir": "/cache",
|
||||
"default_packages": ["bash", "git"],
|
||||
"description": "Git environment",
|
||||
"install_dir": "/cache/linux-x86_64/debian_12-1.0.0",
|
||||
"install_manifest": "/cache/linux-x86_64/debian_12-1.0.0/environment.json",
|
||||
"kernel_image": "/cache/vmlinux",
|
||||
"rootfs_image": "/cache/rootfs.ext4",
|
||||
"oci_registry": "registry-1.docker.io",
|
||||
"oci_repository": "thalesmaciel/pyro-environment-debian-12",
|
||||
"oci_reference": "1.0.0",
|
||||
},
|
||||
action="Environment",
|
||||
)
|
||||
cli._print_prune_human({"count": 2, "deleted_environment_dirs": ["a", "b"]})
|
||||
cli._print_doctor_human(
|
||||
{
|
||||
"platform": "linux-x86_64",
|
||||
"runtime_ok": False,
|
||||
"issues": ["broken"],
|
||||
"kvm": {"exists": True, "readable": True, "writable": False},
|
||||
"runtime": {
|
||||
"cache_dir": "/cache",
|
||||
"capabilities": {
|
||||
"supports_vm_boot": True,
|
||||
"supports_guest_exec": False,
|
||||
"supports_guest_network": True,
|
||||
},
|
||||
},
|
||||
"networking": {"tun_available": True, "ip_forward_enabled": False},
|
||||
}
|
||||
)
|
||||
captured = capsys.readouterr().out
|
||||
assert "Catalog version: 2.0.0" in captured
|
||||
assert "debian:12 [installed] Git environment" in captured
|
||||
assert "Install manifest: /cache/linux-x86_64/debian_12-1.0.0/environment.json" in captured
|
||||
assert "Deleted 2 cached environment entries." in captured
|
||||
assert "Runtime: FAIL" in captured
|
||||
assert "Issues:" in captured
|
||||
|
||||
|
||||
def test_print_env_list_human_handles_empty(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
cli._print_env_list_human({"catalog_version": "2.0.0", "environments": []})
|
||||
output = capsys.readouterr().out
|
||||
assert "No environments found." in output
|
||||
|
||||
|
||||
def test_write_stream_skips_empty(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
cli._write_stream("", stream=sys.stdout)
|
||||
cli._write_stream("x", stream=sys.stdout)
|
||||
captured = capsys.readouterr()
|
||||
assert captured.out == "x"
|
||||
|
||||
|
||||
def test_cli_env_pull_prints_human(
|
||||
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
class StubPyro:
|
||||
def pull_environment(self, environment: str) -> dict[str, object]:
|
||||
assert environment == "debian:12"
|
||||
return {
|
||||
"name": "debian:12",
|
||||
"version": "1.0.0",
|
||||
"distribution": "debian",
|
||||
"distribution_version": "12",
|
||||
"installed": True,
|
||||
"cache_dir": "/cache",
|
||||
}
|
||||
|
||||
class StubParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
command="env",
|
||||
env_command="pull",
|
||||
environment="debian:12",
|
||||
json=False,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
|
||||
monkeypatch.setattr(cli, "Pyro", StubPyro)
|
||||
cli.main()
|
||||
output = capsys.readouterr().out
|
||||
assert "Pulled: debian:12" in output
|
||||
|
||||
|
||||
def test_cli_env_inspect_and_prune_print_human(
|
||||
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
class StubPyro:
|
||||
def inspect_environment(self, environment: str) -> dict[str, object]:
|
||||
assert environment == "debian:12"
|
||||
return {
|
||||
"name": "debian:12",
|
||||
"version": "1.0.0",
|
||||
"distribution": "debian",
|
||||
"distribution_version": "12",
|
||||
"installed": False,
|
||||
"cache_dir": "/cache",
|
||||
}
|
||||
|
||||
def prune_environments(self) -> dict[str, object]:
|
||||
return {"count": 1, "deleted_environment_dirs": ["stale"]}
|
||||
|
||||
class InspectParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
command="env",
|
||||
env_command="inspect",
|
||||
environment="debian:12",
|
||||
json=False,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: InspectParser())
|
||||
monkeypatch.setattr(cli, "Pyro", StubPyro)
|
||||
cli.main()
|
||||
|
||||
class PruneParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(command="env", env_command="prune", json=False)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: PruneParser())
|
||||
cli.main()
|
||||
|
||||
output = capsys.readouterr().out
|
||||
assert "Environment: debian:12" in output
|
||||
assert "Deleted 1 cached environment entry." in output
|
||||
|
||||
|
||||
def test_cli_doctor_prints_human(
|
||||
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
class StubParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(command="doctor", platform="linux-x86_64", json=False)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"doctor_report",
|
||||
lambda platform: {
|
||||
"platform": platform,
|
||||
"runtime_ok": True,
|
||||
"issues": [],
|
||||
"kvm": {"exists": True, "readable": True, "writable": True},
|
||||
},
|
||||
)
|
||||
cli.main()
|
||||
output = capsys.readouterr().out
|
||||
assert "Runtime: PASS" in output
|
||||
|
||||
|
||||
def test_cli_run_json_error_exits_nonzero(
|
||||
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
class StubPyro:
|
||||
def run_in_vm(self, **kwargs: Any) -> dict[str, Any]:
|
||||
del kwargs
|
||||
raise RuntimeError("guest boot is unavailable")
|
||||
|
||||
class StubParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
command="run",
|
||||
environment="debian:12",
|
||||
vcpu_count=1,
|
||||
mem_mib=1024,
|
||||
timeout_seconds=30,
|
||||
ttl_seconds=600,
|
||||
network=False,
|
||||
allow_host_compat=False,
|
||||
json=True,
|
||||
command_args=["--", "echo", "hi"],
|
||||
)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
|
||||
monkeypatch.setattr(cli, "Pyro", StubPyro)
|
||||
|
||||
with pytest.raises(SystemExit, match="1"):
|
||||
cli.main()
|
||||
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert payload["ok"] is False
|
||||
|
||||
|
||||
def test_cli_mcp_runs_stdio_transport(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
observed: dict[str, str] = {}
|
||||
|
||||
class StubPyro:
|
||||
def create_server(self) -> Any:
|
||||
return type(
|
||||
"StubServer",
|
||||
(),
|
||||
{"run": staticmethod(lambda transport: observed.update({"transport": transport}))},
|
||||
)()
|
||||
|
||||
class StubParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(command="mcp", mcp_command="serve")
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
|
||||
monkeypatch.setattr(cli, "Pyro", StubPyro)
|
||||
cli.main()
|
||||
assert observed == {"transport": "stdio"}
|
||||
|
||||
|
||||
def test_cli_demo_default_prints_json(
|
||||
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
class StubParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(command="demo", demo_command=None, network=False)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
|
||||
monkeypatch.setattr(cli, "run_demo", lambda network: {"exit_code": 0, "network": network})
|
||||
cli.main()
|
||||
output = json.loads(capsys.readouterr().out)
|
||||
assert output["exit_code"] == 0
|
||||
|
||||
|
||||
def test_cli_demo_ollama_verbose_and_error_paths(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
class VerboseParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
command="demo",
|
||||
demo_command="ollama",
|
||||
base_url="http://localhost:11434/v1",
|
||||
model="llama3.2:3b",
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: VerboseParser())
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"run_ollama_tool_demo",
|
||||
lambda **kwargs: {
|
||||
"exec_result": {"exit_code": 0, "execution_mode": "guest_vsock", "stdout": "true\n"},
|
||||
"fallback_used": False,
|
||||
},
|
||||
)
|
||||
cli.main()
|
||||
output = capsys.readouterr().out
|
||||
assert "[summary] stdout=true" in output
|
||||
|
||||
class ErrorParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
command="demo",
|
||||
demo_command="ollama",
|
||||
base_url="http://localhost:11434/v1",
|
||||
model="llama3.2:3b",
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: ErrorParser())
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"run_ollama_tool_demo",
|
||||
lambda **kwargs: (_ for _ in ()).throw(RuntimeError("tool loop failed")),
|
||||
)
|
||||
with pytest.raises(SystemExit, match="1"):
|
||||
cli.main()
|
||||
assert "[error] tool loop failed" in capsys.readouterr().out
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ def test_run_demo_happy_path(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
"environment": "debian:12",
|
||||
"command": "git --version",
|
||||
"vcpu_count": 1,
|
||||
"mem_mib": 512,
|
||||
"mem_mib": 1024,
|
||||
"timeout_seconds": 30,
|
||||
"ttl_seconds": 600,
|
||||
"network": False,
|
||||
|
|
@ -95,3 +95,4 @@ def test_run_demo_network_uses_probe(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
demo_module.run_demo(network=True)
|
||||
assert "https://example.com" in str(captured["command"])
|
||||
assert captured["network"] is True
|
||||
assert captured["mem_mib"] == 1024
|
||||
|
|
|
|||
|
|
@ -52,9 +52,8 @@ def _stepwise_model_response(payload: dict[str, Any], step: int) -> dict[str, An
|
|||
{
|
||||
"environment": "debian:12",
|
||||
"command": "printf 'true\\n'",
|
||||
"vcpu_count": 1,
|
||||
"mem_mib": 512,
|
||||
"network": True,
|
||||
"allow_host_compat": True,
|
||||
}
|
||||
),
|
||||
},
|
||||
|
|
@ -119,9 +118,8 @@ def test_run_ollama_tool_demo_accepts_legacy_profile_and_string_network(
|
|||
{
|
||||
"profile": "debian:12",
|
||||
"command": "printf 'true\\n'",
|
||||
"vcpu_count": 1,
|
||||
"mem_mib": 512,
|
||||
"network": "true",
|
||||
"allow_host_compat": True,
|
||||
}
|
||||
),
|
||||
},
|
||||
|
|
@ -224,8 +222,7 @@ def test_run_ollama_tool_demo_resolves_vm_id_placeholder(
|
|||
"arguments": json.dumps(
|
||||
{
|
||||
"environment": "debian:12",
|
||||
"vcpu_count": "2",
|
||||
"mem_mib": "2048",
|
||||
"allow_host_compat": True,
|
||||
}
|
||||
),
|
||||
},
|
||||
|
|
@ -280,6 +277,7 @@ def test_dispatch_tool_call_vm_exec_autostarts_created_vm(tmp_path: Path) -> Non
|
|||
vcpu_count=1,
|
||||
mem_mib=512,
|
||||
ttl_seconds=60,
|
||||
allow_host_compat=True,
|
||||
)
|
||||
vm_id = str(created["vm_id"])
|
||||
|
||||
|
|
@ -458,6 +456,7 @@ def test_dispatch_tool_call_coverage(tmp_path: Path) -> None:
|
|||
"mem_mib": "512",
|
||||
"ttl_seconds": "60",
|
||||
"network": False,
|
||||
"allow_host_compat": True,
|
||||
},
|
||||
)
|
||||
vm_id = str(created["vm_id"])
|
||||
|
|
@ -477,10 +476,9 @@ def test_dispatch_tool_call_coverage(tmp_path: Path) -> None:
|
|||
{
|
||||
"environment": "debian:12-base",
|
||||
"command": "printf 'true\\n'",
|
||||
"vcpu_count": "1",
|
||||
"mem_mib": "512",
|
||||
"timeout_seconds": "30",
|
||||
"network": False,
|
||||
"allow_host_compat": True,
|
||||
},
|
||||
)
|
||||
assert int(executed_run["exit_code"]) == 0
|
||||
|
|
|
|||
|
|
@ -49,11 +49,11 @@ def test_public_cli_help_lists_commands_and_run_flags() -> None:
|
|||
assert command_name in help_text
|
||||
|
||||
run_parser = _build_parser()
|
||||
run_help = run_parser.parse_args(
|
||||
["run", "debian:12-base", "--vcpu-count", "1", "--mem-mib", "512", "--", "true"]
|
||||
)
|
||||
run_help = run_parser.parse_args(["run", "debian:12-base", "--", "true"])
|
||||
assert run_help.command == "run"
|
||||
assert run_help.environment == "debian:12-base"
|
||||
assert run_help.vcpu_count == 1
|
||||
assert run_help.mem_mib == 1024
|
||||
|
||||
run_help_text = _subparser_choice(parser, "run").format_help()
|
||||
for flag in PUBLIC_CLI_RUN_FLAGS:
|
||||
|
|
|
|||
|
|
@ -56,10 +56,9 @@ def test_vm_run_round_trip(tmp_path: Path) -> None:
|
|||
{
|
||||
"environment": "debian:12",
|
||||
"command": "printf 'git version 2.0\\n'",
|
||||
"vcpu_count": 1,
|
||||
"mem_mib": 512,
|
||||
"ttl_seconds": 600,
|
||||
"network": False,
|
||||
"allow_host_compat": True,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
|
@ -109,9 +108,8 @@ def test_vm_tools_status_stop_delete_and_reap(tmp_path: Path) -> None:
|
|||
"vm_create",
|
||||
{
|
||||
"environment": "debian:12-base",
|
||||
"vcpu_count": 1,
|
||||
"mem_mib": 512,
|
||||
"ttl_seconds": 600,
|
||||
"allow_host_compat": True,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
|
@ -127,9 +125,8 @@ def test_vm_tools_status_stop_delete_and_reap(tmp_path: Path) -> None:
|
|||
"vm_create",
|
||||
{
|
||||
"environment": "debian:12-base",
|
||||
"vcpu_count": 1,
|
||||
"mem_mib": 512,
|
||||
"ttl_seconds": 1,
|
||||
"allow_host_compat": True,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ def test_vm_manager_lifecycle_and_auto_cleanup(tmp_path: Path) -> None:
|
|||
vcpu_count=1,
|
||||
mem_mib=512,
|
||||
ttl_seconds=600,
|
||||
allow_host_compat=True,
|
||||
)
|
||||
vm_id = str(created["vm_id"])
|
||||
started = manager.start_vm(vm_id)
|
||||
|
|
@ -47,6 +48,7 @@ def test_vm_manager_exec_timeout(tmp_path: Path) -> None:
|
|||
vcpu_count=1,
|
||||
mem_mib=512,
|
||||
ttl_seconds=600,
|
||||
allow_host_compat=True,
|
||||
)["vm_id"]
|
||||
)
|
||||
manager.start_vm(vm_id)
|
||||
|
|
@ -67,6 +69,7 @@ def test_vm_manager_stop_and_delete(tmp_path: Path) -> None:
|
|||
vcpu_count=1,
|
||||
mem_mib=512,
|
||||
ttl_seconds=600,
|
||||
allow_host_compat=True,
|
||||
)["vm_id"]
|
||||
)
|
||||
manager.start_vm(vm_id)
|
||||
|
|
@ -89,6 +92,7 @@ def test_vm_manager_reaps_expired(tmp_path: Path) -> None:
|
|||
vcpu_count=1,
|
||||
mem_mib=512,
|
||||
ttl_seconds=1,
|
||||
allow_host_compat=True,
|
||||
)["vm_id"]
|
||||
)
|
||||
instance = manager._instances[vm_id] # noqa: SLF001
|
||||
|
|
@ -112,6 +116,7 @@ def test_vm_manager_reaps_started_vm(tmp_path: Path) -> None:
|
|||
vcpu_count=1,
|
||||
mem_mib=512,
|
||||
ttl_seconds=1,
|
||||
allow_host_compat=True,
|
||||
)["vm_id"]
|
||||
)
|
||||
manager.start_vm(vm_id)
|
||||
|
|
@ -145,9 +150,21 @@ def test_vm_manager_max_active_limit(tmp_path: Path) -> None:
|
|||
max_active_vms=1,
|
||||
network_manager=TapNetworkManager(enabled=False),
|
||||
)
|
||||
manager.create_vm(environment="debian:12-base", vcpu_count=1, mem_mib=512, ttl_seconds=600)
|
||||
manager.create_vm(
|
||||
environment="debian:12-base",
|
||||
vcpu_count=1,
|
||||
mem_mib=512,
|
||||
ttl_seconds=600,
|
||||
allow_host_compat=True,
|
||||
)
|
||||
with pytest.raises(RuntimeError, match="max active VMs reached"):
|
||||
manager.create_vm(environment="debian:12-base", vcpu_count=1, mem_mib=512, ttl_seconds=600)
|
||||
manager.create_vm(
|
||||
environment="debian:12-base",
|
||||
vcpu_count=1,
|
||||
mem_mib=512,
|
||||
ttl_seconds=600,
|
||||
allow_host_compat=True,
|
||||
)
|
||||
|
||||
|
||||
def test_vm_manager_state_validation(tmp_path: Path) -> None:
|
||||
|
|
@ -162,6 +179,7 @@ def test_vm_manager_state_validation(tmp_path: Path) -> None:
|
|||
vcpu_count=1,
|
||||
mem_mib=512,
|
||||
ttl_seconds=600,
|
||||
allow_host_compat=True,
|
||||
)["vm_id"]
|
||||
)
|
||||
with pytest.raises(RuntimeError, match="must be in 'started' state"):
|
||||
|
|
@ -186,6 +204,7 @@ def test_vm_manager_status_expired_raises(tmp_path: Path) -> None:
|
|||
vcpu_count=1,
|
||||
mem_mib=512,
|
||||
ttl_seconds=1,
|
||||
allow_host_compat=True,
|
||||
)["vm_id"]
|
||||
)
|
||||
manager._instances[vm_id].expires_at = 0.0 # noqa: SLF001
|
||||
|
|
@ -213,6 +232,7 @@ def test_vm_manager_network_info(tmp_path: Path) -> None:
|
|||
vcpu_count=1,
|
||||
mem_mib=512,
|
||||
ttl_seconds=600,
|
||||
allow_host_compat=True,
|
||||
)
|
||||
vm_id = str(created["vm_id"])
|
||||
status = manager.status_vm(vm_id)
|
||||
|
|
@ -236,6 +256,7 @@ def test_vm_manager_run_vm(tmp_path: Path) -> None:
|
|||
timeout_seconds=30,
|
||||
ttl_seconds=600,
|
||||
network=False,
|
||||
allow_host_compat=True,
|
||||
)
|
||||
assert int(result["exit_code"]) == 0
|
||||
assert str(result["stdout"]) == "ok\n"
|
||||
|
|
@ -283,3 +304,33 @@ def test_vm_manager_firecracker_backend_path(
|
|||
network_manager=TapNetworkManager(enabled=False),
|
||||
)
|
||||
assert manager._backend_name == "firecracker" # noqa: SLF001
|
||||
|
||||
|
||||
def test_vm_manager_fails_closed_without_host_compat_opt_in(tmp_path: Path) -> None:
|
||||
manager = VmManager(
|
||||
backend_name="mock",
|
||||
base_dir=tmp_path / "vms",
|
||||
network_manager=TapNetworkManager(enabled=False),
|
||||
)
|
||||
vm_id = str(
|
||||
manager.create_vm(
|
||||
environment="debian:12-base",
|
||||
ttl_seconds=600,
|
||||
)["vm_id"]
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match="guest boot is unavailable"):
|
||||
manager.start_vm(vm_id)
|
||||
|
||||
|
||||
def test_vm_manager_uses_canonical_default_cache_dir(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
monkeypatch.setenv("PYRO_ENVIRONMENT_CACHE_DIR", str(tmp_path / "cache"))
|
||||
manager = VmManager(
|
||||
backend_name="mock",
|
||||
base_dir=tmp_path / "vms",
|
||||
network_manager=TapNetworkManager(enabled=False),
|
||||
)
|
||||
|
||||
assert manager._environment_store.cache_dir == tmp_path / "cache" # noqa: SLF001
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue