Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer, web workflow engine and protocol, tooling, docs and task records. Large generated evidence, user-testing archives, build snapshots and imported test assets remain untracked. Co-Authored-By: Claude Code <noreply@anthropic.com>
130 lines
7.1 KiB
Python
130 lines
7.1 KiB
Python
"""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()
|