java-topology/defects/prometheus/unit/Prometheus0002DependenciesTest.java

95 lines
3.6 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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");
}
}