feat(devkit): add resumable workspace automation and UI review tooling
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.
This commit is contained in:
Executable
+88
@@ -0,0 +1,88 @@
|
||||
"""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"
|
||||
Reference in New Issue
Block a user