Files
RustRaptor/tools/test_hd_boss_cleanup_reproduce.py
T

143 lines
7.6 KiB
Python

"""Synthetic dispatcher tests; never execute cleanup pixel processing."""
import importlib.util
import json
from pathlib import Path
import tempfile
from types import SimpleNamespace
import unittest
from unittest.mock import patch
spec = importlib.util.spec_from_file_location("batch", Path(__file__).with_name("hd_boss_cleanup_reproduce.py"))
batch = importlib.util.module_from_spec(spec)
spec.loader.exec_module(batch)
class BatchTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.root = Path(self.tmp.name)
self.project = self.root / "project"
(self.project / "tools").mkdir(parents=True)
self.base = self.root / "job"
self.base.mkdir()
for n in ["hd_boss_cleanup.py", "hd_frame_policy.py"]:
(self.project / "tools" / n).write_text("# synthetic bound implementation\n")
for n in ["source", "raw", "palette"]:
(self.base / n).write_bytes(n.encode())
def ref(p, base): return {"path": str(p.relative_to(base)), "sha256": batch.sha(p)}
self.doc = {"format_version": 1, "kind": "boss_cleanup_reproduce_batch", "method": batch.METHOD,
"cleanup_tool": ref(self.project / "tools/hd_boss_cleanup.py", self.project),
"decoder": ref(self.project / "tools/hd_frame_policy.py", self.project), "frames": []}
for asset in ["abc", "abd"]:
sub = {"asset": asset, "source": ref(self.base / "source", self.base),
"parent_raw": ref(self.base / "raw", self.base), "palette": ref(self.base / "palette", self.base)}
p = self.base / f"{asset}.json"
p.write_text(json.dumps(sub))
self.doc["frames"].append({"asset": asset, "recipe": ref(p, self.base),
"expected_output_sha256": __import__('hashlib').sha256(asset.encode()).hexdigest()})
self.recipe = self.base / "batch.json"
self.save()
def save(self): self.recipe.write_text(json.dumps(self.doc))
def test_preflight_rejects_bad_tool_before_any_output(self):
self.doc["cleanup_tool"]["sha256"] = "0" * 64
self.save()
with self.assertRaises(ValueError): batch.run_batch(self.recipe, self.base / "out", self.project)
self.assertFalse((self.base / "out").exists())
def test_duplicate_asset_rejected(self):
self.doc["frames"][1] = self.doc["frames"][0].copy()
self.save()
with self.assertRaises(ValueError): batch.plan(self.recipe, self.project)
def test_escaped_recipe_rejected(self):
self.doc["frames"][0]["recipe"]["path"] = "../outside.json"
self.save()
with self.assertRaises(ValueError): batch.plan(self.recipe, self.project)
def test_wrong_subrecipe_asset_rejected(self):
self.doc["frames"][0]["asset"] = "abe"
self.save()
with self.assertRaises(ValueError): batch.plan(self.recipe, self.project)
def test_changed_source_rejected_before_output(self):
(self.base / "source").write_bytes(b"changed")
with self.assertRaises(ValueError): batch.run_batch(self.recipe, self.base / "out", self.project)
self.assertFalse((self.base / "out").exists())
def fake_cleanup(self, subpath, output):
sub = json.loads(subpath.read_text()); asset = sub["asset"]
output.mkdir()
(output / f"{asset}-derived.png").write_bytes(asset.encode())
result = {"execution": {"status": "success", "method": "derived-generated-boss-source-geometry-rgb-preserved-v1", "recipe_sha256": batch.sha(subpath),
"tool_sha256": self.doc["cleanup_tool"]["sha256"], "source_sha256": {asset: sub["source"]["sha256"]},
"parent_raw_sha256": [sub["parent_raw"]["sha256"]],
"output_sha256": {asset: batch.sha(output / f"{asset}-derived.png")}}}
(output / "derived-provenance.json").write_text(json.dumps(result))
return result
def fake_import(self, name):
return SimpleNamespace(__file__=str(self.project / "tools" / f"{name}.py"), run_recipe=self.fake_cleanup)
def test_actual_aggregate_uses_unique_parent_and_per_asset_outputs(self):
with patch.object(batch.importlib, "import_module", side_effect=self.fake_import):
result = batch.run_batch(self.recipe, self.base / "out", self.project)
e = result["execution"]
self.assertEqual(result["actual_cleanup_subruns"], 2)
self.assertEqual(e["recipe_sha256"], batch.sha(self.recipe))
self.assertEqual(e["tool_sha256"], batch.sha(batch.__file__))
self.assertEqual(e["parent_raw_sha256"], [batch.sha(self.base / "raw")])
self.assertEqual(set(e["source_sha256"]), {"abc", "abd"})
self.assertEqual(e["output_sha256"], {r["asset"]: r["expected_output_sha256"] for r in self.doc["frames"]})
def test_wrong_accepted_png_does_not_write_success_receipt(self):
self.doc["frames"][0]["expected_output_sha256"] = "0" * 64
self.save()
with patch.object(batch.importlib, "import_module", side_effect=self.fake_import):
with self.assertRaises(ValueError): batch.run_batch(self.recipe, self.base / "out", self.project)
self.assertFalse((self.base / "out/batch-execution.json").exists())
def test_existing_output_is_never_overwritten(self):
output = self.base / "out"; output.mkdir(); sentinel = output / "old"; sentinel.write_text("old")
with self.assertRaises(ValueError): batch.run_batch(self.recipe, output, self.project)
self.assertEqual(sentinel.read_text(), "old")
def test_all_subrun_execution_fields_must_match_pinned_inputs(self):
original = self.fake_cleanup
fields = ["status", "method", "recipe_sha256", "tool_sha256", "source_sha256", "parent_raw_sha256", "output_sha256"]
for number, field in enumerate(fields):
with self.subTest(field=field):
def contradictory(path, output):
report = original(path, output)
report['execution'][field] = "contradiction"
return report
with patch.object(batch.importlib, "import_module", side_effect=self.fake_import), patch.object(self, "fake_cleanup", side_effect=contradictory):
output = self.base / f"bad-{number}"
with self.assertRaises(ValueError): batch.run_batch(self.recipe, output, self.project)
self.assertFalse((output / 'batch-execution.json').exists())
def test_batch_recipe_edit_during_subrun_prevents_success(self):
original = self.fake_cleanup
def changed(path, output):
report = original(path, output)
self.recipe.write_text(self.recipe.read_text() + ' ')
return report
with patch.object(batch.importlib, "import_module", side_effect=self.fake_import), patch.object(self, "fake_cleanup", side_effect=changed):
with self.assertRaises(ValueError): batch.run_batch(self.recipe, self.base / "out", self.project)
self.assertFalse((self.base / 'out/batch-execution.json').exists())
def test_subrecipe_edit_during_subrun_prevents_success(self):
original = self.fake_cleanup
def changed(path, output):
report = original(path, output)
path.write_text(path.read_text() + ' ')
return report
with patch.object(batch.importlib, "import_module", side_effect=self.fake_import), patch.object(self, "fake_cleanup", side_effect=changed):
with self.assertRaises(ValueError): batch.run_batch(self.recipe, self.base / "out", self.project)
self.assertFalse((self.base / 'out/batch-execution.json').exists())
if __name__ == "__main__": unittest.main()