package unit; import support.Moad0005Algorithm; import support.Moad0005Algorithm.DefectiveCache; import support.Moad0005Algorithm.FixedCache; /** * Unit tests for MOAD-0005: CWE-362 — A Thundering Herd. * * Proves from first principles: * 1. Defective: N callers all seeing a null cache entry all invoke * expensiveCompute() — N computes instead of 1. This is the herd. * 2. Fixed: ConcurrentHashMap.computeIfAbsent() ensures exactly 1 compute * regardless of how many callers miss simultaneously. * 3. Both return the same (correct) value for any key. * 4. Sequential access on a warm cache: both call compute exactly once. * * The single-threaded simulation models the race window: all callers read * before any caller's put() completes. Integration tests use real threads. * * No build tool required. Compile and run: * * cd tests * java -m jdk.compiler/com.sun.tools.javac.Main -cp . \ * support/Moad0005Algorithm.java unit/Moad0005UnitTest.java * java -cp . unit.Moad0005UnitTest */ public class Moad0005UnitTest { private static int passed = 0; private static int failed = 0; public static void main(String[] args) { System.out.println("=== Moad0005UnitTest (A Thundering Herd) ===\n"); System.out.println("-- Correctness: both caches return correct value --"); testDefectiveReturnsCorrectValue(); testFixedReturnsCorrectValue(); testBothReturnSameValue(); System.out.println("\n-- Defect: N concurrent misses produce N computes --"); testDefectiveConcurrentMissN10(); testDefectiveConcurrentMissN50(); testDefectiveComputeCountScalesWithCallers(); System.out.println("\n-- Fix: N concurrent misses produce exactly 1 compute --"); testFixedConcurrentMissN10(); testFixedConcurrentMissN50(); testFixedComputeCountAlwaysOne(); System.out.println("\n-- Warm cache: both compute exactly once sequentially --"); testDefectiveWarmCacheNoRecompute(); testFixedWarmCacheNoRecompute(); System.out.printf("\n%d passed, %d failed%n", passed, failed); if (failed > 0) System.exit(1); } // ── Correctness ─────────────────────────────────────────────────────────── static void testDefectiveReturnsCorrectValue() { Moad0005Algorithm.resetCounter(); DefectiveCache cache = new DefectiveCache(); String val = cache.getOrCompute("foo"); assertEqual("defective: returns 'value-for-foo'", "value-for-foo", val); } static void testFixedReturnsCorrectValue() { Moad0005Algorithm.resetCounter(); FixedCache cache = new FixedCache(); String val = cache.getOrCompute("foo"); assertEqual("fixed: returns 'value-for-foo'", "value-for-foo", val); } static void testBothReturnSameValue() { Moad0005Algorithm.resetCounter(); DefectiveCache def = new DefectiveCache(); FixedCache fix = new FixedCache(); String dVal = def.getOrCompute("bar"); Moad0005Algorithm.resetCounter(); String fVal = fix.getOrCompute("bar"); assertEqual("both: return same value for 'bar'", dVal, fVal); } // ── Defect ──────────────────────────────────────────────────────────────── static void testDefectiveConcurrentMissN10() { int computes = Moad0005Algorithm.simulateDefectiveConcurrentMiss(10, "key"); // DEFECT: all 10 callers see null simultaneously → 10 computes assertEqual("defective: 10 concurrent misses cause 10 computes (herd)", 10, computes); } static void testDefectiveConcurrentMissN50() { int computes = Moad0005Algorithm.simulateDefectiveConcurrentMiss(50, "key"); // DEFECT: 50 concurrent misses → 50 computes assertEqual("defective: 50 concurrent misses cause 50 computes (herd)", 50, computes); } static void testDefectiveComputeCountScalesWithCallers() { int c10 = Moad0005Algorithm.simulateDefectiveConcurrentMiss(10, "key"); int c20 = Moad0005Algorithm.simulateDefectiveConcurrentMiss(20, "key"); // DEFECT: compute count scales linearly with caller count (N wasted computes) assertTrue("defective: doubling callers doubles computes", c20 == 2 * c10, "c10=" + c10 + " c20=" + c20); } // ── Fix ─────────────────────────────────────────────────────────────────── static void testFixedConcurrentMissN10() { int computes = Moad0005Algorithm.simulateFixedConcurrentMiss(10, "key"); // FIX: computeIfAbsent — exactly 1 compute regardless of caller count assertEqual("fixed: 10 concurrent misses cause exactly 1 compute", 1, computes); } static void testFixedConcurrentMissN50() { int computes = Moad0005Algorithm.simulateFixedConcurrentMiss(50, "key"); assertEqual("fixed: 50 concurrent misses cause exactly 1 compute", 1, computes); } static void testFixedComputeCountAlwaysOne() { int c10 = Moad0005Algorithm.simulateFixedConcurrentMiss(10, "key"); int c100 = Moad0005Algorithm.simulateFixedConcurrentMiss(100, "key"); // FIX: compute count does not scale — always 1 assertEqual("fixed: 10 callers — 1 compute", 1, c10); assertEqual("fixed: 100 callers — 1 compute", 1, c100); } // ── Warm cache ──────────────────────────────────────────────────────────── static void testDefectiveWarmCacheNoRecompute() { Moad0005Algorithm.resetCounter(); DefectiveCache cache = new DefectiveCache(); cache.getOrCompute("warm"); // cold miss — 1 compute int afterFirst = Moad0005Algorithm.COMPUTE_CALLS.get(); cache.getOrCompute("warm"); // warm hit — 0 compute int afterSecond = Moad0005Algorithm.COMPUTE_CALLS.get(); assertEqual("defective: first call computes once", 1, afterFirst); assertEqual("defective: second sequential call hits cache", 1, afterSecond); } static void testFixedWarmCacheNoRecompute() { Moad0005Algorithm.resetCounter(); FixedCache cache = new FixedCache(); cache.getOrCompute("warm"); // cold miss — 1 compute int afterFirst = Moad0005Algorithm.COMPUTE_CALLS.get(); cache.getOrCompute("warm"); // warm hit — 0 compute int afterSecond = Moad0005Algorithm.COMPUTE_CALLS.get(); assertEqual("fixed: first call computes once", 1, afterFirst); assertEqual("fixed: second sequential call hits cache", 1, afterSecond); } // ── Helpers ─────────────────────────────────────────────────────────────── static void assertEqual(String label, int expected, int actual) { if (expected == actual) { System.out.printf(" PASS: %s%n", label); passed++; } else { System.out.printf(" FAIL: %s — expected %d, got %d%n", label, expected, actual); failed++; } } static void assertEqual(String label, String expected, String actual) { if (expected == null ? actual == null : expected.equals(actual)) { System.out.printf(" PASS: %s%n", label); passed++; } else { System.out.printf(" FAIL: %s — expected '%s', got '%s'%n", label, expected, actual); failed++; } } static void assertTrue(String label, boolean condition, String detail) { if (condition) { System.out.printf(" PASS: %s%n", label); passed++; } else { System.out.printf(" FAIL: %s — %s%n", label, detail); failed++; } } }