"""Checkpoint integration tests use synthetic Main fixtures only.""" import contextlib import io import json import unittest from unittest.mock import patch import test_wbv2_local as local_fixtures from wbv2_autopilot import write_json class CheckpointTests(unittest.TestCase): setUp = local_fixtures.LocalWorkflowTests.setUp file = local_fixtures.LocalWorkflowTests.file ready_result = local_fixtures.LocalWorkflowTests.ready_result local_contract = local_fixtures.LocalWorkflowTests.local_contract def prepare(self): contract = self.local_contract() self.file("build.py", "from pathlib import Path\np=Path('build-count.txt')\np.write_text(str(int(p.read_text())+1) if p.exists() else '1')\n") native = contract["result"]["checks"][0] native["kinds"].remove("source_build") native["cacheInputs"] = ["source.cpp", "check.py", self.native_source] contract["result"]["checks"].insert(0, {"id": "build", "argv": ["python3", "build.py"], "cwd": ".", "kinds": ["source_build"]}) contract["result"]["dependencies"].append("build.py") write_json(self.root / "local-contract.json", contract) return contract def attempt(self): with contextlib.redirect_stdout(io.StringIO()): return self.runner.attempt(self.context, self.job) def test_native_build_stays_incremental_and_runtime_success_is_reused(self): self.prepare() self.assertTrue(self.attempt()) first = self.runner.state["jobs"][self.job["id"]]["runDirectory"] self.assertTrue(self.attempt()) self.assertEqual("2", (self.root / "build-count.txt").read_text()) self.assertEqual("1", (self.root / "executions.txt").read_text()) record = self.runner.state["jobs"][self.job["id"]] self.assertTrue(self.runner.receipt_valid(record)) receipt = json.loads((self.root / record["receipt"]).read_text()) cached = next(check for check in receipt["checks"] if check["id"] == "native") self.assertIn("reusedFrom", cached) self.assertTrue(all(case["receipt"].startswith(first) for case in cached["caseBindings"])) def test_later_failure_preserves_success_when_only_failed_step_is_repaired(self): contract = self.prepare() self.file("failure.py", "raise SystemExit(1)\n") contract["result"]["dependencies"].append("failure.py") contract["result"]["checks"].append({"id": "failure", "argv": ["python3", "failure.py"], "cwd": ".", "kinds": ["negative"]}) write_json(self.root / "local-contract.json", contract) self.assertFalse(self.attempt()) record = self.runner.state["jobs"][self.job["id"]] checkpoint = json.loads((self.root / record["runDirectory"] / "step-checkpoint.json").read_text()) self.assertEqual("failure", checkpoint["failures"][0]["checkId"]) self.assertEqual("passed", checkpoint["outcomes"]["native"]) self.file("failure.py", "raise SystemExit(0)\n") self.assertTrue(self.attempt()) self.assertEqual("1", (self.root / "executions.txt").read_text()) def test_artifact_change_invalidates_cached_runtime(self): self.prepare() self.assertTrue(self.attempt()) self.file("engine.wasm", "changed synthetic artifact\n") self.assertTrue(self.attempt()) self.assertEqual("2", (self.root / "executions.txt").read_text()) def test_failure_continues_independent_stage_and_blocks_dependent_stage(self): contract = self.prepare() self.file("failure.py", "raise SystemExit(1)\n") self.file("independent.py", "from pathlib import Path\nPath('independent-ran').touch()\n") contract["result"]["dependencies"] += ["failure.py", "independent.py"] contract["result"]["checks"] += [ {"id": "failure", "argv": ["python3", "failure.py"], "cwd": ".", "kinds": ["negative"], "continueOnFailure": True}, {"id": "dependent", "argv": ["python3", "failure.py"], "cwd": ".", "kinds": ["negative"], "dependsOn": ["failure"]}, {"id": "independent", "argv": ["python3", "independent.py"], "cwd": ".", "kinds": ["positive"]}, ] write_json(self.root / "local-contract.json", contract) self.assertFalse(self.attempt()) self.assertTrue((self.root / "independent-ran").exists()) record = self.runner.state["jobs"][self.job["id"]] checkpoint = json.loads((self.root / record["runDirectory"] / "step-checkpoint.json").read_text()) self.assertEqual("blocked", checkpoint["outcomes"]["dependent"]) self.assertEqual("passed", checkpoint["outcomes"]["independent"]) self.assertNotIn("receipt", record) def test_per_stage_timeout_overrides_global_timeout(self): contract = self.prepare() contract["result"]["checks"][1]["timeoutSeconds"] = 5 write_json(self.root / "local-contract.json", contract) with patch.object(self.runner, "run_process", wraps=self.runner.run_process) as run: self.assertTrue(self.attempt()) self.assertEqual(5, run.call_args_list[1].args[3]) def test_verification_requests_are_parent_bound_and_do_not_advance_queue(self): self.prepare() request = self.runner.directory / "requests" / "pending" / "valid.json" write_json(request, {"task": self.task, "parentSha256": self.context["parentSha256"], "job": self.job["id"]}) self.assertEqual(self.job, self.runner.requested_job(self.context, [self.job])) self.assertEqual(request, self.runner.pending_request) self.assertTrue(request.is_file()) request.unlink() write_json(request, {"task": self.task, "parentSha256": "0" * 64, "job": self.job["id"]}) with contextlib.redirect_stdout(io.StringIO()): self.assertIsNone(self.runner.requested_job(self.context, [self.job])) self.assertFalse(request.exists()) self.assertTrue((self.runner.directory / "requests" / "rejected" / "valid.json").is_file()) def test_process_receipt_includes_resource_observations(self): self.prepare() self.assertTrue(self.attempt()) record = self.runner.state["jobs"][self.job["id"]] receipt = json.loads((self.root / record["receipt"]).read_text()) usage = receipt["checks"][0]["resourceUsage"] self.assertGreaterEqual(usage["reapedChildCpuSeconds"], 0) self.assertGreater(usage["samples"], 0) def test_repeated_cli_start_returns_existing_owner_without_traceback(self): import run_web_product_migration as cli self.runner.state.update(state="waiting_local_work", pid=123, identity="fake", executionMode="local_only") output = io.StringIO() with patch.object(cli, "main", side_effect=RuntimeError("another Python autopilot owns the project")), patch.object(cli, "Autopilot", return_value=self.runner), patch.object(cli, "alive", return_value=True), patch.object(cli, "identity", return_value="fake"), contextlib.redirect_stdout(output): self.assertEqual(0, cli.entrypoint()) self.assertEqual("already_running", json.loads(output.getvalue())["state"]) if __name__ == "__main__": unittest.main()