package unit; import java.util.*; /** * Unit tests for hive-0001 and hive-0002: GenMRProcContext seenOps CWE-407. * * hive-0001: taskToSeenOps maps Task → List. isSeenOp() calls * ArrayList.contains() — O(n) per lookup. Called O(T) times during * MapReduce plan generation. Total O(T²) per task. * * hive-0002: seenFileSinkOps is List. contains() in * GenMRFileSink1 is O(n) on each file sink encountered. * * Fix: List → HashSet for both. O(1) contains(). * * Measurement: count element-level comparisons in contains(). */ public class HiveGenMRSeenOpsTest { // ── Minimal stub types ─────────────────────────────────────────────────── static class Task { final String id; Task(String id) { this.id = id; } @Override public boolean equals(Object o) { return o instanceof Task && ((Task) o).id.equals(id); } @Override public int hashCode() { return id.hashCode(); } @Override public String toString() { return "Task(" + id + ")"; } } static class Operator { final String id; Operator(String id) { this.id = id; } @Override public boolean equals(Object o) { return o instanceof Operator && ((Operator) o).id.equals(id); } @Override public int hashCode() { return id.hashCode(); } @Override public String toString() { return "Op(" + id + ")"; } } // ── DEFECTIVE: List per task ─────────────────────────────────── static class DefectiveSeenOps { final Map> taskToSeenOps = new HashMap<>(); long comparisons = 0; boolean isSeenOp(Task task, Operator op) { List seen = taskToSeenOps.get(task); if (seen == null) return false; for (Operator s : seen) { // linear scan comparisons++; if (s.equals(op)) return true; } return false; } void addSeenOp(Task task, Operator op) { taskToSeenOps.computeIfAbsent(task, k -> new ArrayList<>()).add(op); } } // ── FIXED: Set per task ──────────────────────────────────────── static class FixedSeenOps { final Map> taskToSeenOps = new HashMap<>(); long comparisons = 0; boolean isSeenOp(Task task, Operator op) { Set seen = taskToSeenOps.get(task); if (seen == null) return false; comparisons++; // O(1) hash lookup return seen.contains(op); } void addSeenOp(Task task, Operator op) { taskToSeenOps.computeIfAbsent(task, k -> new HashSet<>()).add(op); } } // ── Simulate MapReduce plan generation ─────────────────────────────────── // For T table-scan operators per task: each is added then checked T times. public static long simulateDefective(int T) { Task task = new Task("mapTask"); DefectiveSeenOps ctx = new DefectiveSeenOps(); List ops = new ArrayList<>(); for (int i = 0; i < T; i++) ops.add(new Operator("op_" + i)); for (Operator op : ops) { // Check if seen (not yet) then add — models isSeenOp + addSeenOp if (!ctx.isSeenOp(task, op)) ctx.addSeenOp(task, op); } // Second pass: re-check all (models merged task path through same operators) for (Operator op : ops) { ctx.isSeenOp(task, op); } return ctx.comparisons; } public static long simulateFixed(int T) { Task task = new Task("mapTask"); FixedSeenOps ctx = new FixedSeenOps(); List ops = new ArrayList<>(); for (int i = 0; i < T; i++) ops.add(new Operator("op_" + i)); for (Operator op : ops) { if (!ctx.isSeenOp(task, op)) ctx.addSeenOp(task, op); } for (Operator op : ops) { ctx.isSeenOp(task, op); } return ctx.comparisons; } // ── Tests ──────────────────────────────────────────────────────────────── static void testCorrectnessMatch() { Task task = new Task("t1"); DefectiveSeenOps def = new DefectiveSeenOps(); FixedSeenOps fix = new FixedSeenOps(); List ops = new ArrayList<>(); for (int i = 0; i < 10; i++) ops.add(new Operator("op_" + i)); for (Operator op : ops) { boolean d = def.isSeenOp(task, op); def.addSeenOp(task, op); boolean f = fix.isSeenOp(task, op); fix.addSeenOp(task, op); assert d == f : "isSeenOp result mismatch before add"; } for (Operator op : ops) { assert def.isSeenOp(task, op) == fix.isSeenOp(task, op) : "isSeenOp result mismatch after add"; } System.out.println("PASS testCorrectnessMatch"); } static void testDefectiveGrowsQuadratically() { long prev = -1; for (int T : new int[]{20, 40, 80}) { long c = simulateDefective(T); if (prev > 0) { double ratio = (double) c / prev; assert ratio > 2.5 : "defective comparisons should grow >2.5x when T doubles; got " + ratio; } prev = c; } System.out.println("PASS testDefectiveGrowsQuadratically"); } static void testFixedGrowsLinearly() { long prev = -1; for (int T : new int[]{20, 40, 80}) { long c = simulateFixed(T); if (prev > 0) { double ratio = (double) c / prev; // Should be very close to 2x (linear) — allow 2.0±0.2 assert ratio < 2.25 : "fixed comparisons should grow ≈2x when T doubles; got " + ratio; } prev = c; } System.out.println("PASS testFixedGrowsLinearly"); } static void testRatioAtScale() { int T = 100; long def_c = simulateDefective(T); long fix_c = simulateFixed(T); double ratio = (double) def_c / fix_c; assert ratio > 20 : "at T=100, defective should be >20x slower; ratio=" + ratio; System.out.printf("PASS testRatioAtScale (defective=%d, fixed=%d, ratio=%.1fx)%n", def_c, fix_c, ratio); } public static void main(String[] args) { testCorrectnessMatch(); testDefectiveGrowsQuadratically(); testFixedGrowsLinearly(); testRatioAtScale(); System.out.println("All hive-0001/0002 tests passed."); } }