#!/usr/bin/env python3 """Mutation harness for deporder: does the test suite actually have teeth? Each mutation is a literal source edit to deporder.py, applied in an isolated temp directory holding a copy of deporder.py + test_deporder.py. A mutation is "caught" when the suite exits non-zero on the mutant. Exit code is 0 only when every mutation labelled expect="caught" is caught and every mutation labelled expect="equivalent" survives (a documented redundant guard, not a hole in the tests). python3 mutate_teeth.py """ from __future__ import annotations import os import shutil import subprocess import sys import tempfile HERE = os.path.dirname(os.path.abspath(__file__)) SRC = os.path.join(HERE, "deporder.py") TESTS = os.path.join(HERE, "test_deporder.py") # (label, old, new, expectation, why) MUTATIONS = [ ("waves: drop the +1 level bump", "level[n] = 1 + max(level[d] for d in deps[n])", "level[n] = max(level[d] for d in deps[n])", "caught", "every task collapses into wave 0"), ("order: pop the heap tail instead of the min", "n = heapq.heappop(ready)", "n = ready.pop()", "caught", "lexicographic tie-break lost in Kahn"), ("cycles: disable the residual-node check", "if len(order) != len(deps):", "if False:", "caught", "cycle silently yields a partial order"), ("cycle path: report a single node", "return path[path.index(nxt):]", "return [nxt]", "caught", "reported 'cycle' is not a cycle"), ("unknown deps: stop validating", "if unknown:", "if False:", "caught", "ghost dependency accepted"), ("spec: allow est == 0", "if not (est > 0.0) or est == INF:", "if not (est >= 0.0) or est == INF:", "caught", "zero-duration task accepted"), ("critical path: break the predecessor tie-break", "if best_d is None or dist[d] > best or (dist[d] == best and d < best_d):", "if best_d is None or dist[d] >= best:", "caught", "path no longer lexicographically canonical"), ("schedule: release completions strictly before now", "while running and running[0][0] <= now:", "while running and running[0][0] < now:", "caught", "worker never freed / clock stalls"), ("schedule: double every task duration", "end = now + est[name]", "end = now + est[name] * 2", "caught", "makespan and critical-path bound violated"), ("schedule: pin the clock to itself (kill the stall guard)", "nxt = running[0][0] if running else None", "nxt = now", "caught", "guard fires: stalled scheduler must not spin"), ("graph: drop the redundant set() in build_graph", 'deps = {n: tuple(sorted(set(tasks[n]["deps"]))) for n in names}', 'deps = {n: tuple(sorted(tasks[n]["deps"])) for n in names}', "equivalent", "parse_spec already deduplicates; guard is redundant"), ] def run_mutant(label, old, new, timeout=300): tmp = tempfile.mkdtemp(prefix="deporder_mut_") try: src = open(SRC, "r", encoding="utf-8").read() hits = src.count(old) if hits == 0: return None, "ANCHOR MISSING" mutated = src.replace(old, new) with open(os.path.join(tmp, "deporder.py"), "w", encoding="utf-8") as fh: fh.write(mutated) shutil.copy(TESTS, os.path.join(tmp, "test_deporder.py")) try: proc = subprocess.run([sys.executable, "test_deporder.py"], cwd=tmp, capture_output=True, text=True, timeout=timeout) except subprocess.TimeoutExpired: return True, "timeout/hang -> caught" if proc.returncode == 0: return False, "suite PASSED on mutant (not caught)" tail = [ln for ln in proc.stderr.strip().splitlines() if ln.strip()] detail = tail[-1] if tail else "nonzero exit" return True, "exit %d | %s" % (proc.returncode, detail[:90]) finally: shutil.rmtree(tmp, ignore_errors=True) def main(): print("deporder mutation harness: %d mutations" % len(MUTATIONS)) print("-" * 100) bad = 0 caught = 0 for label, old, new, expect, why in MUTATIONS: got, detail = run_mutant(label, old, new) if got is None: bad += 1 status = "BROKEN (%s)" % detail else: ok = (got and expect == "caught") or (not got and expect == "equivalent") bad += 0 if ok else 1 caught += 1 if got else 0 status = ("CAUGHT " if got else "SURVIVED ") + detail if not ok: status += " <-- UNEXPECTED (expect=%s)" % expect print("%-46s %s" % (label, status)) print("%-46s why: %s" % ("", why)) print("-" * 100) print("caught %d/%d mutants that must die; %d problem(s)" % (caught, len(MUTATIONS), bad)) return 1 if bad else 0 if __name__ == "__main__": sys.exit(main())