package unit; import java.util.*; /** * argo-0001 — O(N) linear GetTask() scan inside O(N) DAG execution loop * * Simulates the defective GetTask() from: * workflow/controller/dag.go:83-89 * * Defect: dagContext stores tasks as []DAGTask (slice). GetTask(name) does * linear scan. Called O(N) times per executeDAG() cycle → O(N²) total. * * Fix: Build a map[string]*DAGTask at dagContext construction time → O(1) lookup. * * The unit test simulates: * 1. A DAGContext with N tasks (stored as list in defective, map in fixed) * 2. An executeDAG loop that calls GetTask() once per targetTask * 3. Counts total comparisons to confirm O(N²) vs O(N) */ public class ArgoGetTaskAlgorithm { static int opCount = 0; static void check(String desc, boolean cond) { System.out.println((cond ? "PASS" : "FAIL") + ": " + desc); if (!cond) throw new AssertionError("FAIL: " + desc); } // ----------------------------------------------------------------------- // Simulated DAGTask // ----------------------------------------------------------------------- static class DAGTask { final String name; final List dependencies; DAGTask(String name, List deps) { this.name = name; this.dependencies = deps; } } // ----------------------------------------------------------------------- // DEFECTIVE: tasks stored as List, GetTask does linear scan // ----------------------------------------------------------------------- static class DefectiveDagContext { final List tasks; DefectiveDagContext(List tasks) { this.tasks = new ArrayList<>(tasks); } DAGTask getTask(String taskName) { for (DAGTask task : tasks) { // O(N) scan opCount++; if (task.name.equals(taskName)) { return task; } } throw new RuntimeException("task not found: " + taskName); } // Simulates executeDAG: for each target task, call getTask() 3 times // (matches the 3 GetTask calls on lines 292, 300, 307 in dag.go) void executeDAG(List targetTasks) { for (String taskName : targetTasks) { getTask(taskName); // line 292: task := dagCtx.GetTask(ctx, taskName) getTask(taskName); // line 300: dagCtx.GetTask(ctx, taskName).Hooks getTask(taskName); // line 307: dagCtx.GetTask(ctx, taskName).GetExitHook(...) } } } // ----------------------------------------------------------------------- // FIXED: tasks stored as Map, GetTask does O(1) lookup // ----------------------------------------------------------------------- static class FixedDagContext { final List tasks; final Map taskByName; FixedDagContext(List tasks) { this.tasks = new ArrayList<>(tasks); this.taskByName = new HashMap<>(tasks.size() * 2); for (DAGTask t : tasks) { this.taskByName.put(t.name, t); } } DAGTask getTask(String taskName) { opCount++; // count each lookup DAGTask task = taskByName.get(taskName); if (task == null) throw new RuntimeException("task not found: " + taskName); return task; } void executeDAG(List targetTasks) { for (String taskName : targetTasks) { getTask(taskName); getTask(taskName); getTask(taskName); } } } // Build N tasks (all as target tasks — worst case: getTask scans entire list) static List buildTasks(int n) { List tasks = new ArrayList<>(n); for (int i = 0; i < n; i++) { tasks.add(new DAGTask("task-" + i, Collections.emptyList())); } return tasks; } public static void main(String[] args) { int passed = 0; int total = 0; // ------------------------------------------------------------------ // Test 1: Correctness — both return same task // ------------------------------------------------------------------ total++; { List tasks = buildTasks(10); DefectiveDagContext def = new DefectiveDagContext(tasks); FixedDagContext fix = new FixedDagContext(tasks); opCount = 0; DAGTask defTask = def.getTask("task-7"); opCount = 0; DAGTask fixTask = fix.getTask("task-7"); check("Correctness: both find task-7", defTask.name.equals("task-7") && fixTask.name.equals("task-7")); passed++; } // ------------------------------------------------------------------ // Test 2: Op-count for getTask on last element — defective is O(N) // ------------------------------------------------------------------ total++; { int n = 100; List tasks = buildTasks(n); DefectiveDagContext def = new DefectiveDagContext(tasks); FixedDagContext fix = new FixedDagContext(tasks); opCount = 0; def.getTask("task-99"); // last element -> worst case: N comparisons long defOps = opCount; opCount = 0; fix.getTask("task-99"); long fixOps = opCount; System.out.printf(" Single getTask (last of N=%d): defective=%d ops, fixed=%d ops%n", n, defOps, fixOps); check("Defective getTask(last) scans all N elements", defOps == n); check("Fixed getTask is O(1)", fixOps == 1); passed += 2; total++; } // ------------------------------------------------------------------ // Test 3: executeDAG op-count ratio — O(N²) vs O(N) // ------------------------------------------------------------------ total++; { int n = 300; List tasks = buildTasks(n); List targetTasks = new ArrayList<>(); for (DAGTask t : tasks) targetTasks.add(t.name); DefectiveDagContext def = new DefectiveDagContext(tasks); FixedDagContext fix = new FixedDagContext(tasks); opCount = 0; def.executeDAG(targetTasks); long defOps = opCount; opCount = 0; fix.executeDAG(targetTasks); long fixOps = opCount; double ratio = (double) defOps / fixOps; System.out.printf(" executeDAG N=%d: defective=%d ops, fixed=%d ops, ratio=%.1fx%n", n, defOps, fixOps, ratio); // Defective: 3 calls × avg N/2 scans × N tasks = 3N²/2 // Fixed: 3 calls × 1 lookup × N tasks = 3N // Expected ratio ≈ N/2 = 150 for N=300; require >= 50x conservatively check("Op-count ratio >= 50x for executeDAG N=" + n, ratio >= 50.0); passed++; } // ------------------------------------------------------------------ // Test 4: Quadratic growth — ops at N=200 should be ~4x ops at N=100 // ------------------------------------------------------------------ total++; { List tasks100 = buildTasks(100); List tasks200 = buildTasks(200); List target100 = new ArrayList<>(); for (DAGTask t : tasks100) target100.add(t.name); List target200 = new ArrayList<>(); for (DAGTask t : tasks200) target200.add(t.name); opCount = 0; new DefectiveDagContext(tasks100).executeDAG(target100); long ops100 = opCount; opCount = 0; new DefectiveDagContext(tasks200).executeDAG(target200); long ops200 = opCount; double growthRatio = (double) ops200 / ops100; System.out.printf(" Defective growth (200 vs 100): %.2fx (expected ~4x for O(N²))%n", growthRatio); check("Defective shows quadratic growth (ratio >= 3.5x)", growthRatio >= 3.5); passed++; } // ------------------------------------------------------------------ // Test 5: Linear growth for fixed // ------------------------------------------------------------------ total++; { List tasks100 = buildTasks(100); List tasks200 = buildTasks(200); List target100 = new ArrayList<>(); for (DAGTask t : tasks100) target100.add(t.name); List target200 = new ArrayList<>(); for (DAGTask t : tasks200) target200.add(t.name); opCount = 0; new FixedDagContext(tasks100).executeDAG(target100); long ops100 = opCount; opCount = 0; new FixedDagContext(tasks200).executeDAG(target200); long ops200 = opCount; double growthRatio = (double) ops200 / ops100; System.out.printf(" Fixed growth (200 vs 100): %.2fx (expected ~2x for O(N))%n", growthRatio); check("Fixed shows linear growth (ratio < 3.0x)", growthRatio < 3.0); passed++; } System.out.printf("%n%d/%d PASS%n", passed, total); } }