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 getBomChildrenDefect(Map> bomTree, String root) { List bomList = new ArrayList<>(); bomList.add(root); int count = 0; while (count < bomList.size()) { List 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 getBomChildrenFixed(Map> bomTree, String root) { List bomList = new ArrayList<>(); Set bomSet = new HashSet<>(); bomList.add(root); bomSet.add(root); int count = 0; while (count < bomList.size()) { List 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> buildBomTree(int levels, int fanout) { Map> tree = new HashMap<>(); int id = 0; Queue 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 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> 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 defectResult = getBomChildrenDefect(smallTree, "BOM-ROOT"); List 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> 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; } }