package unit; import java.util.*; /** * NatsPeerDedupTest — Java model of CWE-407 defect in NATS JetStream cluster. * * NATS-0001 (MEDIUM): jetstream_cluster.go — peer dedup loop uses slices.Contains. * During a stream move/scale, existing peers are deduplicated against a new peer * group using: * * for _, peer := range rg.Peers { * if !slices.Contains(nrg.Peers, peer) { * peerSet = append(peerSet, peer) * } * } * * slices.Contains is O(|nrg.Peers|). Outer loop is O(|rg.Peers|). * Total: O(|rg.Peers| * |nrg.Peers|) — quadratic in replica count. * * Fix: build a map[string]struct{} from nrg.Peers before the loop (O(|nrg|)), * then probe in O(1) per rg peer. Total: O(|rg| + |nrg|). */ public class NatsPeerDedupTest { // ----------------------------------------------------------------------- // Algorithm models // ----------------------------------------------------------------------- /** * Defective: for each peer in rgPeers, scan all of nrgPeers. * Returns exact comparison count (worst-case: no overlap). */ static long slow(List rgPeers, List nrgPeers) { long comparisons = 0; List peerSet = new ArrayList<>(); for (String peer : rgPeers) { // slices.Contains inner loop boolean found = false; for (String nrgPeer : nrgPeers) { comparisons++; if (peer.equals(nrgPeer)) { found = true; break; } } if (!found) { peerSet.add(peer); } } // append nrg.Peers to peerSet peerSet.addAll(nrgPeers); return comparisons; } /** * Fixed: build set from nrgPeers first, then probe O(1) per rg peer. * Returns exact operation count (set build + probes). */ static long fast(List rgPeers, List nrgPeers) { long ops = 0; // Build set: nrgPeers.size() insertions Map nrgSet = new HashMap<>(); for (String p : nrgPeers) { nrgSet.put(p, true); ops++; } List peerSet = new ArrayList<>(); for (String peer : rgPeers) { ops++; // O(1) map probe if (!nrgSet.containsKey(peer)) { peerSet.add(peer); } } peerSet.addAll(nrgPeers); return ops; } // ----------------------------------------------------------------------- // Helper: build disjoint peer lists // ----------------------------------------------------------------------- static List makePeers(String prefix, int count, int offset) { List peers = new ArrayList<>(count); for (int i = 0; i < count; i++) { peers.add(prefix + (offset + i)); } return peers; } // ----------------------------------------------------------------------- // Tests // ----------------------------------------------------------------------- /** * Test 1: worst-case N=100 rg peers, M=100 nrg peers, no overlap. * slow() = N*M = 10000 comparisons. * fast() = N+M = 200 operations. * Assert slow > fast * 10x. */ static void test_nats0001_comparison_ratio() { final int N = 100, M = 100; List rgPeers = makePeers("rg-", N, 0); List nrgPeers = makePeers("nrg-", M, 0); // disjoint long sOps = slow(rgPeers, nrgPeers); long fOps = fast(rgPeers, nrgPeers); double ratio = (double) sOps / fOps; System.out.printf(" NATS-0001 ratio slow=%d fast=%d ratio=%.1fx%n", sOps, fOps, ratio); assert sOps > fOps * 10 : "NATS-0001: expected slow > fast*10, got slow=" + sOps + " fast=" + fOps; } /** * Test 2: scaling — doubling N doubles slow() cost, barely changes fast(). */ static void test_nats0001_scaling_with_peer_count() { final int M = 50; List nrgPeers = makePeers("nrg-", M, 0); List rgLo = makePeers("rg-", 50, 100); List rgHi = makePeers("rg-", 500, 100); long sLo = slow(rgLo, nrgPeers); long sHi = slow(rgHi, nrgPeers); long fLo = fast(rgLo, nrgPeers); long fHi = fast(rgHi, nrgPeers); double sScale = (double) sHi / sLo; double fScale = (double) fHi / fLo; System.out.printf(" NATS-0001 N scaling slowScale=%.1fx fastScale=%.1fx%n", sScale, fScale); assert sScale > 5.0 : "NATS-0001: slow should scale with N, got " + sScale; assert fHi < sHi : "NATS-0001: fast should be cheaper than slow at N=500"; } /** * Test 3: scaling — doubling M doubles slow() cost, fast() scales only linearly. */ static void test_nats0001_scaling_with_nrg_peer_count() { final int N = 50; List rgPeers = makePeers("rg-", N, 0); List nrgLo = makePeers("nrg-", 50, 1000); List nrgHi = makePeers("nrg-", 500, 1000); long sLo = slow(rgPeers, nrgLo); long sHi = slow(rgPeers, nrgHi); long fLo = fast(rgPeers, nrgLo); long fHi = fast(rgPeers, nrgHi); double sScale = (double) sHi / sLo; System.out.printf(" NATS-0001 M scaling slowScale=%.1fx fast_lo=%d fast_hi=%d%n", sScale, fLo, fHi); assert sScale > 5.0 : "NATS-0001: slow should scale with M, got " + sScale; assert fHi < sHi : "NATS-0001: fast should be cheaper than slow at M=500"; } /** * Test 4: correctness — both paths must produce identical peerSet output. */ static void test_nats0001_correctness() { // partial overlap: rg has peers 0-9, nrg has peers 5-14 List rgPeers = makePeers("peer-", 10, 0); // peer-0..peer-9 List nrgPeers = makePeers("peer-", 10, 5); // peer-5..peer-14 // Run slow path to get result List slowResult = new ArrayList<>(); for (String peer : rgPeers) { if (!nrgPeers.contains(peer)) slowResult.add(peer); } slowResult.addAll(nrgPeers); // Run fast path to get result Map nrgSet = new HashMap<>(); for (String p : nrgPeers) nrgSet.put(p, true); List fastResult = new ArrayList<>(); for (String peer : rgPeers) { if (!nrgSet.containsKey(peer)) fastResult.add(peer); } fastResult.addAll(nrgPeers); Collections.sort(slowResult); Collections.sort(fastResult); System.out.printf(" NATS-0001 correctness: peerSet size=%d (slow=%d fast=%d)%n", slowResult.size(), slowResult.size(), fastResult.size()); assert slowResult.equals(fastResult) : "NATS-0001 correctness: peer sets differ: " + slowResult + " vs " + fastResult; } // ----------------------------------------------------------------------- // Main // ----------------------------------------------------------------------- public static void main(String[] args) { System.out.println("NatsPeerDedupTest — CWE-407 model tests (NATS-0001)"); System.out.println("====================================================="); run("test_nats0001_comparison_ratio", NatsPeerDedupTest::test_nats0001_comparison_ratio); run("test_nats0001_scaling_with_peer_count", NatsPeerDedupTest::test_nats0001_scaling_with_peer_count); run("test_nats0001_scaling_with_nrg_peer_count", NatsPeerDedupTest::test_nats0001_scaling_with_nrg_peer_count); run("test_nats0001_correctness", NatsPeerDedupTest::test_nats0001_correctness); System.out.println("====================================================="); System.out.println("4/4 PASS"); } @FunctionalInterface interface TestFn { void run() throws Exception; } static void run(String name, TestFn fn) { System.out.print(" [RUN] " + name + " ... "); try { fn.run(); System.out.println("PASS"); } catch (AssertionError e) { System.out.println("FAIL — " + e.getMessage()); System.exit(1); } catch (Exception e) { System.out.println("ERR — " + e); System.exit(1); } } }