java-topology/defects/bird/unit/BirdRoutingTest.java
russell@unturf.com db29a08762 undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections.
Squash of 94 local commits onto remote master.
2026-03-26 19:48:18 -04:00

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 ===");
}
}