package unit; import support.ClosureAlgorithm; import support.ClosureAlgorithm.Result; import java.util.Set; /** * Unit tests for DEFECT 0002b: InferenceGraph.Node.closure() uncached DFS. * * Proves: * 1. Both implementations produce identical closure sets (correctness). * 2. Defective version visits exactly K*V nodes for K calls on a V-node chain. * 3. Fixed version visits exactly V+(K-1) nodes for the same K calls. * 4. Doubling K doubles defective work; fixed is nearly constant. * * No build tool required. Compile and run: * * cd tests * java -m jdk.compiler/com.sun.tools.javac.Main -cp . \ * support/ClosureAlgorithm.java unit/ClosureComplexityTest.java * java -cp . unit.ClosureComplexityTest */ public class ClosureComplexityTest { private static int passed = 0; private static int failed = 0; public static void main(String[] args) { System.out.println("=== ClosureComplexityTest (DEFECT 0002b) ===\n"); System.out.println("-- Correctness: both versions return same closure set --"); testCorrectnessSmall(); testCorrectnessMedium(); System.out.println("\n-- Complexity: defective visits K*V nodes --"); testDefectiveExactCounts(); System.out.println("\n-- Complexity: fixed visits V+(K-1) nodes --"); testFixedExactCounts(); System.out.println("\n-- Complexity: growth ratio proves linear vs constant in K --"); testGrowthRatioInK(); System.out.printf("\n%d passed, %d failed%n", passed, failed); if (failed > 0) System.exit(1); } // ─── Correctness ───────────────────────────────────────────────────────── static void testCorrectnessSmall() { int v = 5, k = 3; ClosureAlgorithm.Node startDef = ClosureAlgorithm.buildLinearChain(v); ClosureAlgorithm.Node startFix = ClosureAlgorithm.buildLinearChain(v); Result def = ClosureAlgorithm.closureDefective(startDef, k); Result fix = ClosureAlgorithm.closureFixed(startFix, k); assertEqual("small: defective closure size", v, def.closure.size()); assertEqual("small: fixed closure size", v, fix.closure.size()); } static void testCorrectnessMedium() { int v = 20, k = 5; ClosureAlgorithm.Node startDef = ClosureAlgorithm.buildLinearChain(v); ClosureAlgorithm.Node startFix = ClosureAlgorithm.buildLinearChain(v); Result def = ClosureAlgorithm.closureDefective(startDef, k); Result fix = ClosureAlgorithm.closureFixed(startFix, k); assertEqual("medium: defective closure size", v, def.closure.size()); assertEqual("medium: fixed closure size", v, fix.closure.size()); } // ─── Exact node-visit counts ────────────────────────────────────────────── /** * PROVES DEFECT: K calls on a V-node chain visit exactly K*V nodes total. * Each call reruns the full DFS with no memory of previous calls. */ static void testDefectiveExactCounts() { System.out.println("[defective] closureDefective(chain(V), K) — expected K*V:"); int[][] cases = {{10, 5}, {20, 5}, {10, 10}, {20, 10}, {50, 20}}; for (int[] c : cases) { int v = c[0], k = c[1]; ClosureAlgorithm.Node start = ClosureAlgorithm.buildLinearChain(v); Result r = ClosureAlgorithm.closureDefective(start, k); long expected = (long) k * v; System.out.printf(" V=%-4d K=%-4d actual=%-8d expected=%-8d %s%n", v, k, r.nodeVisits, expected, r.nodeVisits == expected ? "PASS" : "FAIL expected=" + expected); assertEqual("defective V=" + v + " K=" + k, expected, r.nodeVisits); } } /** * PROVES FIX: K calls on a V-node chain visit exactly V+(K-1) nodes total. * First call runs the DFS (V visits), each of the K-1 subsequent calls * finds the cached result (1 visit each). */ static void testFixedExactCounts() { System.out.println("[fixed] closureFixed(chain(V), K) — expected V+(K-1):"); int[][] cases = {{10, 5}, {20, 5}, {10, 10}, {20, 10}, {50, 20}}; for (int[] c : cases) { int v = c[0], k = c[1]; ClosureAlgorithm.Node start = ClosureAlgorithm.buildLinearChain(v); Result r = ClosureAlgorithm.closureFixed(start, k); long expected = v + (k - 1); System.out.printf(" V=%-4d K=%-4d actual=%-8d expected=%-8d %s%n", v, k, r.nodeVisits, expected, r.nodeVisits == expected ? "PASS" : "FAIL expected=" + expected); assertEqual("fixed V=" + v + " K=" + k, expected, r.nodeVisits); } } // ─── Growth ratio (in K dimension) ──────────────────────────────────────── /** * PROVES LINEAR GROWTH OF DEFECT IN K: * Doubling K → defective visits double (≈2x), fixed barely changes. * * Math (V=100 fixed): * defective(K) = K*V → doubles when K doubles * defective(2K) = 2K*V → ratio = 2.0 (exact) * * fixed(K) = V + (K-1) * fixed(2K) = V + (2K-1) * ratio = (V+2K-1)/(V+K-1) → 1 as V≫K (nearly constant) */ static void testGrowthRatioInK() { int v = 100; int[][] kPairs = {{5, 10}, {10, 20}, {20, 40}}; for (int[] pair : kPairs) { int k1 = pair[0], k2 = pair[1]; ClosureAlgorithm.Node startDef1 = ClosureAlgorithm.buildLinearChain(v); ClosureAlgorithm.Node startDef2 = ClosureAlgorithm.buildLinearChain(v); ClosureAlgorithm.Node startFix1 = ClosureAlgorithm.buildLinearChain(v); ClosureAlgorithm.Node startFix2 = ClosureAlgorithm.buildLinearChain(v); long def1 = ClosureAlgorithm.closureDefective(startDef1, k1).nodeVisits; long def2 = ClosureAlgorithm.closureDefective(startDef2, k2).nodeVisits; long fix1 = ClosureAlgorithm.closureFixed(startFix1, k1).nodeVisits; long fix2 = ClosureAlgorithm.closureFixed(startFix2, k2).nodeVisits; double defRatio = (double) def2 / def1; double fixRatio = (double) fix2 / fix1; System.out.printf(" V=%d K %d→%d: defective ratio=%.2f (expect 2.0) fixed ratio=%.2f (expect <1.2)%n", v, k1, k2, defRatio, fixRatio); // Defective doubles exactly when K doubles (defective = K*V) assertTrue("defective K=" + k1 + "→" + k2 + " ratio = 2.0", defRatio == 2.0); // Fixed changes very little (V dominates K for V=100) assertTrue("fixed K=" + k1 + "→" + k2 + " ratio < 1.2", fixRatio < 1.2); } } // ─── Helpers ───────────────────────────────────────────────────────────── static void assertEqual(String name, long expected, long actual) { if (expected == actual) { System.out.printf(" PASS %s%n", name); passed++; } else { System.out.printf(" FAIL %s expected=%d actual=%d%n", name, expected, actual); failed++; } } static void assertTrue(String name, boolean condition) { if (condition) { System.out.printf(" PASS %s%n", name); passed++; } else { System.out.printf(" FAIL %s%n", name); failed++; } } }