225 lines
8.5 KiB
Java
225 lines
8.5 KiB
Java
import java.util.*;
|
||
|
||
/**
|
||
* CWE-407 unit tests for Vert.x core defects.
|
||
*
|
||
* vertx-0001: HAManager.nodeLeft() uses List<String>.contains() inside a loop
|
||
* over clusterMap.entrySet() → O(N×M) where N = clusterMap size, M = node count.
|
||
* Fix: convert List<String> to HashSet<String> before the loop → O(N+M).
|
||
*
|
||
* Additionally: HAManager.addHaInfoIfLost() calls
|
||
* clusterManager.getNodes().contains(nodeID) on every nodeAdded/nodeLeft event
|
||
* → O(M) per event with List, O(1) with Set.
|
||
*/
|
||
public class VertxTest {
|
||
|
||
// -----------------------------------------------------------------------
|
||
// vertx-0001 helpers — simulate the HAManager.nodeLeft inner loop
|
||
// -----------------------------------------------------------------------
|
||
|
||
/**
|
||
* Defective: List<String>.contains() called inside a for-each over
|
||
* clusterMapKeys → O(N × M) total.
|
||
*
|
||
* Mirrors HAManager lines 307-314:
|
||
* List<String> nodes = clusterManager.getNodes();
|
||
* for (Map.Entry<String,String> entry : clusterMap.entrySet()) {
|
||
* if (!leftNodeID.equals(entry.getKey()) && !nodes.contains(entry.getKey())) {
|
||
* checkFailover(...)
|
||
* }
|
||
* }
|
||
*/
|
||
static long nodeLeftDefect(List<String> clusterMapKeys, List<String> nodes, String leftNodeID) {
|
||
long ops = 0;
|
||
for (String key : clusterMapKeys) {
|
||
ops++;
|
||
if (!leftNodeID.equals(key)) {
|
||
// List.contains = linear scan, O(M) worst case
|
||
boolean found = nodes.contains(key);
|
||
ops += nodes.size(); // account for full scan (worst case)
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/**
|
||
* Fixed: wrap nodes in HashSet once, then O(1) membership test per entry.
|
||
*/
|
||
static long nodeLeftFixed(List<String> clusterMapKeys, List<String> nodes, String leftNodeID) {
|
||
Set<String> nodeSet = new HashSet<>(nodes); // O(M) once
|
||
long ops = nodes.size(); // cost of building the set
|
||
for (String key : clusterMapKeys) {
|
||
ops++;
|
||
if (!leftNodeID.equals(key)) {
|
||
boolean found = nodeSet.contains(key); // O(1)
|
||
ops++;
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// vertx-0001 test A — nodeLeft loop op-count ratio
|
||
// -----------------------------------------------------------------------
|
||
|
||
static void testVertx0001_opCount() {
|
||
int N = 1000; // clusterMap size
|
||
int M = 1000; // node count
|
||
|
||
List<String> clusterMapKeys = new ArrayList<>(N);
|
||
List<String> nodes = new ArrayList<>(M);
|
||
|
||
for (int i = 0; i < N; i++) clusterMapKeys.add("node-" + i);
|
||
for (int i = 0; i < M; i++) nodes.add("node-" + i);
|
||
|
||
String leftNode = "node-0";
|
||
|
||
long defectOps = nodeLeftDefect(clusterMapKeys, nodes, leftNode);
|
||
long fixedOps = nodeLeftFixed(clusterMapKeys, nodes, leftNode);
|
||
double ratio = (double) defectOps / fixedOps;
|
||
|
||
System.out.printf(
|
||
"vertx-0001 nodeLeft op-count: defect=%d fixed=%d ratio=%.1fx%n",
|
||
defectOps, fixedOps, ratio
|
||
);
|
||
|
||
if (ratio < 50.0) {
|
||
throw new AssertionError(
|
||
"vertx-0001: expected op-count ratio >= 50x at N=M=1000, got " + ratio);
|
||
}
|
||
System.out.println("vertx-0001 op-count: PASS");
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// vertx-0001 test B — wall-clock timing
|
||
// -----------------------------------------------------------------------
|
||
|
||
static void testVertx0001_timing() {
|
||
int N = 500;
|
||
int M = 500;
|
||
int REPS = 200;
|
||
|
||
List<String> clusterMapKeys = new ArrayList<>(N);
|
||
List<String> nodes = new ArrayList<>(M);
|
||
for (int i = 0; i < N; i++) clusterMapKeys.add("node-" + i);
|
||
for (int i = 0; i < M; i++) nodes.add("node-" + i);
|
||
|
||
String leftNode = "node-0";
|
||
|
||
// Warm up
|
||
for (int r = 0; r < 10; r++) {
|
||
simulateNodeLeftDefect(clusterMapKeys, nodes, leftNode);
|
||
simulateNodeLeftFixed(clusterMapKeys, nodes, leftNode);
|
||
}
|
||
|
||
long t0 = System.nanoTime();
|
||
for (int r = 0; r < REPS; r++) simulateNodeLeftDefect(clusterMapKeys, nodes, leftNode);
|
||
long defectNs = System.nanoTime() - t0;
|
||
|
||
long t1 = System.nanoTime();
|
||
for (int r = 0; r < REPS; r++) simulateNodeLeftFixed(clusterMapKeys, nodes, leftNode);
|
||
long fixedNs = System.nanoTime() - t1;
|
||
|
||
double ratio = (double) defectNs / fixedNs;
|
||
System.out.printf(
|
||
"vertx-0001 nodeLeft timing: defect=%.2fms fixed=%.2fms ratio=%.1fx%n",
|
||
defectNs / 1e6, fixedNs / 1e6, ratio
|
||
);
|
||
|
||
if (ratio < 5.0) {
|
||
throw new AssertionError(
|
||
"vertx-0001: expected wall-clock ratio >= 5x at N=M=500, got " + ratio);
|
||
}
|
||
System.out.println("vertx-0001 timing: PASS");
|
||
}
|
||
|
||
// Real simulation (no op counting) for timing test
|
||
static int simulateNodeLeftDefect(List<String> clusterMapKeys, List<String> nodes, String leftNodeID) {
|
||
int failovers = 0;
|
||
for (String key : clusterMapKeys) {
|
||
if (!leftNodeID.equals(key) && !nodes.contains(key)) {
|
||
failovers++;
|
||
}
|
||
}
|
||
return failovers;
|
||
}
|
||
|
||
static int simulateNodeLeftFixed(List<String> clusterMapKeys, List<String> nodes, String leftNodeID) {
|
||
Set<String> nodeSet = new HashSet<>(nodes);
|
||
int failovers = 0;
|
||
for (String key : clusterMapKeys) {
|
||
if (!leftNodeID.equals(key) && !nodeSet.contains(key)) {
|
||
failovers++;
|
||
}
|
||
}
|
||
return failovers;
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// vertx-0001 test C — correctness
|
||
// -----------------------------------------------------------------------
|
||
|
||
static void testVertx0001_correctness() {
|
||
List<String> nodes = Arrays.asList("alpha", "beta", "gamma", "delta");
|
||
|
||
// HashSet must agree with List for all membership queries
|
||
Set<String> nodeSet = new HashSet<>(nodes);
|
||
for (String n : nodes) {
|
||
if (nodes.contains(n) != nodeSet.contains(n)) {
|
||
throw new AssertionError("Membership mismatch for: " + n);
|
||
}
|
||
}
|
||
if (nodes.contains("omega") != nodeSet.contains("omega")) {
|
||
throw new AssertionError("Membership mismatch for omega");
|
||
}
|
||
|
||
// nodeLeft logic must produce identical failover sets
|
||
List<String> clusterMapKeys = Arrays.asList("alpha", "beta", "gamma", "delta", "epsilon");
|
||
String leftNode = "alpha";
|
||
|
||
// nodes = {alpha, beta, gamma, delta} clusterMap has "epsilon" extra
|
||
List<String> defectiveFailovers = new ArrayList<>();
|
||
for (String key : clusterMapKeys) {
|
||
if (!leftNode.equals(key) && !nodes.contains(key)) {
|
||
defectiveFailovers.add(key);
|
||
}
|
||
}
|
||
|
||
Set<String> fixedNodeSet = new HashSet<>(nodes);
|
||
List<String> fixedFailovers = new ArrayList<>();
|
||
for (String key : clusterMapKeys) {
|
||
if (!leftNode.equals(key) && !fixedNodeSet.contains(key)) {
|
||
fixedFailovers.add(key);
|
||
}
|
||
}
|
||
|
||
if (!defectiveFailovers.equals(fixedFailovers)) {
|
||
throw new AssertionError(
|
||
"Failover lists differ: defect=" + defectiveFailovers + " fixed=" + fixedFailovers);
|
||
}
|
||
if (fixedFailovers.size() != 1 || !fixedFailovers.get(0).equals("epsilon")) {
|
||
throw new AssertionError("Expected [epsilon], got: " + fixedFailovers);
|
||
}
|
||
|
||
// addHaInfoIfLost pattern: single .contains() call on list vs set
|
||
String targetNodeId = "delta";
|
||
boolean listResult = nodes.contains(targetNodeId);
|
||
boolean setResult = nodeSet.contains(targetNodeId);
|
||
if (listResult != setResult) {
|
||
throw new AssertionError("addHaInfoIfLost: list/set mismatch for " + targetNodeId);
|
||
}
|
||
|
||
System.out.println("vertx-0001 correctness: PASS");
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Main
|
||
// -----------------------------------------------------------------------
|
||
|
||
public static void main(String[] args) {
|
||
testVertx0001_correctness();
|
||
testVertx0001_opCount();
|
||
testVertx0001_timing();
|
||
System.out.println("ALL PASS");
|
||
}
|
||
}
|