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:
1 parent
e0ff562bb2
commit
ca3e13d328
2 files changed
+60
-10
No files matched your search
@@ -6,13 +6,16 @@ cheaply reversed. This implementation uses union by size and records enough
|
||||
state to undo each union in O(1).
|
||||
|
||||
API:
|
||||
DSU(n)
|
||||
find(x) -> root (no path compression)
|
||||
union(a, b) -> bool (True if merged)
|
||||
DSU(n) n < 0 -> ValueError
|
||||
find(x) -> root no path compression; TypeError if x is not an int or
|
||||
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)
|
||||
rollback(marker) -> None (revert to marker)
|
||||
size(x) -> component size
|
||||
connected(a, b) -> bool
|
||||
rollback(marker) -> None revert to marker; TypeError if marker is not an int
|
||||
or is a bool, ValueError if marker outside [0, len(history)]
|
||||
size(x) -> component size index checked as find
|
||||
connected(a, b) -> bool both indexes checked as find
|
||||
"""
|
||||
from typing import List, Tuple
|
||||
|
||||
|
||||
@@ -61,12 +61,17 @@ class TestDSURollback(unittest.TestCase):
|
||||
d.rollback(-1)
|
||||
|
||||
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.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)
|
||||
r = d.find(2)
|
||||
self.assertEqual(r, d.find(0))
|
||||
self.assertEqual(d.parent[2], before[2])
|
||||
self.assertEqual(d.find(3), 0) # root via parent[3] -> 2 -> 0
|
||||
self.assertEqual(d.parent, before) # a compressing find writes parent[3]=0
|
||||
self.assertEqual(d.size(3), 4)
|
||||
|
||||
def test_size_after_rollback(self):
|
||||
d = DSU(3)
|
||||
@@ -112,6 +117,48 @@ class TestDSURollback(unittest.TestCase):
|
||||
with self.assertRaises((TypeError, ValueError)):
|
||||
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__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user