Files
troupe-standup/wizard-work/spark/test_json_sha256_validator.py
T
volt 844c55a856 volt: Q80 -- json_sha256_validator hashes the bytes it parsed, refuses NaN, splits rc 1/2
Q80 (spark lane by authorship, unclaimed and taken). Pre-repair blob at e8a916f:
sha256 49f0f3e2, md5 67c34c9c, 60 lines -- two opens per input (digest of the
second, validated document A, printed hash of B), NaN and Infinity rc 0 VALID,
duplicate members silently collapsed, and a missing path / directory / syntax
error all rc 1.

Repaired: 185 lines, sha256 26cb270c. One read serves both halves
(Path.read_bytes once, same bytes hashed and parsed), one decode (utf-8-sig;
BOM tolerated and documented, other encodings rc 1), parse_constant refuses
NaN/Infinity (-Infinity) with the RFC 8259 s6 citation, object_pairs_hook
refuses duplicate member names unless --allow-duplicates, rc 0 valid / 1 not
JSON / 2 could not grade with no verdict and no digest printed for any input,
and --results writes the invocation plus each input's blob sha.

Cells: wizard-work/spark/test_json_sha256_validator.py (new), 15 cells,
sha256 0d2038ea -- "Ran 15 tests ... OK", rc 0. Five mutants, control green in
the same run: M1 parse_constant removed 2 failures; M2 pre-repair blob 11
failures + 2 errors; M2b a second read put back 1 failure ("subject read 2
times: {'read_bytes': 2, 'open': 0}"); M3 ungradable as rc 1 4 failures; M4
duplicates accepted with the flag off 1 failure.

Wiring: wizard-work/spark/CHECK leg 13. bash CHECK in this tree -> VERIFIED,
rc 0; the same gate in a same-depth scratch tree with the pre-repair module
restored -> rc 13.

Gate: python3 tools/verify.py --repo . before landing reads 9 legs, 8 pass,
1 fault -- path-claims names this commit's own new file as untracked, which
the commit clears; re-read after landing is in the tick report.

CHECK: python3 wizard-work/spark/test_json_sha256_validator.py
2026-10-30 12:54:41 +00:00

199 lines
7.4 KiB
Python

#!/usr/bin/env python3
"""Cells for wizard-work/spark/json_sha256_validator.py (Q80).
Black box: every CLI reading runs the shipped module as a subprocess, so the
cells cannot agree with the code by sharing an implementation. The one
in-process cell imports the module by path to count how many times the subject
is read -- the property Q80 asks for, which a subprocess cannot observe.
Run: python3 wizard-work/spark/test_json_sha256_validator.py
Exit 0 iff every cell passes.
"""
import hashlib
import importlib.util
import io
import json
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
TOOL = Path(__file__).resolve().with_name("json_sha256_validator.py")
def run(*args, cwd=None):
proc = subprocess.run([sys.executable, str(TOOL), *args],
capture_output=True, text=True, cwd=cwd)
return proc.returncode, proc.stdout, proc.stderr
class Base(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.dir = Path(self._tmp.name)
self.ok = self.write("ok.json", b'{"a": [1, 2, 3], "b": "x"}\n')
self.nan = self.write("nan.json", b'{"a": NaN}\n')
self.inf = self.write("inf.json", b'{"a": Infinity}\n')
self.neginf = self.write("neginf.json", b'{"a": -Infinity}\n')
self.dup = self.write("dup.json", b'{"a": 1, "a": 2}\n')
self.broken = self.write("broken.json", b'{"a": 1,\n')
self.bom = self.write("bom.json", b'\xef\xbb\xbf{"a": 1}\n')
def tearDown(self):
self._tmp.cleanup()
def write(self, name, data):
path = self.dir / name
path.write_bytes(data)
return path
def digest(self, path):
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
class Contract(Base):
def test_usage_error_is_rc_2(self):
rc, out, err = run()
self.assertEqual(rc, 2, out + err)
def test_valid_document_rc_0_and_digest_covers_the_file(self):
rc, out, err = run(str(self.ok))
self.assertEqual(rc, 0, out + err)
self.assertIn("VALID", out)
self.assertIn(f"SHA256={self.digest(self.ok)}", out)
def test_nan_is_invalid_rc_1(self):
rc, out, err = run(str(self.nan))
self.assertEqual(rc, 1, out + err)
self.assertIn("INVALID", out)
self.assertIn("NaN", out)
self.assertNotIn("SHA256", out)
def test_infinity_and_negative_infinity_are_invalid(self):
for path, name in ((self.inf, "Infinity"), (self.neginf, "Infinity")):
rc, out, err = run(str(path))
self.assertEqual(rc, 1, f"{path.name}: {out}{err}")
self.assertIn("INVALID", out)
self.assertIn(name, out)
def test_duplicate_member_names_are_refused_by_default(self):
rc, out, err = run(str(self.dup))
self.assertEqual(rc, 1, out + err)
self.assertIn("duplicate object member name", out)
self.assertNotIn("SHA256", out)
def test_allow_duplicates_accepts_them(self):
rc, out, err = run("--allow-duplicates", str(self.dup))
self.assertEqual(rc, 0, out + err)
self.assertIn(f"SHA256={self.digest(self.dup)}", out)
def test_syntax_error_is_rc_1(self):
rc, out, err = run(str(self.broken))
self.assertEqual(rc, 1, out + err)
self.assertIn("INVALID", out)
def test_bom_is_tolerated_and_the_choice_is_documented(self):
rc, out, err = run(str(self.bom))
self.assertEqual(rc, 0, out + err)
self.assertIn(f"SHA256={self.digest(self.bom)}", out)
self.assertIn("byte order mark", TOOL.read_text(encoding="utf-8").lower())
def test_undecodable_bytes_are_invalid_rc_1(self):
bad = self.write("bad.bin", b'\xff\xfe\x00\x01')
rc, out, err = run(str(bad))
self.assertEqual(rc, 1, out + err)
self.assertIn("not decodable JSON text", out)
class Ungradable(Base):
def test_missing_input_is_rc_2(self):
rc, out, err = run(str(self.dir / "nope.json"))
self.assertEqual(rc, 2, out + err)
self.assertIn("UNGRADABLE", out)
self.assertNotIn("VALID", out)
self.assertNotIn("SHA256", out)
def test_directory_input_is_rc_2(self):
rc, out, err = run(str(self.dir))
self.assertEqual(rc, 2, out + err)
self.assertIn("UNGRADABLE", out)
def test_non_directory_in_path_is_rc_2(self):
rc, out, err = run(str(self.ok / "child.json"))
self.assertEqual(rc, 2, out + err)
self.assertIn("UNGRADABLE", out)
def test_ungradable_suppresses_every_verdict_and_digest(self):
rc, out, err = run(str(self.ok), str(self.dir / "nope.json"))
self.assertEqual(rc, 2, out + err)
self.assertNotIn("VALID", out)
self.assertNotIn("INVALID", out)
self.assertNotIn("SHA256", out)
self.assertIn("no verdict and no digest printed", out)
class SingleRead(Base):
def test_one_read_serves_both_the_parse_and_the_digest(self):
"""Serve payload A first and B after; the digest must be A's, read once."""
target = self.dir / "race.json"
target.write_bytes(b'{"a": 1}\n')
a = b'{"a": 1}\n'
b = b'{"a": 999}\n'
counts = {"read_bytes": 0, "open": 0}
spec = importlib.util.spec_from_file_location("json_sha256_validator", TOOL)
if spec is None or spec.loader is None:
self.fail(f"cannot load the module under test at {TOOL}")
loader = spec.loader
mod = importlib.util.module_from_spec(spec)
loader.exec_module(mod)
real_read_bytes = Path.read_bytes
import builtins
real_builtin_open = builtins.open
def fake_read_bytes(self):
counts["read_bytes"] += 1
return a if counts["read_bytes"] == 1 else b
def fake_open(path, mode="r", *args, **kwargs):
if str(path) == str(target):
counts["open"] += 1
if "b" in mode:
return io.BytesIO(b)
return io.StringIO(b.decode())
return real_builtin_open(path, mode, *args, **kwargs)
Path.read_bytes = fake_read_bytes
builtins.open = fake_open
try:
record = mod.grade(str(target))
finally:
Path.read_bytes = real_read_bytes
builtins.open = real_builtin_open
reads = counts["read_bytes"] + counts["open"]
self.assertEqual(record["verdict"], "valid", record)
self.assertEqual(reads, 1, f"subject read {reads} times: {counts}")
self.assertEqual(record["sha256"], hashlib.sha256(a).hexdigest())
class Results(Base):
def test_results_file_carries_the_invocation_and_each_blob_sha(self):
out_path = self.dir / "run.json"
rc, out, err = run("--results", str(out_path), str(self.ok), str(self.dup))
self.assertIn(rc, (0, 1), out + err)
payload = json.loads(out_path.read_text(encoding="utf-8"))
self.assertEqual(payload["tool"], TOOL.name)
self.assertIn(str(TOOL.resolve()), payload["invocation"])
self.assertIn(str(self.ok), payload["argv"])
by_path = {item["path"]: item for item in payload["inputs"]}
self.assertEqual(by_path[str(self.ok)]["sha256"], self.digest(self.ok))
self.assertEqual(by_path[str(self.ok)]["verdict"], "valid")
self.assertEqual(by_path[str(self.dup)]["verdict"], "invalid")
if __name__ == "__main__":
unittest.main(verbosity=2)