import java.util.*; /** * Unit test for jitsi-meet-0003: AV moderation pending participant dedup. * * Models the TypeScript reducer logic in Java to verify O(1) Map-based dedup * vs O(P) Array.find() dedup. * * Defect location: * react/features/av-moderation/reducer.ts line 210 (PARTICIPANT_PENDING_AUDIO) * react/features/av-moderation/functions.ts line 129 (isParticipantPending) * * Compile and run: * javac defects/jitsi-meet-0003/unit/AvModerationPendingTest.java * java -cp defects/jitsi-meet-0003/unit AvModerationPendingTest */ public class AvModerationPendingTest { // --- Defective implementation: Array.find() for dedup --- static List> pendingArrayAdd( List> pending, Map participant) { // O(P) linear scan — mirrors reducer.ts line 210 boolean alreadyPresent = false; for (Map p : pending) { if (p.get("id").equals(participant.get("id"))) { alreadyPresent = true; break; } } if (!alreadyPresent) { List> updated = new ArrayList<>(pending); updated.add(participant); return updated; } return pending; } static boolean isPendingArray(List> pending, String id) { // O(P) — mirrors functions.ts line 129 for (Map p : pending) { if (p.get("id").equals(id)) return true; } return false; } // --- Fixed implementation: Map for O(1) dedup --- static Map> pendingMapAdd( Map> pending, Map participant) { String id = participant.get("id"); if (!pending.containsKey(id)) { Map> updated = new LinkedHashMap<>(pending); updated.put(id, participant); return updated; } return pending; } static boolean isPendingMap(Map> pending, String id) { return pending.containsKey(id); // O(1) } // --- Helpers --- static Map participant(String id) { Map p = new HashMap<>(); p.put("id", id); p.put("name", "Participant " + id); return p; } static void check(boolean condition, String msg) { if (!condition) throw new AssertionError("FAIL: " + msg); } public static void main(String[] args) { System.out.println("jitsi-meet-0003: AvModerationPendingTest"); // Test 1: Array dedup correctness { List> pending = new ArrayList<>(); pending = pendingArrayAdd(pending, participant("p1")); pending = pendingArrayAdd(pending, participant("p1")); // duplicate pending = pendingArrayAdd(pending, participant("p2")); check(pending.size() == 2, "Array: duplicate must be rejected, got " + pending.size()); check(isPendingArray(pending, "p1"), "Array: p1 must be present"); check(isPendingArray(pending, "p2"), "Array: p2 must be present"); check(!isPendingArray(pending, "p3"), "Array: p3 must be absent"); System.out.println(" PASS test-1: array dedup correctness"); } // Test 2: Map dedup correctness { Map> pending = new LinkedHashMap<>(); pending = pendingMapAdd(pending, participant("p1")); pending = pendingMapAdd(pending, participant("p1")); // duplicate pending = pendingMapAdd(pending, participant("p2")); check(pending.size() == 2, "Map: duplicate must be rejected, got " + pending.size()); check(isPendingMap(pending, "p1"), "Map: p1 must be present"); check(isPendingMap(pending, "p2"), "Map: p2 must be present"); check(!isPendingMap(pending, "p3"), "Map: p3 must be absent"); System.out.println(" PASS test-2: map dedup correctness"); } // Test 3: Performance — count total comparisons (algorithmic, not timing) // Defective: adding P unique participants costs O(1)+O(2)+...+O(P) = O(P^2/2) comparisons // Fixed: O(P) hash lookups regardless of order { int P = 500; // Count comparison ops for array: each add scans the current list long arrayOps = 0; for (int size = 0; size < P; size++) { arrayOps += size; // scan from 0..size-1 before confirming absent } // Count comparison ops for map: O(1) per add (hash) long mapOps = P; // one hash + compare per add check(arrayOps > mapOps * 10, "Array O(P^2) ops=" + arrayOps + " must be >> map O(P) ops=" + mapOps); double ratio = (double) arrayOps / mapOps; System.out.printf(" PASS test-3: op-count P=%d | array %,d ops | map %,d ops | %.1fx%n", P, arrayOps, mapOps, ratio); } // Test 4: isParticipantPending lookup performance // Use a large enough list to make timing differences measurable { int P = 2000; int REPEATS = 500; List> arrayPending = new ArrayList<>(); Map> mapPending = new LinkedHashMap<>(); for (int i = 0; i < P; i++) { Map p = participant("p" + i); arrayPending.add(p); mapPending.put(p.get("id"), p); } // warm-up for (int i = 0; i < P; i++) { isPendingArray(arrayPending, "p" + i); isPendingMap(mapPending, "p" + i); } long arrayStart = System.nanoTime(); for (int r = 0; r < REPEATS; r++) { for (int i = 0; i < P; i++) { isPendingArray(arrayPending, "p" + i); } } long arrayLookupTime = System.nanoTime() - arrayStart; long mapStart = System.nanoTime(); for (int r = 0; r < REPEATS; r++) { for (int i = 0; i < P; i++) { isPendingMap(mapPending, "p" + i); } } long mapLookupTime = System.nanoTime() - mapStart; double ratio = (double) arrayLookupTime / mapLookupTime; System.out.printf(" PASS test-4: lookup P=%d x%d | array %,d ns | map %,d ns | %.1fx%n", P, REPEATS, arrayLookupTime, mapLookupTime, ratio); check(mapLookupTime < arrayLookupTime, "Map lookup must be faster than array scan P=" + P); } System.out.println("ALL 4 PASS"); } }