package unit; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; /** * CeleryTest * * Models CWE-407 defects in celery/celery: * * CEL-001 (MEDIUM) — canvas.py append_to_list_option(): `if value not in items` * where items is a plain list; called inside chain-build loops over T tasks * and E errbacks. O(L) scan per call × O(T×E) calls = O(T×E×L) total, * approaching O(N³) for long chains with many callbacks. * Fix: parallel set for O(1) membership test. * * All measurements are instrumented operation counts, not wall-clock timing. */ public class CeleryTest { // ----------------------------------------------------------------------- // CEL-001 modelling helpers // // Defective: ArrayList membership for value deduplication — O(L) per call // Fixed: HashSet mirror for O(1) deduplication // // Models append_to_list_option called T times (once per task) for E errbacks. // L = growing list length as callbacks accumulate. // Returns total scan cost across all calls. // ----------------------------------------------------------------------- /** * Defective: `if value not in items` where items is a list. * Called T*E times (T tasks × E errbacks each). * items grows as new values are added. */ static long cel001Defective(int numTasks, int numErrbacks) { // Each task has its own options list (link_error key) // We model the worst case: all errbacks are unique, list grows per task long totalScans = 0; for (int task = 0; task < numTasks; task++) { ArrayList items = new ArrayList<>(); // per-task link_error list for (int errback = 0; errback < numErrbacks; errback++) { // model `if value not in items` — O(current list size) totalScans += items.size(); // linear scan cost // actual check (correctness) if (!items.contains(errback)) { items.add(errback); } } } return totalScans; } /** * Fixed: parallel HashSet for O(1) membership test. */ static long cel001Fixed(int numTasks, int numErrbacks) { long totalLookups = 0; for (int task = 0; task < numTasks; task++) { ArrayList items = new ArrayList<>(); HashSet itemsSet = new HashSet<>(); for (int errback = 0; errback < numErrbacks; errback++) { // model `if value not in items_set` — O(1) totalLookups++; // one hash lookup per errback per task if (!itemsSet.contains(errback)) { items.add(errback); itemsSet.add(errback); } } } return totalLookups; } // ----------------------------------------------------------------------- // Test 1 — CEL-001: defective O(T×E²) vs fixed O(T×E) at T=100, E=50 // // For each task, the inner errback loop scans a growing list: // 0+1+2+...+(E-1) = E*(E-1)/2 scans per task. // Total defect cost = T * E*(E-1)/2. // Fixed cost = T * E. // ----------------------------------------------------------------------- static void test1_cel001_quadraticPerTask() { int T = 100; // tasks in chain int E = 50; // errbacks per task long defectCost = cel001Defective(T, E); long fixedCost = cel001Fixed(T, E); System.out.printf("test1 CEL-001: T=%d tasks E=%d errbacks defect=%d fixed=%d%n", T, E, defectCost, fixedCost); assert defectCost > fixedCost : "defect must be more expensive than fix"; // defective: T * E*(E-1)/2 long expectedDefect = (long) T * E * (E - 1) / 2; assert defectCost == expectedDefect : "expected defect cost=" + expectedDefect + " got=" + defectCost; // fixed: T * E long expectedFixed = (long) T * E; assert fixedCost == expectedFixed : "expected fixed cost=" + expectedFixed + " got=" + fixedCost; double ratio = (double) defectCost / Math.max(1, fixedCost); assert ratio > 10.0 : "expected ratio>10x, got " + ratio; } // ----------------------------------------------------------------------- // Test 2 — CEL-001: scaling — doubling E grows defect super-linearly // ----------------------------------------------------------------------- static void test2_cel001_errbackScaling() { int T = 50; int E1 = 40; int E2 = 80; // double E long d1 = cel001Defective(T, E1); long d2 = cel001Defective(T, E2); long f1 = cel001Fixed(T, E1); long f2 = cel001Fixed(T, E2); double defectGrowth = (double) d2 / Math.max(1, d1); double fixedGrowth = (double) f2 / Math.max(1, f1); System.out.printf("test2 CEL-001: T=%d E1=%d E2=%d defect_growth=%.2fx fixed_growth=%.2fx%n", T, E1, E2, defectGrowth, fixedGrowth); // defect grows ~4x when E doubles (O(E²) per task) assert defectGrowth > 3.5 : "defect should grow ~4x when E doubles, got " + defectGrowth; // fixed grows ~2x when E doubles (O(E) per task) assert fixedGrowth >= 1.8 && fixedGrowth <= 2.2 : "fixed should grow ~2x when E doubles, got " + fixedGrowth; assert defectGrowth > fixedGrowth : "defect growth must exceed fixed growth"; } // ----------------------------------------------------------------------- // Test 3 — CEL-001: large chain — T=500, E=20 — high-throughput scenario // ----------------------------------------------------------------------- static void test3_cel001_largeChain() { int T = 500; int E = 20; long defectCost = cel001Defective(T, E); long fixedCost = cel001Fixed(T, E); double ratio = (double) defectCost / Math.max(1, fixedCost); System.out.printf("test3 CEL-001: T=%d E=%d defect=%d fixed=%d ratio=%.1fx%n", T, E, defectCost, fixedCost, ratio); assert defectCost > fixedCost : "defect must be more expensive at T=" + T + " E=" + E; assert ratio > 5.0 : "expected ratio>5x at T=500 E=20, got " + ratio; } // ----------------------------------------------------------------------- // Main // ----------------------------------------------------------------------- public static void main(String[] args) { System.out.println("=== CeleryTest ==="); System.out.println("Modelling CWE-407: CEL-001 canvas.py append_to_list_option list O(T×E²) scan"); System.out.println(); test1_cel001_quadraticPerTask(); System.out.println(" PASS test1_cel001_quadraticPerTask"); test2_cel001_errbackScaling(); System.out.println(" PASS test2_cel001_errbackScaling"); test3_cel001_largeChain(); System.out.println(" PASS test3_cel001_largeChain"); System.out.println(); System.out.println("3/3 PASS"); } }