java-topology/tests/unit/TarjanComplexityTest.java
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

271 lines
12 KiB
Java

package unit;
import support.TarjanAlgorithm;
import support.TarjanAlgorithm.Node;
import support.TarjanAlgorithm.Result;
import java.util.List;
/**
* Unit tests for the Tarjan SCC defect in GraphUtils.java:186.
*
* Proves:
* 1. Both implementations produce identical (correct) SCCs.
* 2. The defective version performs exactly V*(V+1)/2 - 1 comparisons
* on starWithBackEdges(V) — quadratic in V.
* 3. The fixed version performs exactly V-1 comparisons — linear in V.
* 4. Doubling V quadruples defective work; doubling V doubles fixed work.
*
* The key topology is starWithBackEdges(V):
* N0 → N1..N(v-1) (tree edges)
* N_k → N0 (back edges; k=1..v-1)
*
* N0 is never popped until all children finish. When N_k fires its back
* edge, the stack is [N_k, N_(k-1), ..., N1, N0]. The linear scan must
* walk k+1 elements to reach N0 at the bottom.
* Total comparisons: sum(k+1, k=1..v-1) = v*(v+1)/2 - 1.
*
* No build tool required. Compile and run:
*
* cd tests
* java -m jdk.compiler/com.sun.tools.javac.Main -cp . \
* support/TarjanAlgorithm.java unit/TarjanComplexityTest.java
* java -cp . unit.TarjanComplexityTest
*/
public class TarjanComplexityTest {
private static int passed = 0;
private static int failed = 0;
public static void main(String[] args) {
System.out.println("=== TarjanComplexityTest ===\n");
System.out.println("-- Correctness --");
testCorrectnessStarBackEdges();
testCorrectnessPathBackEdge();
testCorrectnessLinearPath();
testCorrectnessIndependentCycles();
testCorrectnessDiamondNoBack();
testCorrectnessDiamondWithBack();
System.out.println("\n-- Complexity: defective vs fixed comparison counts --");
testDefectiveExactCounts();
testFixedExactCounts();
System.out.println("\n-- Complexity: path+back-edge (O(V) vs O(1) per back-edge) --");
testPathBackEdgeComparisons();
System.out.println("\n-- Complexity: growth ratio proves quadratic vs linear --");
testGrowthRatioQuadratic();
System.out.printf("\n%d passed, %d failed%n", passed, failed);
if (failed > 0) System.exit(1);
}
// ─── Correctness ─────────────────────────────────────────────────────────
static void testCorrectnessStarBackEdges() {
// starWithBackEdges(5): N0→{N1..N4}, N1..N4→N0 → one SCC of size 5
Result def = TarjanAlgorithm.tarjanDefective(TarjanAlgorithm.starWithBackEdges(5));
Result fix = TarjanAlgorithm.tarjanFixed(TarjanAlgorithm.starWithBackEdges(5));
assertEqual("star-back-5: defective SCC count", 1, def.sccs.size());
assertEqual("star-back-5: fixed SCC count", 1, fix.sccs.size());
assertEqual("star-back-5: defective SCC size", 5, def.sccs.get(0).size());
assertEqual("star-back-5: fixed SCC size", 5, fix.sccs.get(0).size());
}
static void testCorrectnessPathBackEdge() {
// pathWithBackEdge(4): N0→N1→N2→N3→N0 → one SCC of size 4
Result def = TarjanAlgorithm.tarjanDefective(TarjanAlgorithm.pathWithBackEdge(4));
Result fix = TarjanAlgorithm.tarjanFixed(TarjanAlgorithm.pathWithBackEdge(4));
assertEqual("path-back-4: defective SCC count", 1, def.sccs.size());
assertEqual("path-back-4: fixed SCC count", 1, fix.sccs.size());
assertEqual("path-back-4: defective SCC size", 4, def.sccs.get(0).size());
assertEqual("path-back-4: fixed SCC size", 4, fix.sccs.get(0).size());
}
static void testCorrectnessLinearPath() {
// linearPath(4): N0→N1→N2→N3 → 4 singleton SCCs
Result def = TarjanAlgorithm.tarjanDefective(TarjanAlgorithm.linearPath(4));
Result fix = TarjanAlgorithm.tarjanFixed(TarjanAlgorithm.linearPath(4));
assertEqual("linear-4: defective SCC count", 4, def.sccs.size());
assertEqual("linear-4: fixed SCC count", 4, fix.sccs.size());
for (int i = 0; i < 4; i++) {
assertEqual("linear-4: defective SCC[" + i + "] size", 1, def.sccs.get(i).size());
}
}
static void testCorrectnessIndependentCycles() {
// 3 independent cycles of length 3 → 3 SCCs of size 3
Result def = TarjanAlgorithm.tarjanDefective(buildCycles(3, 3));
Result fix = TarjanAlgorithm.tarjanFixed(buildCycles(3, 3));
assertEqual("3x3-cycles: defective SCC count", 3, def.sccs.size());
assertEqual("3x3-cycles: fixed SCC count", 3, fix.sccs.size());
for (int i = 0; i < 3; i++) {
assertEqual("3x3-cycles: defective SCC[" + i + "] size", 3, def.sccs.get(i).size());
assertEqual("3x3-cycles: fixed SCC[" + i + "] size", 3, fix.sccs.get(i).size());
}
}
static void testCorrectnessDiamondNoBack() {
// N0→{N1,N2}→N3, no back edge → 4 singleton SCCs
Result def = TarjanAlgorithm.tarjanDefective(buildDiamond(false));
Result fix = TarjanAlgorithm.tarjanFixed(buildDiamond(false));
assertEqual("diamond-no-back: defective SCC count", 4, def.sccs.size());
assertEqual("diamond-no-back: fixed SCC count", 4, fix.sccs.size());
}
static void testCorrectnessDiamondWithBack() {
// N0→{N1,N2}→N3→N0 → 1 SCC of size 4
Result def = TarjanAlgorithm.tarjanDefective(buildDiamond(true));
Result fix = TarjanAlgorithm.tarjanFixed(buildDiamond(true));
assertEqual("diamond-back: defective SCC count", 1, def.sccs.size());
assertEqual("diamond-back: fixed SCC count", 1, fix.sccs.size());
assertEqual("diamond-back: defective SCC size", 4, def.sccs.get(0).size());
assertEqual("diamond-back: fixed SCC size", 4, fix.sccs.get(0).size());
}
// ─── Exact comparison counts ──────────────────────────────────────────────
/**
* PROVES DEFECT: starWithBackEdges(V) forces exactly V*(V+1)/2 - 1 comparisons.
*
* Derivation:
* When N_k fires its back edge to N0, stack = [N_k, N_(k-1), ..., N0].
* listContains scans k+1 elements before finding N0 at index k (0-based).
* Total: sum(k+1, k=1..V-1) = sum(2..V) = V*(V+1)/2 - 1.
*/
static void testDefectiveExactCounts() {
System.out.println("[defective] starWithBackEdges(V) — expected V*(V+1)/2 - 1:");
int[] sizes = {5, 10, 20, 50, 100};
for (int v : sizes) {
Result r = TarjanAlgorithm.tarjanDefective(TarjanAlgorithm.starWithBackEdges(v));
long expected = (long) v * (v + 1) / 2 - 1;
System.out.printf(" V=%-4d actual=%-8d expected=%-8d %s%n",
v, r.comparisons, expected,
r.comparisons == expected ? "PASS" : "FAIL expected=" + expected);
assertEqual("defective V=" + v, expected, r.comparisons);
}
}
/**
* PROVES FIX: starWithBackEdges(V) requires exactly V-1 comparisons with the fix.
* One comparison per back edge (one per non-tree edge that triggers the else branch).
*/
static void testFixedExactCounts() {
System.out.println("[fixed] starWithBackEdges(V) — expected V-1:");
int[] sizes = {5, 10, 20, 50, 100};
for (int v : sizes) {
Result r = TarjanAlgorithm.tarjanFixed(TarjanAlgorithm.starWithBackEdges(v));
long expected = v - 1;
System.out.printf(" V=%-4d actual=%-8d expected=%-8d %s%n",
v, r.comparisons, expected,
r.comparisons == expected ? "PASS" : "FAIL");
assertEqual("fixed V=" + v, expected, r.comparisons);
}
}
/**
* Path+back-edge: one back edge N(v-1)→N0.
* Stack when back edge fires: [N(v-1),...,N0], size=V.
* N0 is at the bottom → defective scans V elements, fixed scans 1.
*/
static void testPathBackEdgeComparisons() {
int[] sizes = {5, 10, 20};
for (int v : sizes) {
Result def = TarjanAlgorithm.tarjanDefective(TarjanAlgorithm.pathWithBackEdge(v));
Result fix = TarjanAlgorithm.tarjanFixed(TarjanAlgorithm.pathWithBackEdge(v));
System.out.printf(" V=%-4d defective=%-6d fixed=%-6d%n",
v, def.comparisons, fix.comparisons);
assertEqual("path-back V=" + v + " defective", (long) v, def.comparisons);
assertEqual("path-back V=" + v + " fixed", 1L, fix.comparisons);
}
}
// ─── Growth ratio ─────────────────────────────────────────────────────────
/**
* PROVES QUADRATIC GROWTH:
* When V doubles, defective comparisons quadruple (≈4x), fixed double (≈2x).
*
* Math:
* defective(V) = V*(V+1)/2 - 1 ≈ V²/2
* defective(2V) = 2V*(2V+1)/2-1 ≈ 2V² = 4 * V²/2
* ratio ≈ 4
*
* fixed(V) = V-1
* fixed(2V) = 2V-1
* ratio ≈ 2
*/
static void testGrowthRatioQuadratic() {
int[][] pairs = {{10, 20}, {20, 40}, {50, 100}, {100, 200}};
for (int[] pair : pairs) {
int v1 = pair[0], v2 = pair[1];
long def1 = TarjanAlgorithm.tarjanDefective(TarjanAlgorithm.starWithBackEdges(v1)).comparisons;
long def2 = TarjanAlgorithm.tarjanDefective(TarjanAlgorithm.starWithBackEdges(v2)).comparisons;
long fix1 = TarjanAlgorithm.tarjanFixed(TarjanAlgorithm.starWithBackEdges(v1)).comparisons;
long fix2 = TarjanAlgorithm.tarjanFixed(TarjanAlgorithm.starWithBackEdges(v2)).comparisons;
double defRatio = (double) def2 / def1;
double fixRatio = (double) fix2 / fix1;
System.out.printf(" V %d→%d: defective ratio=%.2f (expect ~4.0) fixed ratio=%.2f (expect ~2.0)%n",
v1, v2, defRatio, fixRatio);
assertTrue("defective V=" + v1 + "" + v2 + " ratio ≥ 3.8", defRatio >= 3.8);
assertTrue("defective V=" + v1 + "" + v2 + " ratio ≤ 4.2", defRatio <= 4.2);
assertTrue("fixed V=" + v1 + "" + v2 + " ratio ≥ 1.9", fixRatio >= 1.9);
// exact ratio = (2V-1)/(V-1) → approaches 2.0 as V→∞; ≤2.12 for V≥10
assertTrue("fixed V=" + v1 + "" + v2 + " ratio ≤ 2.15", fixRatio <= 2.15);
}
}
// ─── Graph builders ───────────────────────────────────────────────────────
static List<Node> buildCycles(int k, int len) {
List<Node> all = new java.util.ArrayList<>();
for (int c = 0; c < k; c++) {
List<Node> cycle = new java.util.ArrayList<>();
for (int i = 0; i < len; i++) cycle.add(new Node("C" + c + "_N" + i));
for (int i = 0; i < len - 1; i++) cycle.get(i).addDep(cycle.get(i + 1));
cycle.get(len - 1).addDep(cycle.get(0));
all.addAll(cycle);
}
return all;
}
static List<Node> buildDiamond(boolean backEdge) {
Node n0 = new Node("N0"), n1 = new Node("N1"),
n2 = new Node("N2"), n3 = new Node("N3");
n0.addDep(n1); n0.addDep(n2); n1.addDep(n3); n2.addDep(n3);
if (backEdge) n3.addDep(n0);
return java.util.List.of(n0, n1, n2, n3);
}
// ─── 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++;
}
}
}