package unit; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * CWE-407 unit test: pulsar-0007 * * Models ModularLoadManagerImpl.reapDeadBrokerPreallocations(List aliveBrokers). * * DEFECT: The method takes List aliveBrokers from listLocks(). * For each broker in loadData.getBrokerData().keySet(), * it calls aliveBrokers.contains(broker) — O(B) per iteration. * Total: O(B²) triggered on every broker metadata notification. * * FIX: Convert aliveBrokers to HashSet at method entry. * contains() becomes O(1); total: O(B). * * Asserts: slowOps > fastOps * 10 at B=500 brokers. */ public class Pulsar0007ModularLoadMgrTest { /** * Simulates defective reapDeadBrokerPreallocations. * aliveBrokers is a List — contains() is O(B). */ static long slow(int numBrokers, int numDead) { // numBrokers total, numDead are dead (not in alive list) List aliveBrokers = new ArrayList<>(); Set allBrokerSet = new HashSet<>(); for (int i = numDead; i < numBrokers; i++) { String broker = "broker-" + i + ":8080"; aliveBrokers.add(broker); } // All brokers in loadData (both alive and dead) List allBrokers = new ArrayList<>(); for (int i = 0; i < numBrokers; i++) { allBrokers.add("broker-" + i + ":8080"); } long ops = 0; for (String broker : allBrokers) { // O(alive.size()) scan — the defect for (String alive : aliveBrokers) { ops++; if (alive.equals(broker)) break; // if not found, we scan the entire list } // In the real code, also checks contains() returning false for dead brokers // For dead brokers, the full list is scanned } return ops; } /** * Simulates the patched version. * Converts aliveBrokers List to HashSet at method entry. */ static long fast(int numBrokers, int numDead) { List aliveBrokersList = new ArrayList<>(); for (int i = numDead; i < numBrokers; i++) { aliveBrokersList.add("broker-" + i + ":8080"); } Set aliveBrokersSet = new HashSet<>(aliveBrokersList); // O(B) once List allBrokers = new ArrayList<>(); for (int i = 0; i < numBrokers; i++) { allBrokers.add("broker-" + i + ":8080"); } long ops = 0; for (String broker : allBrokers) { ops++; // O(1) hash probe aliveBrokersSet.contains(broker); } return ops; } /** * Count accurate dead-broker detections (correctness check). */ static int countDead(List all, List alive) { int count = 0; for (String broker : all) { if (!alive.contains(broker)) count++; } return count; } static int countDeadFast(List all, List alive) { Set aliveSet = new HashSet<>(alive); int count = 0; for (String broker : all) { if (!aliveSet.contains(broker)) count++; } return count; } public static void main(String[] args) { int passed = 0; int total = 0; // Test 1: 200 brokers, 20 dead { total++; int B = 200, D = 20; long sOps = slow(B, D); long fOps = fast(B, D); // slow: for each of B brokers, scans up to (B-D) alive brokers = ~36000 // fast: B ops = 200 boolean ok = sOps > fOps * 10L; System.out.printf("Test 1 [B=%d dead=%d slow=%d fast=%d ratio=%.1fx]: %s%n", B, D, sOps, fOps, (double) sOps / fOps, ok ? "PASS" : "FAIL"); if (ok) passed++; } // Test 2: 500 brokers, 50 dead { total++; int B = 500, D = 50; long sOps = slow(B, D); long fOps = fast(B, D); boolean ok = sOps > fOps * 50L; System.out.printf("Test 2 [B=%d dead=%d slow=%d fast=%d ratio=%.1fx]: %s%n", B, D, sOps, fOps, (double) sOps / fOps, ok ? "PASS" : "FAIL"); if (ok) passed++; } // Test 3: worst case — all brokers are dead (full scan per check) { total++; int B = 300; List alive = new ArrayList<>(); // empty — all dead List all = new ArrayList<>(); for (int i = 0; i < B; i++) all.add("broker-" + i); long sOps = 0; for (String broker : all) { // contains on empty list — 0 ops but it returns immediately // Simulate non-empty alive list where none match for (String a : alive) { sOps++; if (a.equals(broker)) break; } sOps++; // simulate the contains() call cost even for empty } // Use a more interesting case: alive list has B/2 different brokers List aliveHalf = new ArrayList<>(); for (int i = B; i < B + B / 2; i++) aliveHalf.add("broker-" + i); List allB = new ArrayList<>(); for (int i = 0; i < B; i++) allB.add("broker-" + i); long slowOps = 0; for (String broker : allB) { for (String a : aliveHalf) { slowOps++; if (a.equals(broker)) break; } } long fastOps = 0; Set aliveSet = new HashSet<>(aliveHalf); for (String broker : allB) { fastOps++; aliveSet.contains(broker); } boolean ok = slowOps > fastOps * 50L; System.out.printf("Test 3 [B=%d all-dead slow=%d fast=%d ratio=%.1fx]: %s%n", B, slowOps, fastOps, (double) slowOps / fastOps, ok ? "PASS" : "FAIL"); if (ok) passed++; } // Test 4: correctness — same dead broker detection { total++; int B = 100, D = 10; List all = new ArrayList<>(); List alive = new ArrayList<>(); for (int i = 0; i < B; i++) all.add("broker-" + i); for (int i = D; i < B; i++) alive.add("broker-" + i); int slowDead = countDead(all, alive); int fastDead = countDeadFast(all, alive); boolean ok = slowDead == D && fastDead == D && slowDead == fastDead; System.out.printf("Test 4 [correctness dead_expected=%d slow=%d fast=%d equal=%b]: %s%n", D, slowDead, fastDead, slowDead == fastDead, ok ? "PASS" : "FAIL"); if (ok) passed++; } System.out.printf("%d/%d PASS%n", passed, total); if (passed != total) System.exit(1); } }