Verified with the coordinated workspace changes by devkit full run 2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed). This shared UI pass does not mark the individual module reviews complete.
89 lines
3.1 KiB
Python
Executable File
89 lines
3.1 KiB
Python
Executable File
"""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"
|