Files
AgentLab/backend/tests/test_microvm_kvm.py
T
ThearchyHelios 9d8b41430e 完善口径管理、主动调查与后台任务,修复通用问题
- 完善口径编辑、只读试运行、统一质量验证与执行语义指纹
- 增加可执行指标约束、有预算的主动调查和过程评测
- 提取发布检查、证据读取与起草服务,支持任务停止、重试和检查点恢复
- 增加版本传递影响分析,修复并发覆盖、敏感值遮罩、异常评分及页面状态问题
- 修复 SQLite 复合查询追溯和 microVM 可选依赖缺失处理
- 纳入当前设计文档及部署配置调整

验证:相关主回归 3822 项通过,4 项跳过、8 项预期失败;补充回归通过。
前端构建、文案检查及 git diff --check 通过。
全量检查仍有环境权限、PostgreSQL 可选依赖和既有部署配置、SQLite 文件基线差异导致的未通过项。
2026-11-06 17:14:45 +08:00

162 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Linux 上 microVM 没有 KVM 就起不来,可用性判断要把这一条算进去。
以前 available() 只看运行时装没装。运行时和沙箱镜像打进 Docker 镜像以后,没有 KVM 的机器上
(Mac 上的 Docker Desktop、没开嵌套虚拟化的云主机)auto 会选中 microVM,每次执行代码都启动失败。
selftest 是部署脚本用的:真起一台 VM,起得来才把 /dev/kvm 交给容器。
"""
from __future__ import annotations
import asyncio
import sys
from pathlib import Path
from types import ModuleType
import pytest
from app.sandbox import manager as mgr
from app.sandbox import microvm_sandbox as mv
from app.sandbox import selftest
from app.sandbox.base import ExecResult
from app.sandbox.local_sandbox import LocalSandbox
@pytest.fixture
def linux(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path:
"""在 Linux 上、运行时已装好、镜像已缓存;返回假的 /dev/kvm 路径,默认不存在。"""
kvm = tmp_path / "kvm"
# 此组模拟运行时已经就绪,SDK 是否安装不应影响 KVM 分支的测试。
monkeypatch.setitem(sys.modules, "microsandbox", ModuleType("microsandbox"))
monkeypatch.setattr(mv, "_on_linux", lambda: True)
monkeypatch.setattr(mv, "_KVM_DEVICE", kvm)
monkeypatch.setattr(mv.MicroVMSandbox, "_runtime_installed", staticmethod(lambda: True))
monkeypatch.setattr(mv.MicroVMSandbox, "image_ready", staticmethod(lambda: True))
return kvm
def test_no_kvm_device_means_unavailable(linux: Path) -> None:
assert mv.MicroVMSandbox.available() is False
reason = mv.MicroVMSandbox.unavailable_reason()
assert "/dev/kvm" in reason and "挂进容器" in reason
def test_missing_sdk_returns_a_result_without_import_crashing(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setitem(sys.modules, "microsandbox", None)
result = asyncio.run(mv.MicroVMSandbox().run("print(1)"))
assert not result.ok and "未安装 microsandbox" in result.error
def test_kvm_without_permission_means_unavailable(linux: Path, monkeypatch: pytest.MonkeyPatch) -> None:
linux.touch()
monkeypatch.setattr(mv.os, "access", lambda path, mode: False)
assert mv.MicroVMSandbox.available() is False
assert "kvm 组" in mv.MicroVMSandbox.unavailable_reason()
def test_kvm_present_and_writable(linux: Path, monkeypatch: pytest.MonkeyPatch) -> None:
linux.touch()
monkeypatch.setattr(mv.os, "access", lambda path, mode: True)
assert mv.MicroVMSandbox.available() is True
assert mv.MicroVMSandbox.unavailable_reason() == ""
def test_macos_does_not_need_kvm(linux: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(mv, "_on_linux", lambda: False)
assert not linux.exists()
assert mv.kvm_problem() == ""
assert mv.MicroVMSandbox.available() is True
def test_runtime_missing_is_reported_before_kvm(linux: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(mv.MicroVMSandbox, "_runtime_installed", staticmethod(lambda: False))
assert "运行时未就绪" in mv.MicroVMSandbox.unavailable_reason()
def test_auto_skips_microvm_without_kvm(linux: Path, monkeypatch: pytest.MonkeyPatch) -> None:
# 运行时装了、镜像也缓存了,只差 KVM:auto 不能选它
monkeypatch.setattr(mgr.BubblewrapSandbox, "available", staticmethod(lambda: False))
monkeypatch.setattr(mgr.BubblewrapSandbox, "probe_failure", staticmethod(lambda: ""))
monkeypatch.setattr(mgr.SeatbeltSandbox, "available", staticmethod(lambda: False))
monkeypatch.setattr(mgr.settings, "sandbox_backend", "auto")
manager = mgr.SandboxManager()
assert isinstance(manager._resolve(), LocalSandbox)
# 节点上选 strict 档也点不到它
assert manager._resolve_named("strict") is None
def test_auto_picks_microvm_with_kvm(linux: Path, monkeypatch: pytest.MonkeyPatch) -> None:
linux.touch()
monkeypatch.setattr(mv.os, "access", lambda path, mode: True)
monkeypatch.setattr(mgr.settings, "sandbox_backend", "auto")
manager = mgr.SandboxManager()
assert isinstance(manager._resolve(), mv.MicroVMSandbox)
def test_run_says_why_without_booting(linux: Path, monkeypatch: pytest.MonkeyPatch) -> None:
async def must_not_boot(*args, **kwargs): # noqa: ANN002, ANN003, ANN202
raise AssertionError("没有 KVM 还去启动 VM")
monkeypatch.setattr(mv.MicroVMSandbox, "_lease", must_not_boot)
result = asyncio.run(mv.MicroVMSandbox().run("print(1)"))
assert result.ok is False
assert result.error and result.error.startswith("microVM 不可用:") and "/dev/kvm" in result.error
def test_health_reports_missing_kvm(linux: Path) -> None:
info = asyncio.run(mv.MicroVMSandbox().health())
assert info["available"] is False
assert "/dev/kvm" in info["error"]
assert "KVM" in info["isolation"]
# ---------------- selftest ----------------
def _fake_run(result: ExecResult, calls: list[str]):
async def run(self, code, **kwargs): # noqa: ANN001, ANN003, ANN202
calls.append(code)
return result
return run
def test_selftest_without_kvm_does_not_boot(linux: Path, monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str]) -> None:
calls: list[str] = []
monkeypatch.setattr(mv.MicroVMSandbox, "run", _fake_run(ExecResult(), calls))
assert selftest.main() == 1
assert calls == []
out = capsys.readouterr().out.strip()
assert out.startswith("microVM 不可用:") and "/dev/kvm" in out
def test_selftest_passes_when_the_vm_prints_the_mark(linux: Path, monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str]) -> None:
linux.touch()
monkeypatch.setattr(mv.os, "access", lambda path, mode: True)
calls: list[str] = []
ok = ExecResult(stdout=selftest.MARK + "\n", duration_ms=812, backend="microvm")
monkeypatch.setattr(mv.MicroVMSandbox, "run", _fake_run(ok, calls))
assert selftest.main() == 0
assert len(calls) == 1 and selftest.MARK in calls[0]
assert "812 ms" in capsys.readouterr().out
def test_selftest_reports_boot_failure(linux: Path, monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str]) -> None:
linux.touch()
monkeypatch.setattr(mv.os, "access", lambda path, mode: True)
failed = ExecResult(ok=False, backend="microvm", error="microVM 启动失败:虚拟机没有应答")
monkeypatch.setattr(mv.MicroVMSandbox, "run", _fake_run(failed, []))
assert selftest.main() == 1
assert capsys.readouterr().out.strip() == "microVM 启动失败:虚拟机没有应答"
def test_selftest_does_not_trust_exit_zero_without_output(linux: Path, monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str]) -> None:
# 退出码 0 但什么都没打印:VM 里的代码其实没跑起来,不能当成功
linux.touch()
monkeypatch.setattr(mv.os, "access", lambda path, mode: True)
monkeypatch.setattr(mv.MicroVMSandbox, "run", _fake_run(ExecResult(backend="microvm"), []))
assert selftest.main() == 1
assert capsys.readouterr().out.strip().startswith("microVM 启动失败:")