- the file was seven `test_*` functions that nothing called, plus a module-level
`print("ALL PASS")`: running it printed a verdict with zero assertions, and
`python3 -m unittest discover -s wizard-work/beam -p test_json_canonical.py`
collected 0 (rc 5, NO TESTS RAN) while the import still printed ALL PASS.
- the seven cells are now methods of one `unittest.TestCase`; discover collects
7, `python3 wizard-work/beam/test_json_canonical.py` runs 7 OK, and
`bash wizard-work/beam/CHECK` moves Ran 191 -> Ran 198, rc 0, OK.
- the ALL PASS print is deleted; there is no module-level verdict on import.
- cells bite, measured against a mutant battery (harness
/root/.hermes/profiles/volt/cache/scratch/q82/battery.py, rows in
mutant_results.json, one tree per mutant under .../q82/mut/). Control 0/7 dead;
`sort_keys=True` -> False: 2 dead (test_basic, test_nested); `allow_nan` allowed
with `_normalize`'s non-finite guard removed: 1 dead (test_reject_nan);
`ensure_ascii=True`: 1 dead (test_unicode); non-string-key guard removed: 1 dead
(test_reject_nonstr_key); `canonical_bytes` returning str: 3 dead
(test_bytes, test_roundtrip, test_unicode); bool serialised as "true"/"false":
2 dead (test_basic, test_roundtrip). All 7 cells have a named killer.
- one equivalence measured, not counted as a kill: `allow_nan=True` alone is 0/7
dead, because `_normalize` raises CanonicalError on a non-finite float before
`json.dumps` is reached. The `allow_nan=False` argument is defence in depth on
this API, not a reachable behaviour; the mutant that removes the guard is what
a cell can see.
CHECK: bash wizard-work/beam/CHECK
71 lines
2.9 KiB
Python
71 lines
2.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Cells for json_canonical.py: sorted keys, compact separators, UTF-8 pass-through,
|
|
the rejection surface, and round-trip.
|
|
|
|
Seven cells, one per behaviour the module claims in its docstring. They are
|
|
`unittest.TestCase` methods, so `unittest discover` collects them and the lane's
|
|
CHECK counts them; there is no module-level verdict printed at import.
|
|
|
|
The module path resolves from this file's own directory, never a host path.
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
import unittest
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from json_canonical import canonical, canonical_bytes, CanonicalError # noqa: E402
|
|
|
|
|
|
class JsonCanonicalTest(unittest.TestCase):
|
|
# cell 1: sorted object keys, list order preserved, the three scalars
|
|
def test_basic(self):
|
|
self.assertEqual(canonical({"b": 2, "a": 1}), '{"a":1,"b":2}')
|
|
self.assertEqual(canonical([3, 1, 2]), "[3,1,2]")
|
|
self.assertEqual(canonical(True), "true")
|
|
self.assertEqual(canonical(False), "false")
|
|
self.assertEqual(canonical(None), "null")
|
|
self.assertEqual(canonical("x"), '"x"')
|
|
|
|
# cell 2: sorting reaches nested objects and nothing is reordered inside lists
|
|
def test_nested(self):
|
|
self.assertEqual(canonical({"z": {"y": 1, "x": 2}, "a": [1, 2]}),
|
|
'{"a":[1,2],"z":{"x":2,"y":1}}')
|
|
|
|
# cell 3: ensure_ascii=False -- non-ASCII survives as UTF-8, not \u escapes
|
|
def test_unicode(self):
|
|
self.assertEqual(canonical({"é": "ü"}), '{"é":"ü"}')
|
|
self.assertEqual(canonical_bytes({"é": "ü"}), '{"é":"ü"}'.encode("utf-8"))
|
|
self.assertNotIn("\\u", canonical({"é": "ü"}))
|
|
|
|
# cell 4: every non-finite float is refused, not serialised as NaN/Infinity
|
|
def test_reject_nan(self):
|
|
for bad in (float("nan"), float("inf"), float("-inf")):
|
|
with self.assertRaises(CanonicalError):
|
|
canonical(bad)
|
|
with self.assertRaises(CanonicalError):
|
|
canonical({"k": [bad]})
|
|
|
|
# cell 5: a non-string key is refused wherever it sits, including nested
|
|
def test_reject_nonstr_key(self):
|
|
for bad in ({1: 2}, {None: 2}, {1.0: 2}, {"a": {2: 3}}):
|
|
with self.assertRaises(CanonicalError):
|
|
canonical(bad)
|
|
|
|
# cell 6: canonical_bytes is bytes, encoded once, byte-identical to the str form
|
|
def test_bytes(self):
|
|
out = canonical_bytes({"a": 1})
|
|
self.assertIsInstance(out, bytes)
|
|
self.assertEqual(out, b'{"a":1}')
|
|
self.assertEqual(canonical_bytes([1.5, "é"]), canonical([1.5, "é"]).encode("utf-8"))
|
|
|
|
# cell 7: the output parses back to the input value
|
|
def test_roundtrip(self):
|
|
obj = {"a": [1, 2, 3], "b": {"c": True, "d": None}, "é": "ü", "f": 1.5}
|
|
self.assertEqual(json.loads(canonical(obj)), obj)
|
|
self.assertEqual(json.loads(canonical_bytes(obj).decode("utf-8")), obj)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|