B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections. Squash of 94 local commits onto remote master.
310 lines
11 KiB
Java
310 lines
11 KiB
Java
package unit;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* BirdRoutingTest — unit tests for BIRD CWE-407 defects.
|
|
*
|
|
* BIRD-001 (HIGH): OSPF SPF candidate list insertion sort O(E*V) vs heap O((E+V) log V).
|
|
* BIRD-002 (MEDIUM): BGP community linear scan O(n) vs bsearch O(log n).
|
|
*
|
|
* No external dependencies. Run with: java -ea unit.BirdRoutingTest
|
|
*/
|
|
public class BirdRoutingTest {
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Instrumented comparison counter
|
|
// -------------------------------------------------------------------------
|
|
static long comparisons;
|
|
|
|
static void resetComparisons() { comparisons = 0; }
|
|
static long getComparisons() { return comparisons; }
|
|
|
|
// -------------------------------------------------------------------------
|
|
// BIRD-001 model: Dijkstra with instrumented candidate list
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Defective: sorted LinkedList insertion — O(n) per insert (mirrors WALK_LIST).
|
|
*/
|
|
static int[] dijkstraLinkedList(int[][] adj, int src) {
|
|
int V = adj.length;
|
|
int[] dist = new int[V];
|
|
Arrays.fill(dist, Integer.MAX_VALUE);
|
|
dist[src] = 0;
|
|
|
|
// Candidate list: sorted ascending by distance — insertion sort like BIRD
|
|
LinkedList<Integer> cand = new LinkedList<>();
|
|
cand.add(src);
|
|
|
|
while (!cand.isEmpty()) {
|
|
int u = cand.removeFirst();
|
|
for (int v = 0; v < V; v++) {
|
|
if (adj[u][v] == 0) continue;
|
|
int nd = dist[u] + adj[u][v];
|
|
if (nd < dist[v]) {
|
|
dist[v] = nd;
|
|
// Remove existing entry if present — mirrors rem_node
|
|
cand.remove(Integer.valueOf(v));
|
|
// Insertion sort: walk list to find position — O(n), CWE-407 defect
|
|
ListIterator<Integer> it = cand.listIterator();
|
|
boolean inserted = false;
|
|
while (it.hasNext()) {
|
|
comparisons++; // instrument
|
|
int cur = it.next();
|
|
if (dist[cur] > nd) {
|
|
it.previous();
|
|
it.add(v);
|
|
inserted = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!inserted) cand.addLast(v);
|
|
}
|
|
}
|
|
}
|
|
return dist;
|
|
}
|
|
|
|
/**
|
|
* Fixed: PriorityQueue min-heap — O(log n) per insert (mirrors HEAP_INSERT).
|
|
*/
|
|
static int[] dijkstraHeap(int[][] adj, int src) {
|
|
int V = adj.length;
|
|
int[] dist = new int[V];
|
|
Arrays.fill(dist, Integer.MAX_VALUE);
|
|
dist[src] = 0;
|
|
|
|
// min-heap keyed on distance — mirrors cand_push / cand_pop
|
|
PriorityQueue<int[]> heap = new PriorityQueue<>(Comparator.comparingInt(e -> e[1]));
|
|
heap.offer(new int[]{src, 0});
|
|
|
|
while (!heap.isEmpty()) {
|
|
int[] top = heap.poll();
|
|
int u = top[0], d = top[1];
|
|
if (d > dist[u]) continue; // stale entry
|
|
for (int v = 0; v < V; v++) {
|
|
if (adj[u][v] == 0) continue;
|
|
int nd = dist[u] + adj[u][v];
|
|
if (nd < dist[v]) {
|
|
dist[v] = nd;
|
|
comparisons++; // one heap comparison per insertion (amortised)
|
|
heap.offer(new int[]{v, nd});
|
|
}
|
|
}
|
|
}
|
|
return dist;
|
|
}
|
|
|
|
// Build a random connected sparse graph (adjacency matrix)
|
|
static int[][] buildGraph(int V, int E, Random rng) {
|
|
int[][] adj = new int[V][V];
|
|
// Guarantee connectivity: chain 0→1→2→…→V-1
|
|
for (int i = 0; i < V - 1; i++) {
|
|
int w = 1 + rng.nextInt(10);
|
|
adj[i][i + 1] = w;
|
|
adj[i + 1][i] = w;
|
|
}
|
|
// Add random extra edges
|
|
int added = V - 1;
|
|
while (added < E) {
|
|
int u = rng.nextInt(V);
|
|
int v = rng.nextInt(V);
|
|
if (u != v && adj[u][v] == 0) {
|
|
int w = 1 + rng.nextInt(10);
|
|
adj[u][v] = w;
|
|
adj[v][u] = w;
|
|
added++;
|
|
}
|
|
}
|
|
return adj;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// BIRD-002 model: community membership linear scan vs bsearch
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Defective: linear scan — O(n), mirrors int_set_contains before patch.
|
|
*/
|
|
static boolean communityContainsLinear(int[] communities, int val) {
|
|
for (int c : communities) {
|
|
comparisons++;
|
|
if (c == val) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Fixed: binary search — O(log n), mirrors bsearch after patch.
|
|
* Requires sorted input (enforced on creation by qsort in the C patch).
|
|
*/
|
|
static boolean communityContainsBsearch(int[] sorted, int val) {
|
|
int lo = 0, hi = sorted.length - 1;
|
|
while (lo <= hi) {
|
|
comparisons++;
|
|
int mid = (lo + hi) >>> 1;
|
|
if (sorted[mid] == val) return true;
|
|
if (sorted[mid] < val) lo = mid + 1;
|
|
else hi = mid - 1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Test methods
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Test 1: Defective Dijkstra produces correct shortest distances.
|
|
*/
|
|
static void testLinkedListDijkstraCorrectness() {
|
|
int[][] adj = {
|
|
{0, 4, 0, 0, 8},
|
|
{4, 0, 8, 0, 0},
|
|
{0, 8, 0, 7, 0},
|
|
{0, 0, 7, 0, 9},
|
|
{8, 0, 0, 9, 0},
|
|
};
|
|
resetComparisons();
|
|
int[] dist = dijkstraLinkedList(adj, 0);
|
|
assert dist[0] == 0 : "BIRD-001 defective: dist[0] wrong";
|
|
assert dist[1] == 4 : "BIRD-001 defective: dist[1] wrong";
|
|
assert dist[2] == 12 : "BIRD-001 defective: dist[2] wrong";
|
|
assert dist[3] == 17 : "BIRD-001 defective: dist[3] wrong";
|
|
assert dist[4] == 8 : "BIRD-001 defective: dist[4] wrong";
|
|
System.out.println("PASS test1_linkedlist_dijkstra_correctness");
|
|
}
|
|
|
|
/**
|
|
* Test 2: Fixed (heap) Dijkstra produces identical correct shortest distances.
|
|
*/
|
|
static void testHeapDijkstraCorrectness() {
|
|
int[][] adj = {
|
|
{0, 4, 0, 0, 8},
|
|
{4, 0, 8, 0, 0},
|
|
{0, 8, 0, 7, 0},
|
|
{0, 0, 7, 0, 9},
|
|
{8, 0, 0, 9, 0},
|
|
};
|
|
resetComparisons();
|
|
int[] dist = dijkstraHeap(adj, 0);
|
|
assert dist[0] == 0 : "BIRD-001 fixed: dist[0] wrong";
|
|
assert dist[1] == 4 : "BIRD-001 fixed: dist[1] wrong";
|
|
assert dist[2] == 12 : "BIRD-001 fixed: dist[2] wrong";
|
|
assert dist[3] == 17 : "BIRD-001 fixed: dist[3] wrong";
|
|
assert dist[4] == 8 : "BIRD-001 fixed: dist[4] wrong";
|
|
System.out.println("PASS test2_heap_dijkstra_correctness");
|
|
}
|
|
|
|
/**
|
|
* Test 3: At V=200 / E=600, heap comparison count < linked-list comparison count
|
|
* by at least 5x. Models O(E*V) vs O((E+V) log V).
|
|
*/
|
|
static void testDijkstraComplexityRatio() {
|
|
final int V = 200, E = 600;
|
|
Random rng = new Random(42L);
|
|
int[][] adj = buildGraph(V, E, rng);
|
|
|
|
resetComparisons();
|
|
dijkstraLinkedList(adj, 0);
|
|
long listComps = getComparisons();
|
|
|
|
resetComparisons();
|
|
dijkstraHeap(adj, 0);
|
|
long heapComps = getComparisons();
|
|
|
|
double ratio = (double) listComps / heapComps;
|
|
System.out.printf(
|
|
"BIRD-001 V=%d E=%d: list_comparisons=%d heap_comparisons=%d ratio=%.1fx%n",
|
|
V, E, listComps, heapComps, ratio);
|
|
|
|
assert ratio > 5.0 : String.format(
|
|
"BIRD-001 ratio %.1fx < 5x threshold — heap speedup not demonstrated", ratio);
|
|
System.out.println("PASS test3_dijkstra_complexity_ratio");
|
|
}
|
|
|
|
/**
|
|
* Test 4: Community linear scan and bsearch agree on membership for random queries.
|
|
*/
|
|
static void testCommunityContainsCorrectness() {
|
|
int C = 100;
|
|
int[] communities = new int[C];
|
|
Random rng = new Random(7L);
|
|
for (int i = 0; i < C; i++) communities[i] = rng.nextInt(65536);
|
|
int[] sorted = communities.clone();
|
|
Arrays.sort(sorted);
|
|
|
|
// Test membership for 50 known-present and 50 random values
|
|
for (int i = 0; i < 50; i++) {
|
|
int val = communities[rng.nextInt(C)]; // definitely present
|
|
boolean lin = communityContainsLinear(communities, val);
|
|
boolean bin = communityContainsBsearch(sorted, val);
|
|
assert lin == bin : "BIRD-002 mismatch on present value " + val;
|
|
}
|
|
for (int i = 0; i < 50; i++) {
|
|
int val = 65536 + rng.nextInt(65536); // out of range — absent
|
|
boolean lin = communityContainsLinear(communities, val);
|
|
boolean bin = communityContainsBsearch(sorted, val);
|
|
assert lin == bin : "BIRD-002 mismatch on absent value " + val;
|
|
}
|
|
System.out.println("PASS test4_community_contains_correctness");
|
|
}
|
|
|
|
/**
|
|
* Test 5: At C=100 communities, 1000 lookups — bsearch uses >5x fewer comparisons.
|
|
*/
|
|
static void testCommunityComplexityRatio() {
|
|
final int C = 100, LOOKUPS = 1000;
|
|
Random rng = new Random(13L);
|
|
int[] communities = new int[C];
|
|
for (int i = 0; i < C; i++) communities[i] = i * 3; // deterministic, no duplicates
|
|
int[] sorted = communities.clone();
|
|
Arrays.sort(sorted);
|
|
|
|
resetComparisons();
|
|
for (int i = 0; i < LOOKUPS; i++) {
|
|
int val = rng.nextInt(C * 4); // mix of hits and misses
|
|
communityContainsLinear(communities, val);
|
|
}
|
|
long linearComps = getComparisons();
|
|
|
|
resetComparisons();
|
|
for (int i = 0; i < LOOKUPS; i++) {
|
|
rng = new Random(13L); // same seed — identical query sequence
|
|
int val = rng.nextInt(C * 4);
|
|
communityContainsBsearch(sorted, val);
|
|
}
|
|
|
|
// Re-run with same RNG sequence for a fair comparison
|
|
rng = new Random(13L);
|
|
resetComparisons();
|
|
for (int i = 0; i < LOOKUPS; i++) {
|
|
int val = rng.nextInt(C * 4);
|
|
communityContainsBsearch(sorted, val);
|
|
}
|
|
long bsearchComps = getComparisons();
|
|
|
|
double ratio = (double) linearComps / bsearchComps;
|
|
System.out.printf(
|
|
"BIRD-002 C=%d lookups=%d: linear_comparisons=%d bsearch_comparisons=%d ratio=%.1fx%n",
|
|
C, LOOKUPS, linearComps, bsearchComps, ratio);
|
|
|
|
assert ratio > 5.0 : String.format(
|
|
"BIRD-002 ratio %.1fx < 5x threshold — bsearch speedup not demonstrated", ratio);
|
|
System.out.println("PASS test5_community_complexity_ratio");
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Entry point
|
|
// -------------------------------------------------------------------------
|
|
public static void main(String[] args) {
|
|
System.out.println("=== BirdRoutingTest ===");
|
|
testLinkedListDijkstraCorrectness();
|
|
testHeapDijkstraCorrectness();
|
|
testDijkstraComplexityRatio();
|
|
testCommunityContainsCorrectness();
|
|
testCommunityComplexityRatio();
|
|
System.out.println("=== ALL TESTS PASSED ===");
|
|
}
|
|
}
|