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