test: add coverage for monitor/vm/winrm to reach 90%% gate
Lint / pssa (push) Successful in 35s
Lint / python (push) Successful in 42s

Bring monitor.py 49->99%%, vm.py 79->98%%, winrm.py 76->100%%. Total project coverage 80.10%% -> 90.69%%, satisfying the 90%% gate set in commit 451a61c.
This commit is contained in:
2026-05-14 23:14:08 +02:00
parent 451a61c6a1
commit b2b31f4b6e
4 changed files with 974 additions and 0 deletions
+288
View File
@@ -209,3 +209,291 @@ def test_vm_cleanup_removes_stale_lock(monkeypatch: pytest.MonkeyPatch, tmp_path
)
assert result.exit_code == 0
assert not lock.exists()
# ── helpers + uncovered branches ──────────────────────────────────────────
def test_make_backend_with_vmrun_path(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
"""Override path returns a WorkstationVmrunBackend honouring vmrun_path."""
from ci_orchestrator.backends.workstation import WorkstationVmrunBackend
class _StubConfig:
pass
fake_vmrun = tmp_path / "vmrun.exe"
fake_vmrun.write_text("x")
monkeypatch.setattr(vm_module, "load_config", lambda: _StubConfig())
backend = vm_module._make_backend(str(fake_vmrun))
assert isinstance(backend, WorkstationVmrunBackend)
def test_make_backend_default_uses_load_backend(monkeypatch: pytest.MonkeyPatch) -> None:
sentinel = object()
monkeypatch.setattr(vm_module, "load_config", lambda: "cfg")
monkeypatch.setattr(vm_module, "load_backend", lambda _c: sentinel)
assert vm_module._make_backend(None) is sentinel
def test_try_remove_dir_returns_true_when_missing(tmp_path: Path) -> None:
assert vm_module._try_remove_dir(tmp_path / "ghost") is True
def test_try_remove_dir_real_removal(tmp_path: Path) -> None:
d = tmp_path / "x"
(d / "sub").mkdir(parents=True)
(d / "sub" / "f.txt").write_text("a")
assert vm_module._try_remove_dir(d) is True
assert not d.exists()
def test_stop_with_fallback_soft_error_then_running_with_hard_error() -> None:
from ci_orchestrator.backends.errors import BackendOperationFailed
class _B:
def __init__(self) -> None:
self.calls: list[tuple[str, bool]] = []
def stop(self, _h: VmHandle, hard: bool = False) -> None:
self.calls.append(("stop", hard))
raise BackendOperationFailed("stop", 1, "boom")
def is_running(self, _h: VmHandle) -> bool:
return True
b = _B()
# Should swallow both errors.
vm_module._stop_with_fallback(b, VmHandle(identifier="x.vmx"))
assert ("stop", False) in b.calls
assert ("stop", True) in b.calls
def test_vm_remove_backend_unavailable(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
from ci_orchestrator.backends.errors import BackendNotAvailable
clone_dir, vmx = _make_clone_dir(tmp_path, "noback")
def _raise(_v: Any) -> Any:
raise BackendNotAvailable("vmrun missing")
monkeypatch.setattr(vm_module, "_make_backend", _raise)
result = CliRunner().invoke(cli, ["vm", "remove", "--vmx", str(vmx)])
assert result.exit_code == 0, result.output
assert "vmrun unavailable" in result.output
assert not clone_dir.exists()
def test_vm_remove_force_hard_stop_error(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
from ci_orchestrator.backends.errors import BackendOperationFailed
_, vmx = _make_clone_dir(tmp_path, "forcefail")
class _B:
def stop(self, _h: VmHandle, hard: bool = False) -> None:
raise BackendOperationFailed("stop", 1, "no")
def is_running(self, _h: VmHandle) -> bool:
return False
def delete(self, _h: VmHandle) -> None:
pass
monkeypatch.setattr(vm_module, "_make_backend", lambda _v: _B())
result = CliRunner().invoke(cli, ["vm", "remove", "--vmx", str(vmx), "--force"])
assert result.exit_code == 0
assert "hard stop failed" in result.output
def test_vm_remove_deadline_loop_with_running_then_error(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""Cover the deadline loop that breaks on BackendError."""
from ci_orchestrator.backends.errors import BackendOperationFailed
_, vmx = _make_clone_dir(tmp_path, "loop")
class _B:
def __init__(self) -> None:
self._calls = 0
def stop(self, _h: VmHandle, hard: bool = False) -> None:
pass
def is_running(self, _h: VmHandle) -> bool:
self._calls += 1
if self._calls == 1:
return True
raise BackendOperationFailed("isrunning", 1, "lost")
def delete(self, _h: VmHandle) -> None:
pass
# Make monotonic strictly advance so the loop runs at least twice but exits.
base_t = [0.0]
def _mono() -> float:
base_t[0] += 1.0
return base_t[0]
monkeypatch.setattr(vm_module.time, "monotonic", _mono)
monkeypatch.setattr(vm_module, "_make_backend", lambda _v: _B())
result = CliRunner().invoke(cli, ["vm", "remove", "--vmx", str(vmx)])
assert result.exit_code == 0
def test_vm_remove_delete_fails_all_retries(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""Delete that fails on every retry triggers fallback dir-removal log."""
clone_dir, vmx = _make_clone_dir(tmp_path, "neverdeletes")
class _B:
def stop(self, _h: VmHandle, hard: bool = False) -> None:
pass
def is_running(self, _h: VmHandle) -> bool:
return False
def delete(self, _h: VmHandle) -> None:
from ci_orchestrator.backends.errors import BackendOperationFailed
raise BackendOperationFailed("delete", 1, "always")
monkeypatch.setattr(vm_module, "_make_backend", lambda _v: _B())
result = CliRunner().invoke(cli, ["vm", "remove", "--vmx", str(vmx)])
assert result.exit_code == 0
assert "deleteVM failed after retries" in result.output
assert not clone_dir.exists()
def test_vm_remove_clone_dir_removal_failure(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""When _try_remove_dir reports failure, the manual-cleanup notice fires."""
_, vmx = _make_clone_dir(tmp_path, "stubborn")
backend = _FakeBackend()
monkeypatch.setattr(vm_module, "_make_backend", lambda _v: backend)
monkeypatch.setattr(vm_module, "_try_remove_dir", lambda _p: False)
result = CliRunner().invoke(cli, ["vm", "remove", "--vmx", str(vmx)])
assert result.exit_code == 0
assert "manual cleanup needed" in result.output
def test_list_orphans_non_dir_base(tmp_path: Path) -> None:
f = tmp_path / "file.txt"
f.write_text("x")
assert vm_module._list_orphans(f, 4) == []
def test_list_orphans_skips_non_dir_and_oserror(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
base = tmp_path / "b"
base.mkdir()
(base / "a-file").write_text("not a dir")
bad_dir = base / "bad"
bad_dir.mkdir()
_age_dir(bad_dir, hours=10)
real_stat = Path.stat
def _stat(self: Path, *a: Any, **kw: Any) -> Any:
if self == bad_dir:
raise OSError("nope")
return real_stat(self, *a, **kw)
monkeypatch.setattr(Path, "stat", _stat)
assert vm_module._list_orphans(base, 4) == []
def test_find_vmx_returns_none(tmp_path: Path) -> None:
d = tmp_path / "novmx"
d.mkdir()
(d / "readme.txt").write_text("x")
assert vm_module._find_vmx(d) is None
def test_cleanup_orphans_dir_without_vmx_logs_and_removes(tmp_path: Path) -> None:
base = tmp_path / "b"
base.mkdir()
no_vmx = base / "no-vmx"
no_vmx.mkdir()
_age_dir(no_vmx, hours=10)
runner_count = vm_module.cleanup_orphans(
clone_base=base, max_age_hours=4, backend=None
)
assert runner_count == 1
assert not no_vmx.exists()
def test_cleanup_orphans_backend_stop_error_swallowed(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
from ci_orchestrator.backends.errors import BackendOperationFailed
base = tmp_path / "b"
base.mkdir()
old, _ = _make_clone_dir(base, "old1")
_age_dir(old, hours=10)
class _B:
def stop(self, _h: VmHandle, hard: bool = False) -> None:
raise BackendOperationFailed("stop", 1, "no")
def delete(self, _h: VmHandle) -> None:
raise BackendOperationFailed("delete", 1, "no")
count = vm_module.cleanup_orphans(
clone_base=base, max_age_hours=4, backend=_B()
)
assert count == 1
def test_cleanup_orphans_dir_removal_failure_logs(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
base = tmp_path / "b"
base.mkdir()
old, _ = _make_clone_dir(base, "stuck")
_age_dir(old, hours=10)
monkeypatch.setattr(vm_module, "_try_remove_dir", lambda _p: False)
count = vm_module.cleanup_orphans(
clone_base=base, max_age_hours=4, backend=None
)
assert count == 0
def test_cleanup_orphans_lock_unlink_oserror(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
base = tmp_path / "b"
base.mkdir()
lock = tmp_path / "stale.lock"
lock.write_text("x")
_age_dir(lock, hours=1)
real_unlink = Path.unlink
def _boom(self: Path, *a: Any, **kw: Any) -> Any:
if self == lock:
raise OSError("locked")
return real_unlink(self, *a, **kw)
monkeypatch.setattr(Path, "unlink", _boom)
vm_module.cleanup_orphans(
clone_base=base, max_age_hours=4, backend=None, lock_file=lock
)
def test_vm_cleanup_backend_unavailable(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
from ci_orchestrator.backends.errors import BackendNotAvailable
base = tmp_path / "build-vms"
base.mkdir()
def _raise(_v: Any) -> Any:
raise BackendNotAvailable("no vmrun")
monkeypatch.setattr(vm_module, "_make_backend", _raise)
result = CliRunner().invoke(
cli, ["vm", "cleanup", "--clone-base-dir", str(base)]
)
assert result.exit_code == 0
assert "vmrun not available" in result.output