146 lines
6.2 KiB
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");
|
|
}
|
|
}
|