95 lines
3.6 KiB
Java
95 lines
3.6 KiB
Java
package unit;
|
||
|
||
import java.util.*;
|
||
|
||
/**
|
||
* Standalone unit test for prometheus-0002: CWE-407.
|
||
*
|
||
* prometheus-0002: dependencyMap.dependencies() O(R²×D) in AnalyseRules
|
||
* slow() mirrors the defective pattern: for each of R rules, scan all R
|
||
* map entries doing a linear list-contains on the dependents slice.
|
||
* Cost per call: O(R × D); total for AnalyseRules: O(R² × D).
|
||
* fast() maintains an inverse map built once in O(R×D); each lookup O(1).
|
||
* Cost per call: O(1); total for AnalyseRules: O(R).
|
||
* Assert: slowOps > fastOps * 5x for R=200 rules with D=10 dependents each.
|
||
*/
|
||
public class Prometheus0002DependenciesTest {
|
||
|
||
/**
|
||
* Slow path — dependencies(r): iterate entire forward map, slices.Contains per entry.
|
||
* forwardMap: ruleId → list of ruleIds that depend on it (dependents).
|
||
*/
|
||
static long slowAnalyseRules(Map<Integer, List<Integer>> forwardMap, List<Integer> rules) {
|
||
long ops = 0;
|
||
for (int r : rules) {
|
||
// dependencies(r): scan all entries
|
||
for (Map.Entry<Integer, List<Integer>> e : forwardMap.entrySet()) {
|
||
// slices.Contains(dependents, r) — O(D) linear scan
|
||
for (int dep : e.getValue()) {
|
||
ops++;
|
||
if (dep == r) break; // early exit on hit, worst case no hit
|
||
}
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/**
|
||
* Fast path — precompute inverse map; dependencies(r) is O(1) lookup.
|
||
*/
|
||
static long fastAnalyseRules(Map<Integer, List<Integer>> forwardMap, List<Integer> rules) {
|
||
long ops = 0;
|
||
// Build inverse map once: O(R×D)
|
||
Map<Integer, List<Integer>> inverseMap = new HashMap<>();
|
||
for (Map.Entry<Integer, List<Integer>> e : forwardMap.entrySet()) {
|
||
int rule = e.getKey();
|
||
for (int dep : e.getValue()) {
|
||
ops++; // cost to build inverse
|
||
inverseMap.computeIfAbsent(dep, k -> new ArrayList<>()).add(rule);
|
||
}
|
||
}
|
||
// Now each dependencies(r) lookup is O(1)
|
||
for (int r : rules) {
|
||
ops++; // O(1) map lookup
|
||
List<Integer> deps = inverseMap.get(r);
|
||
// use deps (no scan needed)
|
||
if (deps != null) {
|
||
ops += deps.size(); // charge for reading results (same in both)
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
static void testDependencies() {
|
||
int R = 200; // number of rules
|
||
int D = 10; // average dependents per rule (fan-out)
|
||
|
||
// Build a synthetic forward dependency map:
|
||
// rule i has dependents [i+1, i+2, ..., i+D] (mod R) so D deps each
|
||
List<Integer> rules = new ArrayList<>(R);
|
||
for (int i = 0; i < R; i++) rules.add(i);
|
||
|
||
Map<Integer, List<Integer>> forwardMap = new HashMap<>();
|
||
for (int i = 0; i < R; i++) {
|
||
List<Integer> dependents = new ArrayList<>(D);
|
||
for (int j = 1; j <= D; j++) {
|
||
dependents.add((i + j) % R);
|
||
}
|
||
forwardMap.put(i, dependents);
|
||
}
|
||
|
||
long sOps = slowAnalyseRules(forwardMap, rules);
|
||
long fOps = fastAnalyseRules(forwardMap, rules);
|
||
|
||
int minRatio = 5;
|
||
boolean pass = sOps > fOps * minRatio;
|
||
System.out.printf("prometheus-0002 [R=%d D=%d]: slow=%d fast=%d ratio=%.1fx — %s%n",
|
||
R, D, sOps, fOps, (double) sOps / fOps, pass ? "PASS" : "FAIL");
|
||
if (!pass) throw new AssertionError("prometheus-0002 FAIL: slow=" + sOps + " fast=" + fOps);
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
testDependencies();
|
||
System.out.println("1/1 PASS");
|
||
}
|
||
}
|