beam: Q94 bullets 2/3/4 -- three cells that each kill a named mutant

Rebuild test_no_path_compression_find_stable on 4 nodes so the node it asks
about (3) sits two levels down under union-by-size; the old cell built
union(0,1);union(1,2) -> parent [0,0,0,0], so find(2) rewrote nothing and a
path-compressing find passed the cell that names the mechanism.

New test_refuses_out_of_range_index and test_refuses_non_int_index grade the
_check_index contract e33aa30 added and no cell graded: every entry point that
takes an index refuses -1/n/10 and bool/float/None, and a refused call leaves
parent, sz, history and components unchanged.

Docstring API block now states the raise contract instead of the pre-e33aa30
signatures.

Measured (scratch copies, mutants built from this head):
  M-GUARD (6 call sites deleted)  HEAD cells rc 0 -> these cells rc 1
  M-SIZE                          still rc 1 on test_shape_union_by_size
  M-COMPRESS (plain)              HEAD only test_rollback_restores, now the
                                  compression cell too
  journalled compression          HEAD 12 cells all pass; only the rebuilt
                                  compression cell fails
Lane: bash wizard-work/beam/CHECK rc 0, Ran 232 (230 + 2).
This commit is contained in:
ledger committed 2026-10-27 16:58:24 +00:00
1 parent e0ff562bb2
commit ca3e13d328
2 files changed
+60 -10

No files matched your search

+9 -6
View File
@@ -6,13 +6,16 @@ cheaply reversed. This implementation uses union by size and records enough
state to undo each union in O(1). state to undo each union in O(1).
API: API:
DSU(n) DSU(n) n < 0 -> ValueError
find(x) -> root (no path compression) find(x) -> root no path compression; TypeError if x is not an int or
union(a, b) -> bool (True if merged) is a bool, ValueError if x is outside [0, n)
union(a, b) -> bool True if merged; both arguments are checked as above
and a refused call mutates nothing
snapshot() -> int (marker) snapshot() -> int (marker)
rollback(marker) -> None (revert to marker) rollback(marker) -> None revert to marker; TypeError if marker is not an int
size(x) -> component size or is a bool, ValueError if marker outside [0, len(history)]
connected(a, b) -> bool size(x) -> component size index checked as find
connected(a, b) -> bool both indexes checked as find
""" """
from typing import List, Tuple from typing import List, Tuple
+51 -4
View File
@@ -61,12 +61,17 @@ class TestDSURollback(unittest.TestCase):
d.rollback(-1) d.rollback(-1)
def test_no_path_compression_find_stable(self): def test_no_path_compression_find_stable(self):
# Q94 bullet 2: the node asked about must sit TWO levels down, else a
# compressing find rewrites nothing and the cell is vacuous.
# union-by-size: union(0,1)==(1<-0), union(2,3)==(3<-2), union(1,2)==(2<-0)
d = DSU(4) d = DSU(4)
d.union(0, 1); d.union(1, 2) d.union(0, 1); d.union(2, 3); d.union(1, 2)
self.assertEqual(d.parent, [0, 0, 0, 2])
self.assertEqual(d.parent[3], 2) # precondition: depth 2
before = list(d.parent) before = list(d.parent)
r = d.find(2) self.assertEqual(d.find(3), 0) # root via parent[3] -> 2 -> 0
self.assertEqual(r, d.find(0)) self.assertEqual(d.parent, before) # a compressing find writes parent[3]=0
self.assertEqual(d.parent[2], before[2]) self.assertEqual(d.size(3), 4)
def test_size_after_rollback(self): def test_size_after_rollback(self):
d = DSU(3) d = DSU(3)
@@ -112,6 +117,48 @@ class TestDSURollback(unittest.TestCase):
with self.assertRaises((TypeError, ValueError)): with self.assertRaises((TypeError, ValueError)):
d.rollback(bad) d.rollback(bad)
def _fingerprint(self, d):
return (list(d.parent), list(d.sz), list(d.history), d.components())
def test_refuses_out_of_range_index(self):
# Q94 bullet 3: every entry point that takes an index refuses a range
# violation, and a refused call mutates nothing.
d = DSU(3)
d.union(0, 1)
before = self._fingerprint(d)
for bad in (-1, 3, 10):
for name in ("find", "size"):
with self.assertRaises(ValueError, msg="%s(%r)" % (name, bad)):
getattr(d, name)(bad)
self.assertEqual(self._fingerprint(d), before)
for args in ((bad, 0), (0, bad)):
with self.assertRaises(ValueError, msg="union%r" % (args,)):
d.union(*args)
self.assertEqual(self._fingerprint(d), before)
with self.assertRaises(ValueError, msg="connected%r" % (args,)):
d.connected(*args)
self.assertEqual(self._fingerprint(d), before)
def test_refuses_non_int_index(self):
# Q94 bullet 4: a bool is an int subclass in Python; True must not alias
# index 1 and False must not merge index 0.
d = DSU(3)
before = self._fingerprint(d)
for bad in (True, False):
with self.assertRaises(TypeError, msg="find(%r)" % (bad,)):
d.find(bad)
with self.assertRaises(TypeError, msg="size(%r)" % (bad,)):
d.size(bad)
with self.assertRaises(TypeError, msg="union(%r, 1)" % (bad,)):
d.union(bad, 1)
with self.assertRaises(TypeError, msg="connected(%r, 0)" % (bad,)):
d.connected(bad, 0)
self.assertEqual(self._fingerprint(d), before)
with self.assertRaises(TypeError):
d.find(1.5)
with self.assertRaises(TypeError):
d.find(None)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()