volt: beam: Q82 -- test_json_canonical.py becomes 7 discoverable cells; lane gate Ran 191 -> 198
- 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
This commit is contained in:
1 parent
bf14a7ae14
commit
07351026d4
1 file changed
+61
-30
@@ -1,39 +1,70 @@
|
||||
import sys, os, json
|
||||
#!/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
|
||||
from json_canonical import canonical, canonical_bytes, CanonicalError # noqa: E402
|
||||
|
||||
def test_basic():
|
||||
assert canonical({"b":2,"a":1}) == '{"a":1,"b":2}'
|
||||
assert canonical([3,1,2]) == '[3,1,2]'
|
||||
assert canonical(True) == 'true'
|
||||
assert canonical(None) == 'null'
|
||||
assert canonical("x") == '"x"'
|
||||
|
||||
def test_nested():
|
||||
assert canonical({"z":{"y":1,"x":2},"a":[1,2]}) == '{"a":[1,2],"z":{"x":2,"y":1}}'
|
||||
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"')
|
||||
|
||||
def test_unicode():
|
||||
assert canonical({"é":1}) == '{"é":1}'
|
||||
# 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}}')
|
||||
|
||||
def test_reject_nan():
|
||||
try:
|
||||
canonical(float('nan'))
|
||||
raise AssertionError("expected error")
|
||||
except CanonicalError:
|
||||
pass
|
||||
# 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({"é": "ü"}))
|
||||
|
||||
def test_reject_nonstr_key():
|
||||
try:
|
||||
canonical({1:2})
|
||||
raise AssertionError("expected error")
|
||||
except CanonicalError:
|
||||
pass
|
||||
# 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]})
|
||||
|
||||
def test_bytes():
|
||||
assert canonical_bytes({"a":1}) == b'{"a":1}'
|
||||
# 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)
|
||||
|
||||
def test_roundtrip():
|
||||
o = {"a":[1,2,3],"b":{"c":True}}
|
||||
assert json.loads(canonical(o)) == o
|
||||
# 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"))
|
||||
|
||||
print("ALL PASS")
|
||||
# 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()
|
||||
Reference in new issue
Block a user