java-topology/defects/bun/unit/BunBinFoldersTest.java

164 lines
6.3 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.*;
/**
* Unit test for bun-0001: CWE-407 in dirInfoUncached bin_folders dedup.
*
* bun-0001 (MEDIUM):
* File: src/resolver/resolver.zig ~lines 4041-4073
* Symbol: dirInfoUncached — for (bin_folders.constSlice()) |existing_folder|
* Defect: Module resolution walks D directory levels. For each level that
* contains a node_modules/.bin directory, the path is deduplicated against a
* shared bin_folders array using a linear scan: O(B) per call where B grows.
* With D levels each contributing one entry: O(D²) total dedup cost.
* Fix: Use a HashSet for O(1) membership: O(D) total.
*
* Modeled here in Java:
* Zig slice linear scan ≡ List<String>.contains() (defective)
* Zig StringHashMap ≡ java.util.Set<String> (fixed)
* Comparison counts tracked at the membership-test site.
*
* Expected at D=200 directory levels:
* defective comparisons ~ D*(D-1)/2 ≈ 19,900
* fixed comparisons ~ D = 200
* ratio > 50×
*/
public class BunBinFoldersTest {
// =========================================================================
// Model: dirInfoUncached bin_folders dedup
// =========================================================================
/**
* Defective: bin_folders is a plain List; dedup is a linear scan per entry.
*
* Simulates: dirInfoCachedMaybeLog calls dirInfoUncached D times, once per
* directory level. Each call finds a unique .bin path and deduplicates it
* against the growing bin_folders accumulator.
*/
static long deduplicateDefective(int D) {
long comparisons = 0;
List<String> binFolders = new ArrayList<>();
for (int level = 0; level < D; level++) {
String binPath = "/project/level_" + level + "/node_modules/.bin";
// for (bin_folders.constSlice()) |existing| — O(binFolders.size())
boolean found = false;
for (String existing : binFolders) {
comparisons++;
if (existing.equals(binPath)) { found = true; break; }
}
if (!found) {
binFolders.add(binPath);
}
}
return comparisons;
}
/**
* Fixed: HashSet companion for O(1) membership; same accumulated list.
*/
static long deduplicateFixed(int D) {
long comparisons = 0;
List<String> binFolders = new ArrayList<>();
Set<String> binFoldersSet = new HashSet<>();
for (int level = 0; level < D; level++) {
String binPath = "/project/level_" + level + "/node_modules/.bin";
// binFoldersSet.contains(binPath) — O(1)
comparisons++;
if (!binFoldersSet.contains(binPath)) {
binFoldersSet.add(binPath);
binFolders.add(binPath);
}
}
return comparisons;
}
// =========================================================================
// Tests
// =========================================================================
/** Test 1 — Correctness: both produce the same accumulated bin_folders list. */
static void testCorrectnessMatch() {
int D = 20;
List<String> defList = new ArrayList<>();
List<String> fixList = new ArrayList<>();
Set<String> fixSet = new HashSet<>();
for (int level = 0; level < D; level++) {
String binPath = "/project/level_" + level + "/node_modules/.bin";
if (!defList.contains(binPath)) defList.add(binPath);
if (!fixSet.contains(binPath)) { fixSet.add(binPath); fixList.add(binPath); }
}
assert defList.size() == D
: "expected " + D + " bin folders; got " + defList.size();
assert new HashSet<>(defList).equals(fixSet)
: "defective and fixed bin_folders sets differ";
System.out.println("PASS testCorrectnessMatch");
}
/** Test 2 — bun-0001: defective is O(D²), fixed is O(D). */
static void testRatioAtScale() {
int D = 200;
long defComp = deduplicateDefective(D);
long fixComp = deduplicateFixed(D);
double ratio = (double) defComp / fixComp;
// Defective: all D paths are unique; accumulator grows 0, 1, ..., D-1.
// Sum of scans: 0+1+...+(D-1) = D*(D-1)/2.
long expectedDef = (long) D * (D - 1) / 2;
assert defComp == expectedDef
: "defective comparisons should be D*(D-1)/2=" + expectedDef
+ "; got " + defComp;
// Fixed: exactly D probes (one Set.contains per level).
assert fixComp == D
: "fixed comparisons should be D=" + D + "; got " + fixComp;
assert ratio >= 50.0
: "ratio should be >=50x at D=200; got " + ratio;
System.out.printf(
"PASS testRatioAtScale (defective=%d, fixed=%d, ratio=%.1fx)%n",
defComp, fixComp, ratio);
}
/**
* Test 3 — Duplicate bin paths (same .bin dir encountered multiple times):
* dedup correctly suppresses re-adding the same path in both implementations.
*/
static void testWithDuplicateBinPaths() {
int D = 40; // 40 levels but only 20 unique .bin dirs (every 2 levels share one)
List<String> defList = new ArrayList<>();
Set<String> fixSet = new HashSet<>();
List<String> fixList = new ArrayList<>();
for (int level = 0; level < D; level++) {
String binPath = "/project/level_" + (level / 2) + "/node_modules/.bin";
if (!defList.contains(binPath)) defList.add(binPath);
if (!fixSet.contains(binPath)) { fixSet.add(binPath); fixList.add(binPath); }
}
int expectedUnique = D / 2;
assert defList.size() == expectedUnique
: "expected " + expectedUnique + " unique bin dirs; got " + defList.size();
assert new HashSet<>(defList).equals(fixSet)
: "duplicate-input bin_folders sets differ";
System.out.println("PASS testWithDuplicateBinPaths");
}
// =========================================================================
public static void main(String[] args) {
testCorrectnessMatch();
testRatioAtScale();
testWithDuplicateBinPaths();
System.out.println("All bun-0001 tests passed.");
}
}