245 lines
9.4 KiB
Java
245 lines
9.4 KiB
Java
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<String> dependencies;
|
||
|
||
DAGTask(String name, List<String> deps) {
|
||
this.name = name;
|
||
this.dependencies = deps;
|
||
}
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// DEFECTIVE: tasks stored as List<DAGTask>, GetTask does linear scan
|
||
// -----------------------------------------------------------------------
|
||
static class DefectiveDagContext {
|
||
final List<DAGTask> tasks;
|
||
|
||
DefectiveDagContext(List<DAGTask> 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<String> 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<String, DAGTask>, GetTask does O(1) lookup
|
||
// -----------------------------------------------------------------------
|
||
static class FixedDagContext {
|
||
final List<DAGTask> tasks;
|
||
final Map<String, DAGTask> taskByName;
|
||
|
||
FixedDagContext(List<DAGTask> 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<String> 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<DAGTask> buildTasks(int n) {
|
||
List<DAGTask> 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<DAGTask> 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<DAGTask> 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<DAGTask> tasks = buildTasks(n);
|
||
List<String> 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<DAGTask> tasks100 = buildTasks(100);
|
||
List<DAGTask> tasks200 = buildTasks(200);
|
||
List<String> target100 = new ArrayList<>(); for (DAGTask t : tasks100) target100.add(t.name);
|
||
List<String> 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<DAGTask> tasks100 = buildTasks(100);
|
||
List<DAGTask> tasks200 = buildTasks(200);
|
||
List<String> target100 = new ArrayList<>(); for (DAGTask t : tasks100) target100.add(t.name);
|
||
List<String> 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);
|
||
}
|
||
}
|