Refactor public API around environments

This commit is contained in:
Thales Maciel 2026-03-08 16:02:02 -03:00
parent 57dae52cc2
commit 5d5243df23
41 changed files with 1301 additions and 459 deletions

View file

@ -18,7 +18,7 @@ def test_pyro_run_in_vm_delegates_to_manager(tmp_path: Path) -> None:
)
)
result = pyro.run_in_vm(
profile="debian-base",
environment="debian:12-base",
command="printf 'ok\\n'",
vcpu_count=1,
mem_mib=512,
@ -72,7 +72,7 @@ def test_pyro_vm_run_tool_executes(tmp_path: Path) -> None:
await server.call_tool(
"vm_run",
{
"profile": "debian-base",
"environment": "debian:12-base",
"command": "printf 'ok\\n'",
"vcpu_count": 1,
"mem_mib": 512,

View file

@ -23,7 +23,7 @@ def test_cli_run_prints_json(
def parse_args(self) -> argparse.Namespace:
return argparse.Namespace(
command="run",
profile="debian-git",
environment="debian:12",
vcpu_count=1,
mem_mib=512,
timeout_seconds=30,
@ -84,6 +84,24 @@ def test_cli_demo_ollama_prints_summary(
assert "[summary] exit_code=0 fallback_used=False execution_mode=guest_vsock" in output
def test_cli_env_list_prints_json(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
class StubPyro:
def list_environments(self) -> list[dict[str, object]]:
return [{"name": "debian:12", "installed": False}]
class StubParser:
def parse_args(self) -> argparse.Namespace:
return argparse.Namespace(command="env", env_command="list")
monkeypatch.setattr(cli, "_build_parser", lambda: StubParser())
monkeypatch.setattr(cli, "Pyro", StubPyro)
cli.main()
output = json.loads(capsys.readouterr().out)
assert output["environments"][0]["name"] == "debian:12"
def test_cli_requires_run_command() -> None:
with pytest.raises(ValueError, match="command is required"):
cli._require_command([])

View file

@ -18,7 +18,7 @@ def test_run_demo_happy_path(monkeypatch: pytest.MonkeyPatch) -> None:
def run_in_vm(
self,
*,
profile: str,
environment: str,
command: str,
vcpu_count: int,
mem_mib: int,
@ -30,7 +30,7 @@ def test_run_demo_happy_path(monkeypatch: pytest.MonkeyPatch) -> None:
(
"run_in_vm",
{
"profile": profile,
"environment": environment,
"command": command,
"vcpu_count": vcpu_count,
"mem_mib": mem_mib,
@ -50,7 +50,7 @@ def test_run_demo_happy_path(monkeypatch: pytest.MonkeyPatch) -> None:
(
"run_in_vm",
{
"profile": "debian-git",
"environment": "debian:12",
"command": "git --version",
"vcpu_count": 1,
"mem_mib": 512,

View file

@ -35,7 +35,7 @@ def test_langchain_example_delegates_to_pyro(monkeypatch: pytest.MonkeyPatch) ->
)(),
)
result = module.run_vm_run_tool(
profile="debian-git",
environment="debian:12",
command="git --version",
vcpu_count=1,
mem_mib=1024,

View file

@ -31,7 +31,7 @@ def _stepwise_model_response(payload: dict[str, Any], step: int) -> dict[str, An
"message": {
"role": "assistant",
"content": "",
"tool_calls": [{"id": "1", "function": {"name": "vm_list_profiles"}}],
"tool_calls": [{"id": "1", "function": {"name": "vm_list_environments"}}],
}
}
]
@ -50,7 +50,7 @@ def _stepwise_model_response(payload: dict[str, Any], step: int) -> dict[str, An
"name": "vm_run",
"arguments": json.dumps(
{
"profile": "debian-git",
"environment": "debian:12",
"command": "printf 'true\\n'",
"vcpu_count": 1,
"mem_mib": 512,
@ -117,7 +117,7 @@ def test_run_ollama_tool_demo_recovers_from_bad_vm_id(
"name": "vm_exec",
"arguments": json.dumps(
{
"vm_id": "vm_list_profiles",
"vm_id": "vm_list_environments",
"command": ollama_demo.NETWORK_PROOF_COMMAND,
}
),
@ -157,15 +157,15 @@ def test_run_ollama_tool_demo_resolves_vm_id_placeholder(
"role": "assistant",
"content": "",
"tool_calls": [
{"id": "1", "function": {"name": "vm_list_profiles"}},
{"id": "2", "function": {"name": "vm_list_profiles"}},
{"id": "1", "function": {"name": "vm_list_environments"}},
{"id": "2", "function": {"name": "vm_list_environments"}},
{
"id": "3",
"function": {
"name": "vm_create",
"arguments": json.dumps(
{
"profile": "debian-git",
"environment": "debian:12",
"vcpu_count": "2",
"mem_mib": "2048",
}
@ -217,7 +217,12 @@ def test_run_ollama_tool_demo_resolves_vm_id_placeholder(
def test_dispatch_tool_call_vm_exec_autostarts_created_vm(tmp_path: Path) -> None:
pyro = RealPyro(manager=RealVmManager(backend_name="mock", base_dir=tmp_path / "vms"))
created = pyro.create_vm(profile="debian-base", vcpu_count=1, mem_mib=512, ttl_seconds=60)
created = pyro.create_vm(
environment="debian:12-base",
vcpu_count=1,
mem_mib=512,
ttl_seconds=60,
)
vm_id = str(created["vm_id"])
executed = ollama_demo._dispatch_tool_call(
@ -291,7 +296,7 @@ def test_run_ollama_tool_demo_verbose_logs_values(monkeypatch: pytest.MonkeyPatc
assert result["fallback_used"] is False
assert str(result["exec_result"]["stdout"]).strip() == "true"
assert any("[model] input user:" in line for line in logs)
assert any("[model] tool_call vm_list_profiles args={}" in line for line in logs)
assert any("[model] tool_call vm_list_environments args={}" in line for line in logs)
assert any("[tool] result vm_run " in line for line in logs)
@ -299,7 +304,7 @@ def test_run_ollama_tool_demo_verbose_logs_values(monkeypatch: pytest.MonkeyPatc
("tool_call", "error"),
[
(1, "invalid tool call entry"),
({"id": "", "function": {"name": "vm_list_profiles"}}, "valid call id"),
({"id": "", "function": {"name": "vm_list_environments"}}, "valid call id"),
({"id": "1"}, "function metadata"),
({"id": "1", "function": {"name": 3}}, "name is invalid"),
],
@ -326,7 +331,7 @@ def test_run_ollama_tool_demo_max_rounds(monkeypatch: pytest.MonkeyPatch) -> Non
{
"message": {
"role": "assistant",
"tool_calls": [{"id": "1", "function": {"name": "vm_list_profiles"}}],
"tool_calls": [{"id": "1", "function": {"name": "vm_list_environments"}}],
}
}
]
@ -384,13 +389,13 @@ def test_run_ollama_tool_demo_exec_result_validation(
def test_dispatch_tool_call_coverage(tmp_path: Path) -> None:
pyro = RealPyro(manager=RealVmManager(backend_name="mock", base_dir=tmp_path / "vms"))
profiles = ollama_demo._dispatch_tool_call(pyro, "vm_list_profiles", {})
assert "profiles" in profiles
environments = ollama_demo._dispatch_tool_call(pyro, "vm_list_environments", {})
assert "environments" in environments
created = ollama_demo._dispatch_tool_call(
pyro,
"vm_create",
{
"profile": "debian-base",
"environment": "debian:12-base",
"vcpu_count": "1",
"mem_mib": "512",
"ttl_seconds": "60",
@ -412,7 +417,7 @@ def test_dispatch_tool_call_coverage(tmp_path: Path) -> None:
pyro,
"vm_run",
{
"profile": "debian-base",
"environment": "debian:12-base",
"command": "printf 'true\\n'",
"vcpu_count": "1",
"mem_mib": "512",

View file

@ -33,7 +33,7 @@ def test_openai_example_runs_function_call_loop(monkeypatch: pytest.MonkeyPatch)
name="vm_run",
call_id="call_123",
arguments=(
'{"profile":"debian-git","command":"git --version",'
'{"environment":"debian:12","command":"git --version",'
'"vcpu_count":1,"mem_mib":1024}'
),
)

View file

@ -15,6 +15,7 @@ from pyro_mcp.cli import _build_parser
from pyro_mcp.contract import (
PUBLIC_CLI_COMMANDS,
PUBLIC_CLI_DEMO_SUBCOMMANDS,
PUBLIC_CLI_ENV_SUBCOMMANDS,
PUBLIC_CLI_RUN_FLAGS,
PUBLIC_MCP_TOOLS,
PUBLIC_SDK_METHODS,
@ -49,14 +50,19 @@ def test_public_cli_help_lists_commands_and_run_flags() -> None:
run_parser = _build_parser()
run_help = run_parser.parse_args(
["run", "--profile", "debian-base", "--vcpu-count", "1", "--mem-mib", "512", "--", "true"]
["run", "debian:12-base", "--vcpu-count", "1", "--mem-mib", "512", "--", "true"]
)
assert run_help.command == "run"
assert run_help.environment == "debian:12-base"
run_help_text = _subparser_choice(parser, "run").format_help()
for flag in PUBLIC_CLI_RUN_FLAGS:
assert flag in run_help_text
env_help_text = _subparser_choice(parser, "env").format_help()
for subcommand_name in PUBLIC_CLI_ENV_SUBCOMMANDS:
assert subcommand_name in env_help_text
demo_help_text = _subparser_choice(parser, "demo").format_help()
for subcommand_name in PUBLIC_CLI_DEMO_SUBCOMMANDS:
assert subcommand_name in demo_help_text

View file

@ -80,6 +80,7 @@ def test_doctor_report_has_runtime_fields() -> None:
assert "firecracker_bin" in runtime
assert "guest_agent_path" in runtime
assert "component_versions" in runtime
assert "environments" in runtime
networking = report["networking"]
assert isinstance(networking, dict)
assert "tun_available" in networking

View file

@ -27,7 +27,7 @@ def test_network_check_uses_network_enabled_manager(monkeypatch: pytest.MonkeyPa
result = runtime_network_check.run_network_check()
assert observed["run_kwargs"] == {
"profile": "debian-git",
"environment": "debian:12",
"command": runtime_network_check.NETWORK_CHECK_COMMAND,
"vcpu_count": 1,
"mem_mib": 1024,

View file

@ -27,7 +27,7 @@ def test_create_server_registers_vm_tools(tmp_path: Path) -> None:
tool_names = asyncio.run(_run())
assert "vm_create" in tool_names
assert "vm_exec" in tool_names
assert "vm_list_profiles" in tool_names
assert "vm_list_environments" in tool_names
assert "vm_network_info" in tool_names
assert "vm_run" in tool_names
assert "vm_status" in tool_names
@ -54,7 +54,7 @@ def test_vm_run_round_trip(tmp_path: Path) -> None:
await server.call_tool(
"vm_run",
{
"profile": "debian-git",
"environment": "debian:12",
"command": "printf 'git version 2.0\\n'",
"vcpu_count": 1,
"mem_mib": 512,
@ -95,19 +95,24 @@ def test_vm_tools_status_stop_delete_and_reap(tmp_path: Path) -> None:
dict[str, Any],
]:
server = create_server(manager=manager)
profiles_raw = await server.call_tool("vm_list_profiles", {})
if not isinstance(profiles_raw, tuple) or len(profiles_raw) != 2:
raise TypeError("unexpected profiles result")
_, profiles_structured = profiles_raw
if not isinstance(profiles_structured, dict):
raise TypeError("profiles tool should return a dictionary")
raw_profiles = profiles_structured.get("result")
if not isinstance(raw_profiles, list):
raise TypeError("profiles tool did not contain a result list")
environments_raw = await server.call_tool("vm_list_environments", {})
if not isinstance(environments_raw, tuple) or len(environments_raw) != 2:
raise TypeError("unexpected environments result")
_, environments_structured = environments_raw
if not isinstance(environments_structured, dict):
raise TypeError("environments tool should return a dictionary")
raw_environments = environments_structured.get("result")
if not isinstance(raw_environments, list):
raise TypeError("environments tool did not contain a result list")
created = _extract_structured(
await server.call_tool(
"vm_create",
{"profile": "debian-base", "vcpu_count": 1, "mem_mib": 512, "ttl_seconds": 600},
{
"environment": "debian:12-base",
"vcpu_count": 1,
"mem_mib": 512,
"ttl_seconds": 600,
},
)
)
vm_id = str(created["vm_id"])
@ -120,7 +125,12 @@ def test_vm_tools_status_stop_delete_and_reap(tmp_path: Path) -> None:
expiring = _extract_structured(
await server.call_tool(
"vm_create",
{"profile": "debian-base", "vcpu_count": 1, "mem_mib": 512, "ttl_seconds": 1},
{
"environment": "debian:12-base",
"vcpu_count": 1,
"mem_mib": 512,
"ttl_seconds": 1,
},
)
)
expiring_id = str(expiring["vm_id"])
@ -131,16 +141,16 @@ def test_vm_tools_status_stop_delete_and_reap(tmp_path: Path) -> None:
network,
stopped,
deleted,
cast(list[dict[str, object]], raw_profiles),
cast(list[dict[str, object]], raw_environments),
reaped,
)
status, network, stopped, deleted, profiles, reaped = asyncio.run(_run())
status, network, stopped, deleted, environments, reaped = asyncio.run(_run())
assert status["state"] == "started"
assert network["network_enabled"] is False
assert stopped["state"] == "stopped"
assert bool(deleted["deleted"]) is True
assert profiles[0]["name"] == "debian-base"
assert environments[0]["name"] == "debian:12"
assert int(reaped["count"]) == 1

View file

@ -0,0 +1,153 @@
from __future__ import annotations
import tarfile
from pathlib import Path
import pytest
from pyro_mcp.runtime import resolve_runtime_paths
from pyro_mcp.vm_environments import EnvironmentStore, get_environment, list_environments
def test_list_environments_includes_expected_entries() -> None:
environments = list_environments(runtime_paths=resolve_runtime_paths())
names = {str(entry["name"]) for entry in environments}
assert {"debian:12", "debian:12-base", "debian:12-build"} <= names
def test_get_environment_rejects_unknown() -> None:
with pytest.raises(ValueError, match="unknown environment"):
get_environment("does-not-exist")
def test_environment_store_installs_from_local_runtime_source(tmp_path: Path) -> None:
store = EnvironmentStore(runtime_paths=resolve_runtime_paths(), cache_dir=tmp_path / "cache")
installed = store.ensure_installed("debian:12")
assert installed.kernel_image.exists()
assert installed.rootfs_image.exists()
assert (installed.install_dir / "environment.json").exists()
def test_environment_store_pull_and_cached_inspect(tmp_path: Path) -> None:
store = EnvironmentStore(runtime_paths=resolve_runtime_paths(), cache_dir=tmp_path / "cache")
before = store.inspect_environment("debian:12")
assert before["installed"] is False
pulled = store.pull_environment("debian:12")
assert pulled["installed"] is True
assert "install_manifest" in pulled
cached = store.ensure_installed("debian:12")
assert cached.installed is True
after = store.inspect_environment("debian:12")
assert after["installed"] is True
assert "install_manifest" in after
def test_environment_store_uses_env_override_for_default_cache_dir(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
monkeypatch.setenv("PYRO_ENVIRONMENT_CACHE_DIR", str(tmp_path / "override-cache"))
store = EnvironmentStore(runtime_paths=resolve_runtime_paths())
assert store.cache_dir == tmp_path / "override-cache"
def test_environment_store_installs_from_archive_when_runtime_source_missing(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
runtime_paths = resolve_runtime_paths()
source_environment = get_environment("debian:12-base", runtime_paths=runtime_paths)
archive_dir = tmp_path / "archive"
archive_dir.mkdir(parents=True, exist_ok=True)
(archive_dir / "vmlinux").write_text("kernel\n", encoding="utf-8")
(archive_dir / "rootfs.ext4").write_text("rootfs\n", encoding="utf-8")
archive_path = tmp_path / "environment.tgz"
with tarfile.open(archive_path, "w:gz") as archive:
archive.add(archive_dir / "vmlinux", arcname="vmlinux")
archive.add(archive_dir / "rootfs.ext4", arcname="rootfs.ext4")
missing_bundle = tmp_path / "bundle"
platform_root = missing_bundle / "linux-x86_64"
platform_root.mkdir(parents=True, exist_ok=True)
(missing_bundle / "NOTICE").write_text(
runtime_paths.notice_path.read_text(encoding="utf-8"),
encoding="utf-8",
)
(platform_root / "manifest.json").write_text(
runtime_paths.manifest_path.read_text(encoding="utf-8"),
encoding="utf-8",
)
(platform_root / "bin").mkdir(parents=True, exist_ok=True)
(platform_root / "bin" / "firecracker").write_bytes(runtime_paths.firecracker_bin.read_bytes())
(platform_root / "bin" / "jailer").write_bytes(runtime_paths.jailer_bin.read_bytes())
guest_agent_path = runtime_paths.guest_agent_path
if guest_agent_path is None:
raise AssertionError("expected guest agent path")
(platform_root / "guest").mkdir(parents=True, exist_ok=True)
(platform_root / "guest" / "pyro_guest_agent.py").write_text(
guest_agent_path.read_text(encoding="utf-8"),
encoding="utf-8",
)
monkeypatch.setenv("PYRO_RUNTIME_BUNDLE_DIR", str(missing_bundle))
monkeypatch.setattr(
"pyro_mcp.vm_environments.CATALOG",
{
"debian:12-base": source_environment.__class__(
name=source_environment.name,
version=source_environment.version,
description=source_environment.description,
default_packages=source_environment.default_packages,
distribution=source_environment.distribution,
distribution_version=source_environment.distribution_version,
source_profile=source_environment.source_profile,
platform=source_environment.platform,
source_url=archive_path.resolve().as_uri(),
source_digest=source_environment.source_digest,
compatibility=source_environment.compatibility,
)
},
)
store = EnvironmentStore(
runtime_paths=resolve_runtime_paths(verify_checksums=False),
cache_dir=tmp_path / "cache",
)
installed = store.ensure_installed("debian:12-base")
assert installed.kernel_image.read_text(encoding="utf-8") == "kernel\n"
assert installed.rootfs_image.read_text(encoding="utf-8") == "rootfs\n"
def test_environment_store_prunes_stale_entries(tmp_path: Path) -> None:
store = EnvironmentStore(runtime_paths=resolve_runtime_paths(), cache_dir=tmp_path / "cache")
platform_dir = store.cache_dir / "linux-x86_64"
platform_dir.mkdir(parents=True, exist_ok=True)
(platform_dir / ".partial-download").mkdir()
(platform_dir / "missing-marker").mkdir()
invalid = platform_dir / "invalid"
invalid.mkdir()
(invalid / "environment.json").write_text('{"name": 1, "version": 2}', encoding="utf-8")
unknown = platform_dir / "unknown"
unknown.mkdir()
(unknown / "environment.json").write_text(
'{"name": "unknown:1", "version": "1.0.0"}',
encoding="utf-8",
)
stale = platform_dir / "stale"
stale.mkdir()
(stale / "environment.json").write_text(
'{"name": "debian:12", "version": "0.9.0"}',
encoding="utf-8",
)
result = store.prune_environments()
assert result["count"] == 5

View file

@ -17,7 +17,12 @@ def test_vm_manager_lifecycle_and_auto_cleanup(tmp_path: Path) -> None:
base_dir=tmp_path / "vms",
network_manager=TapNetworkManager(enabled=False),
)
created = manager.create_vm(profile="debian-git", vcpu_count=1, mem_mib=512, ttl_seconds=600)
created = manager.create_vm(
environment="debian:12",
vcpu_count=1,
mem_mib=512,
ttl_seconds=600,
)
vm_id = str(created["vm_id"])
started = manager.start_vm(vm_id)
assert started["state"] == "started"
@ -37,9 +42,12 @@ def test_vm_manager_exec_timeout(tmp_path: Path) -> None:
network_manager=TapNetworkManager(enabled=False),
)
vm_id = str(
manager.create_vm(profile="debian-base", vcpu_count=1, mem_mib=512, ttl_seconds=600)[
"vm_id"
]
manager.create_vm(
environment="debian:12-base",
vcpu_count=1,
mem_mib=512,
ttl_seconds=600,
)["vm_id"]
)
manager.start_vm(vm_id)
result = manager.exec_vm(vm_id, command="sleep 2", timeout_seconds=1)
@ -54,9 +62,12 @@ def test_vm_manager_stop_and_delete(tmp_path: Path) -> None:
network_manager=TapNetworkManager(enabled=False),
)
vm_id = str(
manager.create_vm(profile="debian-base", vcpu_count=1, mem_mib=512, ttl_seconds=600)[
"vm_id"
]
manager.create_vm(
environment="debian:12-base",
vcpu_count=1,
mem_mib=512,
ttl_seconds=600,
)["vm_id"]
)
manager.start_vm(vm_id)
stopped = manager.stop_vm(vm_id)
@ -73,7 +84,12 @@ def test_vm_manager_reaps_expired(tmp_path: Path) -> None:
)
manager.MIN_TTL_SECONDS = 1
vm_id = str(
manager.create_vm(profile="debian-base", vcpu_count=1, mem_mib=512, ttl_seconds=1)["vm_id"]
manager.create_vm(
environment="debian:12-base",
vcpu_count=1,
mem_mib=512,
ttl_seconds=1,
)["vm_id"]
)
instance = manager._instances[vm_id] # noqa: SLF001
instance.expires_at = 0.0
@ -91,7 +107,12 @@ def test_vm_manager_reaps_started_vm(tmp_path: Path) -> None:
)
manager.MIN_TTL_SECONDS = 1
vm_id = str(
manager.create_vm(profile="debian-base", vcpu_count=1, mem_mib=512, ttl_seconds=1)["vm_id"]
manager.create_vm(
environment="debian:12-base",
vcpu_count=1,
mem_mib=512,
ttl_seconds=1,
)["vm_id"]
)
manager.start_vm(vm_id)
manager._instances[vm_id].expires_at = 0.0 # noqa: SLF001
@ -114,7 +135,7 @@ def test_vm_manager_validates_limits(tmp_path: Path, kwargs: dict[str, Any], msg
network_manager=TapNetworkManager(enabled=False),
)
with pytest.raises(ValueError, match=msg):
manager.create_vm(profile="debian-base", **kwargs)
manager.create_vm(environment="debian:12-base", **kwargs)
def test_vm_manager_max_active_limit(tmp_path: Path) -> None:
@ -124,9 +145,9 @@ def test_vm_manager_max_active_limit(tmp_path: Path) -> None:
max_active_vms=1,
network_manager=TapNetworkManager(enabled=False),
)
manager.create_vm(profile="debian-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)
with pytest.raises(RuntimeError, match="max active VMs reached"):
manager.create_vm(profile="debian-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)
def test_vm_manager_state_validation(tmp_path: Path) -> None:
@ -136,9 +157,12 @@ def test_vm_manager_state_validation(tmp_path: Path) -> None:
network_manager=TapNetworkManager(enabled=False),
)
vm_id = str(
manager.create_vm(profile="debian-base", vcpu_count=1, mem_mib=512, ttl_seconds=600)[
"vm_id"
]
manager.create_vm(
environment="debian:12-base",
vcpu_count=1,
mem_mib=512,
ttl_seconds=600,
)["vm_id"]
)
with pytest.raises(RuntimeError, match="must be in 'started' state"):
manager.exec_vm(vm_id, command="echo hi", timeout_seconds=30)
@ -157,7 +181,12 @@ def test_vm_manager_status_expired_raises(tmp_path: Path) -> None:
)
manager.MIN_TTL_SECONDS = 1
vm_id = str(
manager.create_vm(profile="debian-base", vcpu_count=1, mem_mib=512, ttl_seconds=1)["vm_id"]
manager.create_vm(
environment="debian:12-base",
vcpu_count=1,
mem_mib=512,
ttl_seconds=1,
)["vm_id"]
)
manager._instances[vm_id].expires_at = 0.0 # noqa: SLF001
with pytest.raises(RuntimeError, match="expired and was automatically deleted"):
@ -179,7 +208,12 @@ def test_vm_manager_network_info(tmp_path: Path) -> None:
base_dir=tmp_path / "vms",
network_manager=TapNetworkManager(enabled=False),
)
created = manager.create_vm(profile="debian-base", vcpu_count=1, mem_mib=512, ttl_seconds=600)
created = manager.create_vm(
environment="debian:12-base",
vcpu_count=1,
mem_mib=512,
ttl_seconds=600,
)
vm_id = str(created["vm_id"])
status = manager.status_vm(vm_id)
info = manager.network_info_vm(vm_id)
@ -195,7 +229,7 @@ def test_vm_manager_run_vm(tmp_path: Path) -> None:
network_manager=TapNetworkManager(enabled=False),
)
result = manager.run_vm(
profile="debian-base",
environment="debian:12-base",
command="printf 'ok\\n'",
vcpu_count=1,
mem_mib=512,
@ -213,13 +247,13 @@ def test_vm_manager_firecracker_backend_path(
class StubFirecrackerBackend:
def __init__(
self,
artifacts_dir: Path,
environment_store: Any,
firecracker_bin: Path,
jailer_bin: Path,
runtime_capabilities: Any,
network_manager: TapNetworkManager,
) -> None:
self.artifacts_dir = artifacts_dir
self.environment_store = environment_store
self.firecracker_bin = firecracker_bin
self.jailer_bin = jailer_bin
self.runtime_capabilities = runtime_capabilities

View file

@ -1,24 +0,0 @@
from __future__ import annotations
from pathlib import Path
import pytest
from pyro_mcp.vm_profiles import get_profile, list_profiles, resolve_artifacts
def test_list_profiles_includes_expected_entries() -> None:
profiles = list_profiles()
names = {str(entry["name"]) for entry in profiles}
assert {"debian-base", "debian-git", "debian-build"} <= names
def test_get_profile_rejects_unknown() -> None:
with pytest.raises(ValueError, match="unknown profile"):
get_profile("does-not-exist")
def test_resolve_artifacts() -> None:
artifacts = resolve_artifacts(Path("/tmp/artifacts"), "debian-git")
assert str(artifacts.kernel_image).endswith("/debian-git/vmlinux")
assert str(artifacts.rootfs_image).endswith("/debian-git/rootfs.ext4")