feat(template): cleanup-after-update + ruff fixes
Move disk cleanup out of the Windows Update loop: - PS scripts: extract Invoke-DiskCleanup function, add -CleanupOnly switch for standalone post-update cleanup pass. - Python command: always pass -SkipCleanup:$true during WU loop; after loop exits 0, run a separate -CleanupOnly invocation (skipped if --skip-cleanup). Fails fast if cleanup-only returns nonzero. - Tests: 3 new tests for cleanup-runs-after-loop, skip-cleanup suppresses it, and cleanup-failure propagates as ClickException. - ruff --fix: remove unused shutil import, sort imports, fix UP037/UP035. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,6 @@ from __future__ import annotations
|
||||
import shutil
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
@@ -13,7 +12,6 @@ from click.testing import CliRunner
|
||||
import ci_orchestrator.commands.retention as ret
|
||||
from ci_orchestrator.__main__ import cli
|
||||
|
||||
|
||||
# ── helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import ci_orchestrator.commands.template as tmpl_mod
|
||||
from ci_orchestrator.__main__ import cli
|
||||
from ci_orchestrator.transport.winrm import WinRmResult
|
||||
|
||||
|
||||
# ── fixtures ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -345,7 +344,7 @@ def test_parse_exit_marker_malformed() -> None:
|
||||
|
||||
def test_wait_tcp_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
class _FakeConn:
|
||||
def __enter__(self) -> "_FakeConn":
|
||||
def __enter__(self) -> _FakeConn:
|
||||
return self
|
||||
|
||||
def __exit__(self, *_: object) -> None:
|
||||
@@ -699,7 +698,7 @@ def test_prepare_win_recreate_snapshot_flag(
|
||||
def test_prepare_win_skip_flags(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""--skip-windows-update/tier2/cleanup flags reach the invoke script."""
|
||||
"""-SkipCleanup:$true always in loop args; --skip-cleanup suppresses post-loop cleanup call."""
|
||||
vmx = _setup_prepare_env(tmp_path, with_static_ip=False)
|
||||
monkeypatch.chdir(tmp_path)
|
||||
backend = _make_backend([True, False])
|
||||
@@ -1030,3 +1029,116 @@ def test_prepare_win_store_credential_default_target(
|
||||
assert result.exit_code == 0, result.output
|
||||
assert any(s[0] == "BuildVMGuest:meta" for s in stored)
|
||||
assert any(s[0] == "BuildVMGuest" for s in stored)
|
||||
|
||||
|
||||
def test_prepare_win_cleanup_runs_after_loop(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""CleanupOnly invocation fires after WU loop exits 0 (default, no --skip-cleanup)."""
|
||||
vmx = _setup_prepare_env(tmp_path, with_static_ip=False)
|
||||
monkeypatch.chdir(tmp_path)
|
||||
backend = _make_backend([True, False])
|
||||
|
||||
captured_scripts: list[str] = []
|
||||
|
||||
def _run(script: str = "", **kwargs: object) -> WinRmResult:
|
||||
captured_scripts.append(script)
|
||||
if "ConvertTo-Json" in script:
|
||||
return WinRmResult(stdout="{}", stderr="", returncode=0)
|
||||
return WinRmResult(stdout="CI_EXITCODE:0", stderr="", returncode=0)
|
||||
|
||||
transport: MagicMock = MagicMock()
|
||||
transport.run.side_effect = _run
|
||||
transport.is_ready.return_value = True
|
||||
|
||||
_patch_prepare(monkeypatch, backend, transport)
|
||||
|
||||
result = CliRunner().invoke(
|
||||
cli,
|
||||
[
|
||||
"template", "prepare-win",
|
||||
"--vmx", str(vmx),
|
||||
"--ip", "192.168.1.100",
|
||||
"--admin-password", "adminpass",
|
||||
"--build-password", "buildpass",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
assert any("CleanupOnly:$true" in s for s in captured_scripts), (
|
||||
"Expected a -CleanupOnly:$true script after the WU loop"
|
||||
)
|
||||
assert "Post-update cleanup complete" in result.output
|
||||
|
||||
|
||||
def test_prepare_win_skip_cleanup_no_cleanup_call(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""--skip-cleanup suppresses the post-loop CleanupOnly invocation."""
|
||||
vmx = _setup_prepare_env(tmp_path, with_static_ip=False)
|
||||
monkeypatch.chdir(tmp_path)
|
||||
backend = _make_backend([True, False])
|
||||
|
||||
captured_scripts: list[str] = []
|
||||
|
||||
def _run(script: str = "", **kwargs: object) -> WinRmResult:
|
||||
captured_scripts.append(script)
|
||||
if "ConvertTo-Json" in script:
|
||||
return WinRmResult(stdout="{}", stderr="", returncode=0)
|
||||
return WinRmResult(stdout="CI_EXITCODE:0", stderr="", returncode=0)
|
||||
|
||||
transport: MagicMock = MagicMock()
|
||||
transport.run.side_effect = _run
|
||||
transport.is_ready.return_value = True
|
||||
|
||||
_patch_prepare(monkeypatch, backend, transport)
|
||||
|
||||
result = CliRunner().invoke(
|
||||
cli,
|
||||
[
|
||||
"template", "prepare-win",
|
||||
"--vmx", str(vmx),
|
||||
"--ip", "192.168.1.100",
|
||||
"--admin-password", "adminpass",
|
||||
"--build-password", "buildpass",
|
||||
"--skip-cleanup",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
assert not any("CleanupOnly:$true" in s for s in captured_scripts), (
|
||||
"--skip-cleanup must suppress the -CleanupOnly:$true call"
|
||||
)
|
||||
|
||||
|
||||
def test_prepare_win_cleanup_only_fails(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""Cleanup-only step returning nonzero exits the command with an error."""
|
||||
vmx = _setup_prepare_env(tmp_path, with_static_ip=False)
|
||||
monkeypatch.chdir(tmp_path)
|
||||
backend = _make_backend([True])
|
||||
|
||||
def _run(script: str = "", **kwargs: object) -> WinRmResult:
|
||||
if "CleanupOnly" in script:
|
||||
return WinRmResult(stdout="CI_EXITCODE:1", stderr="cleanup error", returncode=0)
|
||||
if "ConvertTo-Json" in script:
|
||||
return WinRmResult(stdout="{}", stderr="", returncode=0)
|
||||
return WinRmResult(stdout="CI_EXITCODE:0", stderr="", returncode=0)
|
||||
|
||||
transport: MagicMock = MagicMock()
|
||||
transport.run.side_effect = _run
|
||||
transport.is_ready.return_value = True
|
||||
|
||||
_patch_prepare(monkeypatch, backend, transport)
|
||||
|
||||
result = CliRunner().invoke(
|
||||
cli,
|
||||
[
|
||||
"template", "prepare-win",
|
||||
"--vmx", str(vmx),
|
||||
"--ip", "192.168.1.100",
|
||||
"--admin-password", "adminpass",
|
||||
"--build-password", "buildpass",
|
||||
],
|
||||
)
|
||||
assert result.exit_code != 0
|
||||
assert "Disk cleanup failed" in result.output
|
||||
|
||||
Reference in New Issue
Block a user