java-topology/defects/thunderbird-0007/test/ThunderbirdInitServerTest.java

146 lines
6.2 KiB
Java

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<String> remainingFolderURIs,
List<String> initialExistingURIs) {
List<String> existingRows = new ArrayList<>(initialExistingURIs);
int ops = 0;
List<String> remaining = new ArrayList<>(remainingFolderURIs);
List<String> 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<String> remainingFolderURIs,
List<String> initialExistingURIs) {
List<String> existingRows = new ArrayList<>(initialExistingURIs);
int ops = 0;
List<String> remaining = new ArrayList<>(remainingFolderURIs);
Set<String> 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<String> makeFolderURIs(int count, String prefix) {
List<String> 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<String> existing = makeFolderURIs(3, "existing-");
List<String> 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<String> existing = makeFolderURIs(2, "e-");
List<String> 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<String> existing = makeFolderURIs(10, "existing-");
List<String> 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<String> existing = makeFolderURIs(100, "folder-");
List<String> 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");
}
}