java-topology/defects/dgraph/unit/DgraphTest.java

136 lines
5.2 KiB
Java

package unit;
import java.util.*;
/**
* Unit test for dgraph-0001: CWE-407 O(P) route.indexOf inside k-shortest-path neighbour loop.
*
* Slow path: ArrayList<Long> path, indexOf = O(P) linear scan.
* Fast path: HashMap<Long,Boolean> visited alongside path, lookup = O(1).
*
* Both produce identical cycle-detection results; the test confirms op counts diverge as N grows.
*/
public class DgraphTest {
// -------------------------------------------------------------------------
// Slow path: simulates route.indexOf — linear scan of path slice
// -------------------------------------------------------------------------
static long slowIndexOf(List<Long> path, long uid) {
for (long val : path) {
if (val == uid) return 0; // found
}
return -1;
}
/**
* Simulate k-shortest-paths neighbour expansion with O(P) cycle detection.
* Returns total indexOf scan operations performed.
*
* Graph: linear chain 0→1→2→...→(n-1) plus a back-edge n-1→0 to force cycle checks.
* Each dequeue: process n neighbours, call indexOf once per neighbour.
* Dequeue n items (one per node). Path grows by 1 per hop.
*/
static long slowOps(int n) {
long ops = 0;
// Simulate dequeuing n items with path lengths 1..n
for (int hop = 1; hop <= n; hop++) {
// path length at this hop = hop
int pathLen = hop;
// process n neighbours per dequeue
for (int nb = 0; nb < n; nb++) {
// indexOf scans entire path
ops += pathLen;
}
}
return ops;
}
// -------------------------------------------------------------------------
// Fast path: O(1) HashMap lookup for cycle detection
// -------------------------------------------------------------------------
static long fastOps(int n) {
long ops = 0;
// Simulate dequeuing n items with path lengths 1..n
for (int hop = 1; hop <= n; hop++) {
// n neighbours per dequeue, each is O(1) map lookup = 1 op
for (int nb = 0; nb < n; nb++) {
ops += 1;
}
}
return ops;
}
// -------------------------------------------------------------------------
// Correctness check: both strategies agree on cycle detection result
// -------------------------------------------------------------------------
static boolean slowContains(List<Long> path, long uid) {
return slowIndexOf(path, uid) != -1;
}
static boolean fastContains(Map<Long, Boolean> visited, long uid) {
return visited.containsKey(uid);
}
static void testCorrectness() {
List<Long> path = new ArrayList<>(Arrays.asList(10L, 20L, 30L, 40L));
Map<Long, Boolean> visited = new HashMap<>();
for (long v : path) visited.put(v, Boolean.TRUE);
// uid present in path
assert slowContains(path, 30L) == fastContains(visited, 30L)
: "FAIL: mismatch for uid in path";
// uid absent from path
assert slowContains(path, 99L) == fastContains(visited, 99L)
: "FAIL: mismatch for uid not in path";
System.out.println("1/2 PASS correctness: slow and fast agree on cycle detection");
}
// -------------------------------------------------------------------------
// Complexity check: slow grows O(n²), fast grows O(n)
// -------------------------------------------------------------------------
static void testComplexity() {
int n1 = 100;
int n2 = 1000;
long slowN1 = slowOps(n1);
long slowN2 = slowOps(n2);
long fastN1 = fastOps(n1);
long fastN2 = fastOps(n2);
// Slow should grow ~100x (10x nodes → 10x*10x = 100x ops)
double slowRatio = (double) slowN2 / slowN1;
// Fast should grow ~10x (linear in n²: n*n neighbours each 1 op)
// Actually fast is also n*n ops but each is 1, while slow is n*n*pathLen
// The ratio slowN2/fastN2 should be >> 1 at n2 (slow pays pathLen = n per op)
double overhead = (double) slowN2 / fastN2;
System.out.printf(" slow ops n=%d: %,d%n", n1, slowN1);
System.out.printf(" slow ops n=%d: %,d%n", n2, slowN2);
System.out.printf(" fast ops n=%d: %,d%n", n1, fastN1);
System.out.printf(" fast ops n=%d: %,d%n", n2, fastN2);
System.out.printf(" slow/fast overhead at n=%d: %.1fx%n", n2, overhead);
// slow is O(n³) here: n hops * n neighbours * n path length
// fast is O(n²): n hops * n neighbours * 1
// overhead should be ~n2 = 1000
assert slowRatio > 50.0
: "FAIL: slow ops did not grow super-linearly: ratio=" + slowRatio;
assert overhead > 100.0
: "FAIL: slow not significantly worse than fast: overhead=" + overhead;
System.out.printf("2/2 PASS complexity: slow/fast=%.0fx (expected ~%d)%n",
overhead, n2);
}
public static void main(String[] args) {
System.out.println("dgraph-0001: route.indexOf O(P) cycle check in k-shortest-paths");
testCorrectness();
testComplexity();
System.out.println("2/2 PASS");
}
}