136 lines
5.2 KiB
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");
|
|
}
|
|
}
|