java-topology/defects/odl/unit/OdlGroupRegistryTest.java
russell@unturf.com db29a08762 undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections.
Squash of 94 local commits onto remote master.
2026-03-26 19:48:18 -04:00

264 lines
11 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.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* Unit test for ODL-001: DevicesGroupRegistry CWE-407 defect.
*
* Models both defective (ArrayList/O(N)) and fixed (HashSet/O(1))
* implementations of DevicesGroupRegistry and measures comparison counts
* using an instrumented counter — no wall-clock timing needed.
*
* Defect: isGroupPresent() calls ArrayList.contains() which does a linear scan.
* Called from the outer reconciliation loop over G groups with N already-tracked
* groups per node, producing O(G×N) total comparisons per switch reconnect.
*
* Fix: Replace List<Uint32> with Set<Uint32> (HashSet). contains() becomes O(1).
*/
public class OdlGroupRegistryTest {
// -----------------------------------------------------------------------
// Instrumented counter — incremented by every element comparison
// -----------------------------------------------------------------------
static final AtomicLong comparisonCounter = new AtomicLong(0);
// -----------------------------------------------------------------------
// Instrumented value type (stand-in for Uint32)
// -----------------------------------------------------------------------
static class TrackedId {
final long value;
TrackedId(long value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
comparisonCounter.incrementAndGet();
if (this == o) return true;
if (!(o instanceof TrackedId)) return false;
return value == ((TrackedId) o).value;
}
@Override
public int hashCode() {
// Standard hash — NOT instrumented; only equals() counts comparisons.
return Long.hashCode(value);
}
}
// -----------------------------------------------------------------------
// Defective registry — ArrayList per node (O(N) contains)
// -----------------------------------------------------------------------
static class DefectiveRegistry {
private final Map<String, List<TrackedId>> deviceGroupMapping = new ConcurrentHashMap<>();
public boolean isGroupPresent(String nodeId, TrackedId groupId) {
List<TrackedId> groups = deviceGroupMapping.get(nodeId);
return groups != null && groups.contains(groupId); // O(N) linear scan
}
public void storeGroup(String nodeId, TrackedId groupId) {
deviceGroupMapping.computeIfAbsent(nodeId, k -> new ArrayList<>()).add(groupId);
}
}
// -----------------------------------------------------------------------
// Fixed registry — HashSet per node (O(1) contains) // CWE-407 fix
// -----------------------------------------------------------------------
static class FixedRegistry {
private final Map<String, Set<TrackedId>> deviceGroupMapping = new ConcurrentHashMap<>();
public boolean isGroupPresent(String nodeId, TrackedId groupId) {
Set<TrackedId> groups = deviceGroupMapping.get(nodeId);
return groups != null && groups.contains(groupId); // O(1) hash lookup
}
public void storeGroup(String nodeId, TrackedId groupId) {
deviceGroupMapping.computeIfAbsent(nodeId, k -> new HashSet<>()).add(groupId); // CWE-407 fix
}
}
// -----------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------
/** Pre-populate a DefectiveRegistry with N already-tracked groups for nodeId. */
static DefectiveRegistry buildDefective(String nodeId, int n) {
DefectiveRegistry reg = new DefectiveRegistry();
for (int i = 0; i < n; i++) {
reg.storeGroup(nodeId, new TrackedId(i));
}
return reg;
}
/** Pre-populate a FixedRegistry with N already-tracked groups for nodeId. */
static FixedRegistry buildFixed(String nodeId, int n) {
FixedRegistry reg = new FixedRegistry();
for (int i = 0; i < n; i++) {
reg.storeGroup(nodeId, new TrackedId(i));
}
return reg;
}
/**
* Simulate the reconciliation loop: for each of G groups-to-install,
* call isGroupPresent() using a TrackedId that is NOT present (worst-case
* for ArrayList — must scan full list before returning false).
*
* Returns the number of equals() comparisons recorded.
*/
static long runDefectiveReconciliation(DefectiveRegistry reg, String nodeId, int g) {
comparisonCounter.set(0);
long absent = 1_000_000L; // ids that do not exist in the registry
for (int i = 0; i < g; i++) {
reg.isGroupPresent(nodeId, new TrackedId(absent + i));
}
return comparisonCounter.get();
}
static long runFixedReconciliation(FixedRegistry reg, String nodeId, int g) {
comparisonCounter.set(0);
long absent = 1_000_000L;
for (int i = 0; i < g; i++) {
reg.isGroupPresent(nodeId, new TrackedId(absent + i));
}
return comparisonCounter.get();
}
// -----------------------------------------------------------------------
// Test methods
// -----------------------------------------------------------------------
/**
* testDefectiveIsQuadratic:
* With G=200 installs and N=200 tracked, the defective ArrayList path
* must produce at least G*N/2 comparisons (i.e. at least N comparisons
* per miss for half the cases on average — in practice all N because
* the id is absent).
*/
static void testDefectiveIsQuadratic() {
int G = 200, N = 200;
String node = "openflow:1";
DefectiveRegistry reg = buildDefective(node, N);
long comparisons = runDefectiveReconciliation(reg, node, G);
long expected = (long) G * N; // each miss scans all N entries
assert comparisons >= expected :
"testDefectiveIsQuadratic FAIL: expected >= " + expected + " comparisons, got " + comparisons;
System.out.printf(" testDefectiveIsQuadratic PASS G=%d N=%d comparisons=%d (expected>=%d)%n",
G, N, comparisons, expected);
}
/**
* testFixedIsLinear:
* With G=200 installs and N=200 tracked, the fixed HashSet path must
* produce at most G*2 comparisons (each hash-bucket lookup may hit at
* most a handful of equals() calls in a well-distributed set; in practice
* usually 0 or 1 for absent keys with no hash collisions).
*
* Upper bound: G * 4 comparisons — very generous for a 200-entry HashSet.
*/
static void testFixedIsLinear() {
int G = 200, N = 200;
String node = "openflow:1";
FixedRegistry reg = buildFixed(node, N);
long comparisons = runFixedReconciliation(reg, node, G);
long upperBound = (long) G * 4;
assert comparisons <= upperBound :
"testFixedIsLinear FAIL: expected <= " + upperBound + " comparisons, got " + comparisons;
System.out.printf(" testFixedIsLinear PASS G=%d N=%d comparisons=%d (expected<=%d)%n",
G, N, comparisons, upperBound);
}
/**
* testRatioAtScale:
* Measures defective vs fixed comparison counts at G=200, N=200 and
* asserts the ratio is at least 20x, confirming the algorithmic
* complexity improvement.
*/
static void testRatioAtScale() {
int G = 200, N = 200;
String node = "openflow:1";
DefectiveRegistry defReg = buildDefective(node, N);
long defectiveCount = runDefectiveReconciliation(defReg, node, G);
FixedRegistry fixReg = buildFixed(node, N);
long fixedCount = runFixedReconciliation(fixReg, node, G);
// Avoid division by zero: fixed may be 0 comparisons (all hash misses with no collisions)
double ratio = fixedCount > 0 ? (double) defectiveCount / fixedCount : defectiveCount;
assert ratio >= 20.0 :
"testRatioAtScale FAIL: ratio=" + ratio + " (defective=" + defectiveCount
+ " fixed=" + fixedCount + "), expected >= 20x";
System.out.printf(" testRatioAtScale PASS defective=%d fixed=%d ratio=%.1fx%n",
defectiveCount, fixedCount, ratio);
}
/**
* testCorrectnessDefective:
* Verifies that the defective implementation still produces correct
* boolean results — the defect is performance only, not correctness.
*/
static void testCorrectnessDefective() {
String node = "openflow:1";
DefectiveRegistry reg = new DefectiveRegistry();
TrackedId g1 = new TrackedId(10);
TrackedId g2 = new TrackedId(20);
TrackedId g3 = new TrackedId(30);
reg.storeGroup(node, g1);
reg.storeGroup(node, g2);
comparisonCounter.set(0);
assert reg.isGroupPresent(node, new TrackedId(10)) : "testCorrectnessDefective FAIL: g1 should be present";
assert reg.isGroupPresent(node, new TrackedId(20)) : "testCorrectnessDefective FAIL: g2 should be present";
assert !reg.isGroupPresent(node, new TrackedId(30)) : "testCorrectnessDefective FAIL: g3 should be absent";
assert !reg.isGroupPresent("openflow:2", new TrackedId(10)) : "testCorrectnessDefective FAIL: wrong node";
System.out.println(" testCorrectnessDefective PASS present/absent/wrong-node all correct");
}
/**
* testCorrectnessFixed:
* Same correctness assertions for the fixed HashSet implementation.
*/
static void testCorrectnessFixed() {
String node = "openflow:1";
FixedRegistry reg = new FixedRegistry();
reg.storeGroup(node, new TrackedId(10));
reg.storeGroup(node, new TrackedId(20));
comparisonCounter.set(0);
assert reg.isGroupPresent(node, new TrackedId(10)) : "testCorrectnessFixed FAIL: g1 should be present";
assert reg.isGroupPresent(node, new TrackedId(20)) : "testCorrectnessFixed FAIL: g2 should be present";
assert !reg.isGroupPresent(node, new TrackedId(30)) : "testCorrectnessFixed FAIL: g3 should be absent";
assert !reg.isGroupPresent("openflow:2", new TrackedId(10)) : "testCorrectnessFixed FAIL: wrong node";
System.out.println(" testCorrectnessFixed PASS present/absent/wrong-node all correct");
}
// -----------------------------------------------------------------------
// Main
// -----------------------------------------------------------------------
public static void main(String[] args) {
System.out.println("ODL-001 DevicesGroupRegistry CWE-407 unit tests");
System.out.println("================================================");
testCorrectnessDefective();
testCorrectnessFixed();
testDefectiveIsQuadratic();
testFixedIsLinear();
testRatioAtScale();
System.out.println("================================================");
System.out.println("ALL TESTS PASSED");
}
}