import java.util.*; /** * Unit test for thunderbird-0007: about3Pane.js SmartServerPane.initServer() * existingURIs Array.includes() O(N²) in do-while loop. * * Models the JavaScript pattern: * let existingURIs = Array.from(existingRows, li => li.uri); // re-built each iteration * do { * const folderURI = remainingFolderURIs.shift(); * if (existingURIs.includes(folderURI)) continue; // O(F) per check * this.addFolder(...); * existingURIs = Array.from(existingRows, li => li.uri); // full rebuild! * } while (remainingFolderURIs.length); * * Fix: replace Array with Set so membership checks are O(1). */ public class ThunderbirdInitServerTest { // --- Defective implementation (Array + linear includes + full rebuild) --- static int initServerDefective(List remainingFolderURIs, List initialExistingURIs) { List existingRows = new ArrayList<>(initialExistingURIs); int ops = 0; List remaining = new ArrayList<>(remainingFolderURIs); List existingURIs = new ArrayList<>(existingRows); while (!remaining.isEmpty()) { String folderURI = remaining.remove(0); // O(F) scan — the defect ops++; boolean found = existingURIs.contains(folderURI); if (found) continue; // addFolder: mutates existingRows existingRows.add(folderURI); // Full rebuild — another O(F) scan hidden here existingURIs = new ArrayList<>(existingRows); ops += existingRows.size(); // cost of rebuild } return ops; } // --- Fixed implementation (Set + O(1) has()) --- static int initServerFixed(List remainingFolderURIs, List initialExistingURIs) { List existingRows = new ArrayList<>(initialExistingURIs); int ops = 0; List remaining = new ArrayList<>(remainingFolderURIs); Set existingURIs = new HashSet<>(existingRows); while (!remaining.isEmpty()) { String folderURI = remaining.remove(0); ops++; if (existingURIs.contains(folderURI)) continue; // addFolder: just add to Set, no rebuild existingRows.add(folderURI); existingURIs.add(folderURI); } return ops; } // --- Helper --- static List makeFolderURIs(int count, String prefix) { List uris = new ArrayList<>(); for (int i = 0; i < count; i++) { uris.add("imap://user@server/mailbox/" + prefix + i); } return uris; } public static void main(String[] args) { System.out.println("=== thunderbird-0007: initServer existingURIs O(N²) ===\n"); // Test 1: correctness - no duplicates processed { List existing = makeFolderURIs(3, "existing-"); List remaining = makeFolderURIs(5, "new-"); // Add overlap: one of the new folders is already in existing remaining.add(existing.get(0)); // duplicate // Both implementations should accept the same non-duplicate count // (just verify they run without error and give non-negative ops) int opsD = initServerDefective(new ArrayList<>(remaining), new ArrayList<>(existing)); int opsF = initServerFixed(new ArrayList<>(remaining), new ArrayList<>(existing)); assert opsD > 0 : "FAIL: defective returned 0 ops"; assert opsF > 0 : "FAIL: fixed returned 0 ops"; System.out.println("PASS correctness: defective=" + opsD + " ops, fixed=" + opsF + " ops"); } // Test 2: small N — verify both give same logical result { for (int n : new int[]{10, 50, 100}) { List existing = makeFolderURIs(2, "e-"); List remaining = makeFolderURIs(n, "r-"); int opsD = initServerDefective(new ArrayList<>(remaining), new ArrayList<>(existing)); int opsF = initServerFixed(new ArrayList<>(remaining), new ArrayList<>(existing)); // Fixed should always use fewer ops than defective for N > small constant System.out.printf("N=%-4d defective=%6d ops fixed=%4d ops ratio=%.1fx%n", n, opsD, opsF, (double) opsD / opsF); } } // Test 3: ratio benchmark at F=500 (large IMAP account) { int F = 500; List existing = makeFolderURIs(10, "existing-"); List remaining = makeFolderURIs(F, "folder-"); long t0 = System.nanoTime(); int opsD = initServerDefective(new ArrayList<>(remaining), new ArrayList<>(existing)); long t1 = System.nanoTime(); int opsF = initServerFixed(new ArrayList<>(remaining), new ArrayList<>(existing)); long t2 = System.nanoTime(); double ratio = (double) opsD / opsF; System.out.printf("%nF=%-4d defective=%8d ops fixed=%6d ops op-ratio=%.1fx%n", F, opsD, opsF, ratio); System.out.printf(" defective=%6d µs fixed=%5d µs%n", (t1 - t0) / 1000, (t2 - t1) / 1000); assert ratio > 5.0 : "FAIL: expected ratio > 5x at F=500, got " + ratio; System.out.println("PASS: ratio > 5x confirmed"); } // Test 4: all-duplicate case (everything already in tree) { List existing = makeFolderURIs(100, "folder-"); List remaining = new ArrayList<>(existing); // 100% overlap int opsD = initServerDefective(new ArrayList<>(remaining), new ArrayList<>(existing)); int opsF = initServerFixed(new ArrayList<>(remaining), new ArrayList<>(existing)); // Both should still handle this correctly (skip all) assert opsD > 0 : "FAIL: defective should still do ops on duplicate check"; System.out.println("PASS all-duplicate: defective=" + opsD + " fixed=" + opsF); } System.out.println("\nAll tests PASS"); } }