153 lines
5.9 KiB
Python
153 lines
5.9 KiB
Python
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
|