Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
185 lines
7 KiB
Java
185 lines
7 KiB
Java
package unit;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* Unit tests for hive-0001 and hive-0002: GenMRProcContext seenOps CWE-407.
|
|
*
|
|
* hive-0001: taskToSeenOps maps Task → List<Operator>. 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<FileSinkOperator>. 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<Operator> per task ───────────────────────────────────
|
|
|
|
static class DefectiveSeenOps {
|
|
final Map<Task, List<Operator>> taskToSeenOps = new HashMap<>();
|
|
long comparisons = 0;
|
|
|
|
boolean isSeenOp(Task task, Operator op) {
|
|
List<Operator> 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<Operator> per task ────────────────────────────────────────
|
|
|
|
static class FixedSeenOps {
|
|
final Map<Task, Set<Operator>> taskToSeenOps = new HashMap<>();
|
|
long comparisons = 0;
|
|
|
|
boolean isSeenOp(Task task, Operator op) {
|
|
Set<Operator> 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<Operator> 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<Operator> 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<Operator> 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.");
|
|
}
|
|
}
|