111 lines
3.8 KiB
Java
111 lines
3.8 KiB
Java
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);
|
||
}
|
||
}
|
||
}
|