package unit; import java.util.*; /** * Cassandra CWE-407 unit tests — standalone, no JUnit. * * cassandra-0001 DEAD_STATES/SILENT_SHUTDOWN_STATES List.contains() per endpoint * src/java/org/apache/cassandra/gms/Gossiper.java:147,1334,1343 */ public class CassandraTest { // ----------------------------------------------------------------------- // cassandra-0001: isDeadState() — List.contains() vs Set.contains() // // Models the per-gossip-tick loop: for each of N endpoints, check whether // the endpoint's status string is in the dead-states collection. // Slow: List (ArrayList / Arrays.asList) — O(|states|) per lookup. // Fast: HashSet — O(1) per lookup. // ----------------------------------------------------------------------- static final String[] STATUS_STRINGS = { "REMOVING_TOKEN", "REMOVED_TOKEN", "STATUS_LEFT", "HIBERNATE", "NORMAL", "BOOTSTRAPPING", "JOINING", "LEAVING" }; static final String[] DEAD_STATES_ARR = { "REMOVING_TOKEN", "REMOVED_TOKEN", "STATUS_LEFT", "HIBERNATE" }; /** * Slow: DEAD_STATES is an ArrayList; membership test is O(|DEAD_STATES|). * Called once per endpoint per gossip round. * Returns total comparison operations performed. */ static long deadStateSlowOps(int numEndpoints) { List deadStates = new ArrayList<>(Arrays.asList(DEAD_STATES_ARR)); // Simulate SILENT_SHUTDOWN_STATES built as ArrayList too List silentStates = new ArrayList<>(deadStates); long ops = 0; Random rng = new Random(42); for (int ep = 0; ep < numEndpoints; ep++) { // Each endpoint has a status — pick one at random String status = STATUS_STRINGS[rng.nextInt(STATUS_STRINGS.length)]; // isDeadState: scan deadStates list for (int i = 0; i < deadStates.size(); i++) { ops++; if (deadStates.get(i).equals(status)) break; } // isSilentShutdownState: scan silentStates list for (int i = 0; i < silentStates.size(); i++) { ops++; if (silentStates.get(i).equals(status)) break; } } return ops; } /** * Fast: DEAD_STATES and SILENT_SHUTDOWN_STATES are HashSet; O(1) lookup. * Returns total comparison operations performed (one per endpoint per check). */ static long deadStateFastOps(int numEndpoints) { Set deadStates = new HashSet<>(Arrays.asList(DEAD_STATES_ARR)); Set silentStates = new HashSet<>(deadStates); long ops = 0; Random rng = new Random(42); for (int ep = 0; ep < numEndpoints; ep++) { String status = STATUS_STRINGS[rng.nextInt(STATUS_STRINGS.length)]; // isDeadState: O(1) hash lookup — count as 1 op ops++; deadStates.contains(status); // isSilentShutdownState: O(1) hash lookup — count as 1 op ops++; silentStates.contains(status); } return ops; } // ----------------------------------------------------------------------- // Test runner // ----------------------------------------------------------------------- public static void main(String[] args) { int passed = 0; int failed = 0; // cassandra-0001 at various cluster sizes int[] sizes = {100, 500, 1000, 2000}; for (int n : sizes) { long slow = deadStateSlowOps(n); long fast = deadStateFastOps(n); // Slow should be > 2x fast: List scan vs O(1) set // At minimum 2x because DEAD_STATES has 4 entries and ~half statuses // won't be found until partway through the list. boolean pass = slow > fast * 2; System.out.printf("cassandra-0001 N=%-5d slow=%6d fast=%6d ratio=%.1fx %s%n", n, slow, fast, (double) slow / fast, pass ? "PASS" : "FAIL"); if (pass) passed++; else failed++; } System.out.printf("%nTotal: %d/%d PASS%n", passed, passed + failed); if (failed > 0) System.exit(1); } }