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

225 lines
8.5 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.

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