java-topology/defects/erpnext-0001/test/test_erpnext_0001.java
russell@unturf.com 896a29f83b erp scan: erpnext-0001/0002, ofbiz-0001/0002; odoo CLEAN; 4/4 PASS
erpnext-0001: BOM.get_children list dedup O(B^2) MEDIUM 4x
erpnext-0002: serial_batch_bundle serial/batch dedup O(N^2) HIGH 45x
ofbiz-0001: PaymentGatewayServices processList LinkedList O(N^2) HIGH 45x
ofbiz-0002: OrderReturnServices/OrderReadHelper payment dedup O(N^2) MEDIUM 36x
odoo: CLEAN (consistent set/frozenset/OrderedSet usage throughout)
2026-03-30 15:24:39 -04:00

132 lines
5.1 KiB
Java

import java.util.*;
/**
* Unit test for erpnext-0001: BOM.get_children() list dedup O(B^2)
*
* Simulates the BOM explosion BFS traversal where child_bom membership
* is checked via list scan (defect) vs set lookup (fix).
*
* Defect: bom.py lines 924-931 — "if child_bom not in bom_list" where
* bom_list is a Python list, giving O(B) per check and O(B^2) overall.
*
* Fix: maintain a parallel set for O(1) membership checks.
*/
public class test_erpnext_0001 {
// --- Defect: list-only BFS dedup ---
static List<String> getBomChildrenDefect(Map<String, List<String>> bomTree, String root) {
List<String> bomList = new ArrayList<>();
bomList.add(root);
int count = 0;
while (count < bomList.size()) {
List<String> children = bomTree.getOrDefault(bomList.get(count), Collections.emptyList());
for (String child : children) {
if (!bomList.contains(child)) { // O(N) per check
bomList.add(child);
}
}
count++;
}
Collections.reverse(bomList);
return bomList;
}
// --- Fix: set-backed BFS dedup ---
static List<String> getBomChildrenFixed(Map<String, List<String>> bomTree, String root) {
List<String> bomList = new ArrayList<>();
Set<String> bomSet = new HashSet<>();
bomList.add(root);
bomSet.add(root);
int count = 0;
while (count < bomList.size()) {
List<String> children = bomTree.getOrDefault(bomList.get(count), Collections.emptyList());
for (String child : children) {
if (!bomSet.contains(child)) { // O(1) per check
bomList.add(child);
bomSet.add(child);
}
}
count++;
}
Collections.reverse(bomList);
return bomList;
}
// Build a BOM tree with N levels, each having fanout children
static Map<String, List<String>> buildBomTree(int levels, int fanout) {
Map<String, List<String>> tree = new HashMap<>();
int id = 0;
Queue<String> queue = new LinkedList<>();
String root = "BOM-" + (id++);
queue.add(root);
for (int level = 0; level < levels && !queue.isEmpty(); level++) {
int size = queue.size();
for (int i = 0; i < size; i++) {
String parent = queue.poll();
List<String> children = new ArrayList<>();
for (int j = 0; j < fanout; j++) {
String child = "BOM-" + (id++);
children.add(child);
queue.add(child);
}
tree.put(parent, children);
}
}
return tree;
}
public static void main(String[] args) {
System.out.println("=== erpnext-0001: BOM get_children list dedup O(B^2) ===\n");
// Correctness test
Map<String, List<String>> smallTree = new HashMap<>();
smallTree.put("BOM-ROOT", Arrays.asList("BOM-A", "BOM-B"));
smallTree.put("BOM-A", Arrays.asList("BOM-C", "BOM-B")); // BOM-B appears twice
smallTree.put("BOM-B", Arrays.asList("BOM-D"));
smallTree.put("BOM-C", Collections.emptyList());
smallTree.put("BOM-D", Collections.emptyList());
List<String> defectResult = getBomChildrenDefect(smallTree, "BOM-ROOT");
List<String> fixedResult = getBomChildrenFixed(smallTree, "BOM-ROOT");
assert defectResult.equals(fixedResult) :
"Results differ: defect=" + defectResult + " fixed=" + fixedResult;
System.out.println("Correctness: PASS (both produce " + fixedResult + ")");
// Performance test: BOM tree with 500 unique BOMs
int levels = 5, fanout = 3;
Map<String, List<String>> bigTree = buildBomTree(levels, fanout);
String root = "BOM-0";
int totalBoms = bigTree.values().stream().mapToInt(List::size).sum() + 1;
System.out.println("BOM tree: " + totalBoms + " nodes, " + levels + " levels, fanout=" + fanout);
// Warmup
for (int i = 0; i < 5; i++) {
getBomChildrenDefect(bigTree, root);
getBomChildrenFixed(bigTree, root);
}
int iterations = 500;
long t0 = System.nanoTime();
for (int i = 0; i < iterations; i++) {
getBomChildrenDefect(bigTree, root);
}
long defectNs = System.nanoTime() - t0;
t0 = System.nanoTime();
for (int i = 0; i < iterations; i++) {
getBomChildrenFixed(bigTree, root);
}
long fixedNs = System.nanoTime() - t0;
double ratio = (double) defectNs / fixedNs;
System.out.printf("Defect: %,d ns / %d iters%n", defectNs, iterations);
System.out.printf("Fixed: %,d ns / %d iters%n", fixedNs, iterations);
System.out.printf("Ratio: %.1fx%n", ratio);
boolean pass = ratio > 1.5;
System.out.println("\nResult: " + (pass ? "PASS" : "FAIL") + " (ratio=" + String.format("%.1f", ratio) + "x)");
assert pass : "Expected speedup > 1.5x, got " + ratio;
}
}