"""Signals during snapshot probes must not become passing run evidence.""" import os import signal import pytest import test_devkit_runner as fixtures from govoplan_devkit import runner from govoplan_devkit.checkpoints import Checkpoints example = fixtures.example @pytest.mark.parametrize("interruption", [signal.SIGINT, signal.SIGTERM]) @pytest.mark.parametrize("probe", ["source", "environment"]) def test_interrupt_during_final_snapshot_is_not_a_passing_run( example, monkeypatch, interruption, probe ): args, repo = example calls = {"source": 0, "environment": 0} events, interruptions = [], [] args.on_progress = events.append original = Checkpoints.source def reached(kind): calls[kind] += 1 if kind == probe and events and events[-1]["phase"] == "finalizing": # Intermediate checkpoint probes are also real verification. Target # finalization explicitly, while the runner owns signal handlers. interruptions.append(kind) os.kill(os.getpid(), interruption) def source(self, *values, **kwargs): reached("source") return original(self, *values, **kwargs) def environment(*_args, **_kwargs): reached("environment") return "fixture-environment" monkeypatch.setattr(Checkpoints, "source", source) monkeypatch.setattr(runner, "environment_fingerprint", environment) result = runner.run_checks(args, [fixtures.stage(repo)]) assert interruptions == [probe] assert calls[probe] >= 2 assert result["stages"][0]["status"] == "passed" assert result["status"] == "interrupted" assert result["_exit_code"] != 0 receipt = runner.read_receipt(args.workspace_root, args.state_dir, result["run_id"]) assert receipt["status"] == "interrupted" @pytest.mark.parametrize("interruption", [signal.SIGINT, signal.SIGTERM]) @pytest.mark.parametrize("probe", ["source", "environment"]) def test_preparation_interrupt_stops_at_probe_boundary_without_running_checks( example, monkeypatch, interruption, probe ): args, repo = example calls = [] original = Checkpoints.source def reached(kind): calls.append(kind) if kind == probe: os.kill(os.getpid(), interruption) def source(self, *values, **kwargs): reached("source") return original(self, *values, **kwargs) def environment(*_args, **_kwargs): reached("environment") return "fixture-environment" def must_not_execute(*args, **kwargs): pytest.fail("A cancelled preparation must not start a check") monkeypatch.setattr(Checkpoints, "source", source) monkeypatch.setattr(runner, "environment_fingerprint", environment) monkeypatch.setattr(runner, "execute_stage", must_not_execute) result = runner.run_checks(args, [fixtures.stage(repo)]) assert calls == (["source"] if probe == "source" else ["source", "environment"]) assert result["status"] == "interrupted" assert result["snapshot_verified"] is False assert result["_exit_code"] != 0 receipt = runner.read_receipt(args.workspace_root, args.state_dir, result["run_id"]) assert receipt["status"] == "interrupted"