java-topology/defects/vertx/unit/HAManagerNodeLeftAlgorithm.java

111 lines
3.8 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.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* CWE-407 unit test — vertx-0001
*
* HAManager.nodeLeft() scans clusterMap (C entries) and calls nodes.contains() on a
* List<String> for each entry → O(C × N) = O(N²) in cluster size.
*
* Fix: convert nodes List to HashSet before the loop → O(N) total.
*/
public class HAManagerNodeLeftAlgorithm {
// Simulated clusterMap: nodeId -> haInfo (one entry per cluster node)
static Map<String, String> buildClusterMap(int clusterSize) {
Map<String, String> map = new HashMap<>();
for (int i = 0; i < clusterSize; i++) {
map.put("node-" + i, "{\"group\":\"default\",\"id\":\"node-" + i + "\"}");
}
return map;
}
// Simulated getNodes() — returns a List (as per ClusteredNode SPI contract)
static List<String> buildNodesList(int clusterSize) {
List<String> list = new ArrayList<>();
for (int i = 0; i < clusterSize; i++) {
list.add("node-" + i);
}
return list;
}
// SLOW: original pattern — List.contains inside loop
static long slowNodeLeft(Map<String, String> clusterMap, List<String> nodes, String leftNodeID) {
long slowOps = 0;
for (Map.Entry<String, String> entry : clusterMap.entrySet()) {
if (!leftNodeID.equals(entry.getKey())) {
// O(N) scan per iteration — the defect
for (String n : nodes) {
slowOps++;
if (n.equals(entry.getKey())) {
break;
}
}
}
}
return slowOps;
}
// FAST: fixed pattern — HashSet.contains inside loop
static long fastNodeLeft(Map<String, String> clusterMap, List<String> nodes, String leftNodeID) {
Set<String> nodesSet = new HashSet<>(nodes); // O(N) once
long fastOps = 0;
for (Map.Entry<String, String> entry : clusterMap.entrySet()) {
if (!leftNodeID.equals(entry.getKey())) {
// O(1) lookup
fastOps++;
nodesSet.contains(entry.getKey());
}
}
return fastOps;
}
public static void main(String[] args) {
int[] sizes = {100, 250, 500};
int pass = 0;
int fail = 0;
double minRatio = Double.MAX_VALUE;
for (int N : sizes) {
Map<String, String> clusterMap = buildClusterMap(N);
List<String> nodes = buildNodesList(N);
String leftNodeID = "node-" + (N - 1);
long slowOps = slowNodeLeft(clusterMap, nodes, leftNodeID);
long fastOps = fastNodeLeft(clusterMap, nodes, leftNodeID);
// Expected: slow ≈ triangular number sum of scan depths ≈ N*(N-1)/2
// fast = O(N-1) operations (one per remaining entry, O(1) each)
long expectedSlow = (long) N * (N - 1) / 2; // approximate
long expectedFast = N - 1;
double ratio = (double) slowOps / fastOps;
// Verify ordering
boolean ok = slowOps > fastOps && ratio >= 5.0;
if (ok) {
pass++;
} else {
fail++;
System.out.printf("FAIL N=%d: slowOps=%d fastOps=%d ratio=%.1f%n",
N, slowOps, fastOps, ratio);
}
System.out.printf("N=%d: slowOps=%d fastOps=%d ratio=%.1fx [%s]%n",
N, slowOps, fastOps, ratio, ok ? "PASS" : "FAIL");
minRatio = Math.min(minRatio, ratio);
}
System.out.printf("%d/%d PASS (min ratio=%.1fx)%n", pass, pass + fail, minRatio);
if (fail > 0) {
System.exit(1);
}
}
}