Q68 CLOSED at 5a43291: gate rc 0 / Ran 136 / ALL PASS (242 checks) from a clone of that commit, against rc 1 with zero cells and a skipped toposort leg at 5a43291^. 17 cells, six mutants graded (4 flipped from SURVIVED to KILLED, 1 already killed by the old suite -- the item's split does not reproduce, 1 proven equivalent over 3000 graphs and retired by deletion), 3000-graph fuzz 0/0/0/0, six control cells. My own first cut named the host path in a docstring while explaining it was unused; git grep counted it, so the sentence is rewritten here.
147 lines
5.6 KiB
Python
147 lines
5.6 KiB
Python
"""Cells for `topo_levels`, collectable by the lane's `CHECK`.
|
|
|
|
`python3 -m unittest discover -p 'test_*.py'` collects this file only because
|
|
the cells are `unittest.TestCase` methods; the previous module-level functions
|
|
were invisible to the gate (Q68's third fault, the `retry_jitter` shape).
|
|
|
|
The module under test is resolved from this file's directory and the import is
|
|
verified to land on the sibling file: the author's host output tree is never
|
|
imported (Q62's host-pin class -- the path is deliberately not written here, so
|
|
a text census of host-path readers does not count this file).
|
|
The check is a `raise`, not an `assert`, so it keeps grading under `-O`.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
if str(HERE) not in sys.path:
|
|
sys.path.insert(0, str(HERE))
|
|
|
|
import topo_levels # noqa: E402
|
|
from topo_levels import CycleError, flatten # noqa: E402
|
|
|
|
EXPECTED_MODULE = HERE / "topo_levels.py"
|
|
_RESOLVED = Path(topo_levels.__file__).resolve()
|
|
if _RESOLVED != EXPECTED_MODULE:
|
|
raise ImportError(
|
|
f"topo_levels resolved to {str(_RESOLVED)!r}, expected {str(EXPECTED_MODULE)!r}"
|
|
)
|
|
|
|
|
|
class TestTopoLevels(unittest.TestCase):
|
|
# ---- the import itself is graded, not assumed -------------------------
|
|
def test_import_is_sibling(self):
|
|
self.assertEqual(Path(topo_levels.__file__).resolve(), EXPECTED_MODULE)
|
|
|
|
# ---- level shape ------------------------------------------------------
|
|
def test_basic(self):
|
|
levels = topo_levels.topo_levels(
|
|
["a", "b", "c", "d"], [("a", "c"), ("b", "c"), ("c", "d")]
|
|
)
|
|
self.assertEqual(levels, [["a", "b"], ["c"], ["d"]])
|
|
|
|
def test_disconnected(self):
|
|
self.assertEqual(topo_levels.topo_levels([1, 2, 3], []), [[1, 2, 3]])
|
|
|
|
def test_single_node_no_edges(self):
|
|
self.assertEqual(topo_levels.topo_levels(["x"], []), [["x"]])
|
|
|
|
def test_diamond(self):
|
|
levels = topo_levels.topo_levels(
|
|
["a", "b", "c", "d"], [("a", "b"), ("a", "c"), ("b", "d"), ("c", "d")]
|
|
)
|
|
self.assertEqual(levels, [["a"], ["b", "c"], ["d"]])
|
|
|
|
def test_determinism_under_input_permutation(self):
|
|
nodes = list("zyxwv")
|
|
edges = [("z", "w"), ("y", "w")]
|
|
self.assertEqual(
|
|
topo_levels.topo_levels(nodes, edges),
|
|
topo_levels.topo_levels(list(reversed(nodes)), edges),
|
|
)
|
|
|
|
def test_level_interior_is_sorted_by_key(self):
|
|
# adjacency arrival order ('c' before 'a') differs from sorted order;
|
|
# a level emitted in arrival order ([['p'], ['c', 'a']]) must fail.
|
|
self.assertEqual(
|
|
topo_levels.topo_levels(["p", "c", "a"], [("p", "c"), ("p", "a")]),
|
|
[["p"], ["a", "c"]],
|
|
)
|
|
|
|
def test_duplicate_nodes_deduped(self):
|
|
# without dedupe this returns [['a', 'a', 'b']].
|
|
self.assertEqual(
|
|
topo_levels.topo_levels(["a", "a", "b"], [("a", "b")]), [["a"], ["b"]]
|
|
)
|
|
|
|
# ---- one-shot iterables (edges is annotated Iterable) ------------------
|
|
def test_generator_edges_materialised(self):
|
|
edges = [("a", "c"), ("b", "c")]
|
|
self.assertEqual(
|
|
topo_levels.topo_levels(["a", "b", "c"], (e for e in edges)),
|
|
[["a", "b"], ["c"]],
|
|
)
|
|
|
|
def test_iter_edges_materialised(self):
|
|
edges = [("a", "c"), ("b", "c")]
|
|
self.assertEqual(
|
|
topo_levels.topo_levels(["a", "b", "c"], iter(edges)), [["a", "b"], ["c"]]
|
|
)
|
|
|
|
def test_map_edges_materialised(self):
|
|
edges = [("a", "c"), ("b", "c")]
|
|
self.assertEqual(
|
|
topo_levels.topo_levels(["a", "b", "c"], map(tuple, edges)),
|
|
[["a", "b"], ["c"]],
|
|
)
|
|
|
|
def test_generator_cycle_still_raises(self):
|
|
cycle = [("a", "b"), ("b", "c"), ("c", "a")]
|
|
with self.assertRaises(CycleError):
|
|
topo_levels.topo_levels(["a", "b", "c"], (e for e in cycle))
|
|
|
|
# ---- cycles -----------------------------------------------------------
|
|
def test_cycle_remaining_is_exact(self):
|
|
# 'd' is acyclic: `remaining = list(node_set)` would add it here.
|
|
with self.assertRaises(CycleError) as ctx:
|
|
topo_levels.topo_levels(
|
|
["a", "b", "c", "d"], [("a", "b"), ("b", "c"), ("c", "a")]
|
|
)
|
|
self.assertEqual(ctx.exception.remaining, ["a", "b", "c"])
|
|
|
|
# ---- unknown nodes: the message, not just the exception type -----------
|
|
def test_unknown_prerequisite_message(self):
|
|
with self.assertRaises(KeyError) as ctx:
|
|
topo_levels.topo_levels(["a"], [("z", "a")])
|
|
self.assertEqual(ctx.exception.args[0], "edge references unknown node: 'z'")
|
|
|
|
def test_unknown_dependent_message(self):
|
|
with self.assertRaises(KeyError) as ctx:
|
|
topo_levels.topo_levels(["a"], [("a", "z")])
|
|
self.assertEqual(ctx.exception.args[0], "edge references unknown node: 'z'")
|
|
|
|
# ---- flatten ----------------------------------------------------------
|
|
def test_flatten_roundtrip(self):
|
|
nodes = ["a", "b", "c", "d", "e"]
|
|
edges = [("a", "c"), ("b", "c"), ("c", "d"), ("c", "e")]
|
|
levels = topo_levels.topo_levels(nodes, edges)
|
|
flat = flatten(levels)
|
|
self.assertEqual(sorted(flat), sorted(nodes))
|
|
pos = {n: i for i, n in enumerate(flat)}
|
|
for u, v in edges:
|
|
self.assertLess(pos[u], pos[v])
|
|
|
|
def test_flatten_keeps_level_order(self):
|
|
# reversing a level inside flatten gives ['b', 'a', 'c'].
|
|
self.assertEqual(
|
|
flatten(topo_levels.topo_levels(["a", "b"], [])),
|
|
["a", "b"],
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(verbosity=2)
|