feat: add unit, integration, and functional test coverage for all 9 MOADs
Each MOAD now has a synthetic defective specimen and fixed specimen proven from first principles across three test tiers: Unit (tests/unit/Moad000X*.java): - Correctness: defective and fixed produce identical functional output - Defect behavior: defective specimen exhibits the defect (measurable) - Fix behavior: fixed specimen eliminates the defect Integration (tests/integration/AllMoadsIntegrationTest.java): - All 9 MOADs proven at medium scale (N=500-2000) - MOAD-0001: O(N^2) vs O(N) list scan at N=1000 - MOAD-0002: 500 sessions trample each other (defective) vs coexist (fixed) - MOAD-0003: 250 anonymous requests leak auth identity (defective) vs zero (fixed) - MOAD-0004: 3000 credential exposures across 1000 requests (defective) vs zero (fixed) - MOAD-0005: 500 computes for 500 concurrent misses vs exactly 1 - MOAD-0006: all 500 passwords extractable from DB (defective) vs unextractable (fixed) - MOAD-0007: N=2000 spatial objects, defective visits all 2000 vs O(log N + k) - MOAD-0009: 990 wasted firings for 1000 ticks / 10 events vs zero waste - MOAD-0011: 10240 NFA steps vs 13 steps on N=12 adversarial input (788x) Functional (tests/functional/AllMoadsFunctionalTest.java): - MOAD-0005: real-thread contention proves herd (defective >1 compute, fixed exactly 1) - MOAD-0007: N=50000 spatial objects, 50M defective probes vs 516K fixed (97x speedup) - MOAD-0009: 10000 ticks / 10 events, 9990 wasted firings vs zero (1000x ratio) - MOAD-0011: N=16 adversarial, 163840 defective steps vs 17 fixed (9638x ratio) Support algorithms (tests/support/Moad000X*.java): - Moad0002Algorithm: shared mutable global state (DefectiveAudioSystem / FixedAudioSystem + Context) - Moad0003Algorithm: ThreadLocal not cleared (handleDefective / handleFixed with finally) - Moad0004Algorithm: HTTP headers logged verbatim (logDefective / logFixed with CREDENTIAL_HEADERS denylist) - Moad0005Algorithm: get+null+compute+put (DefectiveCache HashMap / FixedCache ConcurrentHashMap.computeIfAbsent) - Moad0006Algorithm: Base64 password storage (DefectiveCredentialStore / FixedCredentialStore SHA-256+salt) - Moad0007Algorithm: linear spatial scan (queryDefective list / queryFixed sorted array + binary search) - Moad0009Algorithm: timer-driven polling (runDefectiveScheduler / runFixedEventDriven) - Moad0011Algorithm: PCRE nested quantifiers (matchDefective backtracking NFA / matchFixed linear NFA) Makefile: added unit-moad-0002 through unit-moad-0011 targets, integration-all-moads, functional-all-moads. integration and functional targets now depend on all-MOADs variants.
This commit is contained in:
parent
1f88aa0374
commit
4888153e40
19 changed files with 3359 additions and 5 deletions
155
tests/unit/Moad0002UnitTest.java
Normal file
155
tests/unit/Moad0002UnitTest.java
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
package unit;
|
||||
|
||||
import support.Moad0002Algorithm;
|
||||
import support.Moad0002Algorithm.Context;
|
||||
import support.Moad0002Algorithm.DefectiveAudioSystem;
|
||||
import support.Moad0002Algorithm.DefectiveDisplaySystem;
|
||||
import support.Moad0002Algorithm.FixedAudioSystem;
|
||||
import support.Moad0002Algorithm.FixedDisplaySystem;
|
||||
|
||||
/**
|
||||
* Unit tests for MOAD-0002: An Intertangled Defect.
|
||||
*
|
||||
* Proves from first principles:
|
||||
* 1. Defective: AudioSystem.setLocale() mutates state seen by DisplaySystem —
|
||||
* cross-subsystem interference through shared global state.
|
||||
* 2. Defective: two independent "contexts" sharing GLOBAL cannot coexist —
|
||||
* the second write tramples the first silently.
|
||||
* 3. Fixed: two Context objects carry independent values simultaneously.
|
||||
* 4. Fixed: AudioSystem and DisplaySystem read from separate Context objects
|
||||
* without interfering with each other.
|
||||
*
|
||||
* No build tool required. Compile and run:
|
||||
*
|
||||
* cd tests
|
||||
* java -m jdk.compiler/com.sun.tools.javac.Main -cp . \
|
||||
* support/Moad0002Algorithm.java unit/Moad0002UnitTest.java
|
||||
* java -cp . unit.Moad0002UnitTest
|
||||
*/
|
||||
public class Moad0002UnitTest {
|
||||
|
||||
private static int passed = 0;
|
||||
private static int failed = 0;
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Moad0002UnitTest (An Intertangled Defect) ===\n");
|
||||
|
||||
System.out.println("-- Correctness: subsystems read values correctly --");
|
||||
testDefectiveBasicRead();
|
||||
testFixedBasicRead();
|
||||
|
||||
System.out.println("\n-- Defect: shared global state causes cross-subsystem interference --");
|
||||
testDefectiveAudioLocaleLeaksToDisplay();
|
||||
testDefectiveTwoContextsTrample();
|
||||
|
||||
System.out.println("\n-- Fix: isolated Context objects prevent interference --");
|
||||
testFixedTwoContextsCoexist();
|
||||
testFixedSubsystemsDoNotInterfere();
|
||||
|
||||
System.out.printf("\n%d passed, %d failed%n", passed, failed);
|
||||
if (failed > 0) System.exit(1);
|
||||
}
|
||||
|
||||
// ── Correctness ───────────────────────────────────────────────────────────
|
||||
|
||||
static void testDefectiveBasicRead() {
|
||||
Moad0002Algorithm.resetGlobal();
|
||||
DefectiveAudioSystem audio = new DefectiveAudioSystem();
|
||||
audio.setVolume(80);
|
||||
assertEqual("defective audio: volume reads back correctly", 80, audio.getVolume());
|
||||
}
|
||||
|
||||
static void testFixedBasicRead() {
|
||||
Context ctx = new Context(70, 200, "fr");
|
||||
FixedAudioSystem audio = new FixedAudioSystem(ctx);
|
||||
FixedDisplaySystem display = new FixedDisplaySystem(ctx);
|
||||
assertEqual("fixed audio: volume", 70, audio.getVolume());
|
||||
assertEqual("fixed display: brightness", 200, display.getBrightness());
|
||||
assertEqual("fixed audio: locale", "fr", audio.getLocale());
|
||||
assertEqual("fixed display: locale", "fr", display.getLocale());
|
||||
}
|
||||
|
||||
// ── Defect ────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testDefectiveAudioLocaleLeaksToDisplay() {
|
||||
Moad0002Algorithm.resetGlobal();
|
||||
DefectiveAudioSystem audio = new DefectiveAudioSystem();
|
||||
DefectiveDisplaySystem display = new DefectiveDisplaySystem();
|
||||
|
||||
// AudioSystem configures locale — conceptually an audio concern (TTS language)
|
||||
audio.setLocale("ja");
|
||||
|
||||
// DEFECT: DisplaySystem reads the same global — sees audio's mutation
|
||||
// This is the intertangle: two subsystems that should be independent
|
||||
// are coupled through a shared mutable state object.
|
||||
String displayLocale = display.getLocale();
|
||||
assertEqual("defective: audio locale write leaks into display", "ja", displayLocale);
|
||||
}
|
||||
|
||||
static void testDefectiveTwoContextsTrample() {
|
||||
Moad0002Algorithm.resetGlobal();
|
||||
// Two independent "sessions" each configure their own audio subsystem.
|
||||
DefectiveAudioSystem session1 = new DefectiveAudioSystem();
|
||||
DefectiveAudioSystem session2 = new DefectiveAudioSystem();
|
||||
|
||||
session1.setVolume(30); // session 1 sets volume to 30
|
||||
session2.setVolume(90); // session 2 sets volume to 90 — TRAMPLES session 1
|
||||
|
||||
// DEFECT: session 1 now sees 90, not 30 — its configuration was silently destroyed.
|
||||
// Two contexts cannot coexist when they share a global state object.
|
||||
int session1Volume = session1.getVolume();
|
||||
assertEqual("defective: session2 write tramples session1 volume", 90, session1Volume);
|
||||
}
|
||||
|
||||
// ── Fix ───────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testFixedTwoContextsCoexist() {
|
||||
Context ctx1 = new Context(30, 150, "en");
|
||||
Context ctx2 = new Context(90, 200, "ja");
|
||||
|
||||
FixedAudioSystem s1 = new FixedAudioSystem(ctx1);
|
||||
FixedAudioSystem s2 = new FixedAudioSystem(ctx2);
|
||||
|
||||
// FIX: both values coexist — ctx1 and ctx2 are independent objects
|
||||
assertEqual("fixed: session1 volume preserved after session2 created", 30, s1.getVolume());
|
||||
assertEqual("fixed: session2 volume independent of session1", 90, s2.getVolume());
|
||||
assertEqual("fixed: session1 locale preserved", "en", s1.getLocale());
|
||||
assertEqual("fixed: session2 locale independent", "ja", s2.getLocale());
|
||||
}
|
||||
|
||||
static void testFixedSubsystemsDoNotInterfere() {
|
||||
Context audioCtx = new Context(75, 0, "de");
|
||||
Context displayCtx = new Context(0, 180, "fr");
|
||||
|
||||
FixedAudioSystem audio = new FixedAudioSystem(audioCtx);
|
||||
FixedDisplaySystem display = new FixedDisplaySystem(displayCtx);
|
||||
|
||||
// FIX: each subsystem reads only its own context — no cross-contamination
|
||||
assertEqual("fixed: audio locale unaffected by display context", "de", audio.getLocale());
|
||||
assertEqual("fixed: display locale unaffected by audio context", "fr", display.getLocale());
|
||||
assertEqual("fixed: display brightness from own context", 180, display.getBrightness());
|
||||
assertEqual("fixed: audio volume from own context", 75, audio.getVolume());
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
static void assertEqual(String label, int expected, int actual) {
|
||||
if (expected == actual) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — expected %d, got %d%n", label, expected, actual);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
static void assertEqual(String label, String expected, String actual) {
|
||||
if (expected == null ? actual == null : expected.equals(actual)) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — expected '%s', got '%s'%n", label, expected, actual);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
142
tests/unit/Moad0003UnitTest.java
Normal file
142
tests/unit/Moad0003UnitTest.java
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
package unit;
|
||||
|
||||
import support.Moad0003Algorithm;
|
||||
|
||||
/**
|
||||
* Unit tests for MOAD-0003: A Leaked Context.
|
||||
*
|
||||
* Proves from first principles:
|
||||
* 1. Defective: ThreadLocal set in request R1 remains accessible after R1
|
||||
* completes — the next request R2 (which never calls set()) inherits R1's
|
||||
* identity. This is the thread-pool leak: the thread is reused, but the
|
||||
* ThreadLocal is not cleared between units of work.
|
||||
* 2. Fixed: ThreadLocal.remove() in a finally block ensures R2 sees null —
|
||||
* each request starts from a clean slate.
|
||||
* 3. Both correctly return the identity when explicitly set.
|
||||
* 4. Partial-set scenario: R2 sets its own identity and removes it correctly
|
||||
* even when R1 failed to clean up in the defective variant.
|
||||
*
|
||||
* No build tool required. Compile and run:
|
||||
*
|
||||
* cd tests
|
||||
* java -m jdk.compiler/com.sun.tools.javac.Main -cp . \
|
||||
* support/Moad0003Algorithm.java unit/Moad0003UnitTest.java
|
||||
* java -cp . unit.Moad0003UnitTest
|
||||
*/
|
||||
public class Moad0003UnitTest {
|
||||
|
||||
private static int passed = 0;
|
||||
private static int failed = 0;
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Moad0003UnitTest (A Leaked Context) ===\n");
|
||||
|
||||
System.out.println("-- Correctness: identity returned correctly when set --");
|
||||
testDefectiveReturnsSetIdentity();
|
||||
testFixedReturnsSetIdentity();
|
||||
|
||||
System.out.println("\n-- Defect: stale identity leaks to anonymous next request --");
|
||||
testDefectiveLeaksToAnonymousRequest();
|
||||
testDefectiveLeaksAcrossMultipleRequests();
|
||||
|
||||
System.out.println("\n-- Fix: ThreadLocal.remove() cleans up before next request --");
|
||||
testFixedAnonymousRequestSeesNull();
|
||||
testFixedMultipleRequestsClean();
|
||||
|
||||
System.out.printf("\n%d passed, %d failed%n", passed, failed);
|
||||
if (failed > 0) System.exit(1);
|
||||
}
|
||||
|
||||
// ── Correctness ───────────────────────────────────────────────────────────
|
||||
|
||||
static void testDefectiveReturnsSetIdentity() {
|
||||
Moad0003Algorithm.reset();
|
||||
String result = Moad0003Algorithm.handleDefective("alice");
|
||||
assertEqual("defective: returns set identity 'alice'", "alice", result);
|
||||
Moad0003Algorithm.reset(); // clean up after defective handler
|
||||
}
|
||||
|
||||
static void testFixedReturnsSetIdentity() {
|
||||
Moad0003Algorithm.reset();
|
||||
String result = Moad0003Algorithm.handleFixed("bob");
|
||||
assertEqual("fixed: returns set identity 'bob'", "bob", result);
|
||||
// handleFixed removes ThreadLocal automatically
|
||||
}
|
||||
|
||||
// ── Defect ────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testDefectiveLeaksToAnonymousRequest() {
|
||||
Moad0003Algorithm.reset();
|
||||
|
||||
// Request R1: authenticated as "alice"
|
||||
Moad0003Algorithm.handleDefective("alice");
|
||||
// R1 completes but DOES NOT call remove() — ThreadLocal still holds "alice"
|
||||
|
||||
// Request R2: anonymous — never calls set()
|
||||
// Simulates the same pooled thread handling the next request.
|
||||
String leaked = Moad0003Algorithm.handleDefectiveAnonymous();
|
||||
|
||||
// DEFECT: R2 sees "alice" even though it never set any identity.
|
||||
// A CSRF-style confusion: R2 could perform privileged operations as "alice".
|
||||
assertEqual("defective: anonymous request leaks alice's identity", "alice", leaked);
|
||||
|
||||
Moad0003Algorithm.reset();
|
||||
}
|
||||
|
||||
static void testDefectiveLeaksAcrossMultipleRequests() {
|
||||
Moad0003Algorithm.reset();
|
||||
|
||||
// R1 sets identity, completes without cleanup
|
||||
Moad0003Algorithm.handleDefective("carol");
|
||||
// R2 anonymous — sees leaked "carol"
|
||||
String leak1 = Moad0003Algorithm.handleDefectiveAnonymous();
|
||||
// R3 anonymous — still sees "carol" (ThreadLocal persists until explicitly removed)
|
||||
String leak2 = Moad0003Algorithm.handleDefectiveAnonymous();
|
||||
|
||||
assertEqual("defective: first anonymous request leaks 'carol'", "carol", leak1);
|
||||
assertEqual("defective: second anonymous request still leaks 'carol'", "carol", leak2);
|
||||
|
||||
Moad0003Algorithm.reset();
|
||||
}
|
||||
|
||||
// ── Fix ───────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testFixedAnonymousRequestSeesNull() {
|
||||
Moad0003Algorithm.reset();
|
||||
|
||||
// R1: authenticated — handleFixed removes ThreadLocal in finally
|
||||
Moad0003Algorithm.handleFixed("alice");
|
||||
// ThreadLocal is now null — handleFixed called remove() before returning
|
||||
|
||||
// R2: anonymous — sees null (clean slate)
|
||||
String identity = Moad0003Algorithm.handleFixedAnonymous();
|
||||
assertEqual("fixed: anonymous request after fixed handler sees null", null, identity);
|
||||
}
|
||||
|
||||
static void testFixedMultipleRequestsClean() {
|
||||
Moad0003Algorithm.reset();
|
||||
|
||||
// Simulate 3 requests on the same pooled thread, alternating auth/anon
|
||||
String r1 = Moad0003Algorithm.handleFixed("alice"); // auth
|
||||
String r2 = Moad0003Algorithm.handleFixedAnonymous(); // anon — must be null
|
||||
String r3 = Moad0003Algorithm.handleFixed("bob"); // auth
|
||||
String r4 = Moad0003Algorithm.handleFixedAnonymous(); // anon — must be null
|
||||
|
||||
assertEqual("fixed: R1 returns 'alice'", "alice", r1);
|
||||
assertEqual("fixed: R2 anonymous sees null (not alice)", null, r2);
|
||||
assertEqual("fixed: R3 returns 'bob'", "bob", r3);
|
||||
assertEqual("fixed: R4 anonymous sees null (not bob)", null, r4);
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
static void assertEqual(String label, String expected, String actual) {
|
||||
if (expected == null ? actual == null : expected.equals(actual)) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — expected '%s', got '%s'%n", label, expected, actual);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
174
tests/unit/Moad0004UnitTest.java
Normal file
174
tests/unit/Moad0004UnitTest.java
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
package unit;
|
||||
|
||||
import support.Moad0004Algorithm;
|
||||
import support.Moad0004Algorithm.Result;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Unit tests for MOAD-0004: A Logged Secret.
|
||||
*
|
||||
* Proves from first principles:
|
||||
* 1. Defective: Authorization and Cookie headers appear verbatim in the log line —
|
||||
* bearer token and session cookie exposed in cleartext.
|
||||
* 2. Defective: leak count equals the number of credential headers present.
|
||||
* 3. Fixed: credential header values are replaced with [REDACTED] —
|
||||
* log line contains header names (for debugging) but not values.
|
||||
* 4. Fixed: non-credential headers (Host, Content-Type) pass through unchanged.
|
||||
* 5. Both handle empty header maps without error.
|
||||
*
|
||||
* No build tool required. Compile and run:
|
||||
*
|
||||
* cd tests
|
||||
* java -m jdk.compiler/com.sun.tools.javac.Main -cp . \
|
||||
* support/Moad0004Algorithm.java unit/Moad0004UnitTest.java
|
||||
* java -cp . unit.Moad0004UnitTest
|
||||
*/
|
||||
public class Moad0004UnitTest {
|
||||
|
||||
private static int passed = 0;
|
||||
private static int failed = 0;
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Moad0004UnitTest (A Logged Secret) ===\n");
|
||||
|
||||
System.out.println("-- Correctness: both handle headers without throwing --");
|
||||
testBothHandleEmpty();
|
||||
|
||||
System.out.println("\n-- Defect: credential headers appear in cleartext log --");
|
||||
testDefectiveAuthorizationLeaks();
|
||||
testDefectiveCookieLeaks();
|
||||
testDefectiveCountsAllCredentialHeaders();
|
||||
testDefectiveNonCredentialHeadersPresent();
|
||||
|
||||
System.out.println("\n-- Fix: credential headers redacted, non-credential headers intact --");
|
||||
testFixedAuthorizationRedacted();
|
||||
testFixedCookieRedacted();
|
||||
testFixedNonCredentialHeadersPassThrough();
|
||||
testFixedZeroLeakCount();
|
||||
|
||||
System.out.printf("\n%d passed, %d failed%n", passed, failed);
|
||||
if (failed > 0) System.exit(1);
|
||||
}
|
||||
|
||||
// ── Correctness ───────────────────────────────────────────────────────────
|
||||
|
||||
static void testBothHandleEmpty() {
|
||||
Map<String, String> empty = Map.of();
|
||||
Result def = Moad0004Algorithm.logDefective(empty);
|
||||
Result fix = Moad0004Algorithm.logFixed(empty);
|
||||
assertEqual("defective: empty headers, zero leaks", 0, def.credentialLeakCount);
|
||||
assertEqual("fixed: empty headers, zero leaks", 0, fix.credentialLeakCount);
|
||||
}
|
||||
|
||||
// ── Defect ────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testDefectiveAuthorizationLeaks() {
|
||||
Map<String, String> headers = Moad0004Algorithm.sampleHeaders();
|
||||
Result result = Moad0004Algorithm.logDefective(headers);
|
||||
|
||||
// DEFECT: bearer token appears in the log line verbatim
|
||||
assertContains("defective: Authorization value in log (bearer token exposed)",
|
||||
result.logLine, "eyJhbGciOiJSUzI1NiJ9.secret.token");
|
||||
}
|
||||
|
||||
static void testDefectiveCookieLeaks() {
|
||||
Map<String, String> headers = Moad0004Algorithm.sampleHeaders();
|
||||
Result result = Moad0004Algorithm.logDefective(headers);
|
||||
|
||||
// DEFECT: session cookie and CSRF token appear in the log line
|
||||
assertContains("defective: Cookie value in log (session cookie exposed)",
|
||||
result.logLine, "session=abc123def456");
|
||||
}
|
||||
|
||||
static void testDefectiveCountsAllCredentialHeaders() {
|
||||
Map<String, String> headers = Moad0004Algorithm.sampleHeaders();
|
||||
Result result = Moad0004Algorithm.logDefective(headers);
|
||||
|
||||
// sampleHeaders() contains: Authorization, Cookie, X-Api-Key = 3 credential headers
|
||||
assertEqual("defective: counts 3 credential header leaks", 3, result.credentialLeakCount);
|
||||
}
|
||||
|
||||
static void testDefectiveNonCredentialHeadersPresent() {
|
||||
Map<String, String> headers = Moad0004Algorithm.sampleHeaders();
|
||||
Result result = Moad0004Algorithm.logDefective(headers);
|
||||
|
||||
// Non-credential headers should appear (correct behavior for logging)
|
||||
assertContains("defective: Host header present in log", result.logLine, "api.example.com");
|
||||
assertContains("defective: X-Request-Id present in log", result.logLine, "req-7f3a9c");
|
||||
}
|
||||
|
||||
// ── Fix ───────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testFixedAuthorizationRedacted() {
|
||||
Map<String, String> headers = Moad0004Algorithm.sampleHeaders();
|
||||
Result result = Moad0004Algorithm.logFixed(headers);
|
||||
|
||||
// FIX: bearer token must NOT appear in the log line
|
||||
assertNotContains("fixed: bearer token NOT in log",
|
||||
result.logLine, "eyJhbGciOiJSUzI1NiJ9.secret.token");
|
||||
|
||||
// The header name should still appear (useful for debugging)
|
||||
assertContains("fixed: Authorization header name present (redacted value)",
|
||||
result.logLine, "Authorization");
|
||||
}
|
||||
|
||||
static void testFixedCookieRedacted() {
|
||||
Map<String, String> headers = Moad0004Algorithm.sampleHeaders();
|
||||
Result result = Moad0004Algorithm.logFixed(headers);
|
||||
|
||||
// FIX: session cookie must NOT appear
|
||||
assertNotContains("fixed: session cookie NOT in log", result.logLine, "session=abc123def456");
|
||||
|
||||
// The header name should still appear
|
||||
assertContains("fixed: Cookie header name present", result.logLine, "Cookie");
|
||||
}
|
||||
|
||||
static void testFixedNonCredentialHeadersPassThrough() {
|
||||
Map<String, String> headers = Moad0004Algorithm.sampleHeaders();
|
||||
Result result = Moad0004Algorithm.logFixed(headers);
|
||||
|
||||
// Non-credential headers must pass through unchanged
|
||||
assertContains("fixed: Host value passes through", result.logLine, "api.example.com");
|
||||
assertContains("fixed: X-Request-Id passes through", result.logLine, "req-7f3a9c");
|
||||
assertContains("fixed: Content-Type passes through", result.logLine, "application/json");
|
||||
}
|
||||
|
||||
static void testFixedZeroLeakCount() {
|
||||
Map<String, String> headers = Moad0004Algorithm.sampleHeaders();
|
||||
Result result = Moad0004Algorithm.logFixed(headers);
|
||||
assertEqual("fixed: zero credential leaks", 0, result.credentialLeakCount);
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
static void assertEqual(String label, int expected, int actual) {
|
||||
if (expected == actual) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — expected %d, got %d%n", label, expected, actual);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
static void assertContains(String label, String haystack, String needle) {
|
||||
if (haystack.contains(needle)) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — '%s' not found in: %s%n", label, needle, haystack);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
static void assertNotContains(String label, String haystack, String needle) {
|
||||
if (!haystack.contains(needle)) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — '%s' should NOT be in: %s%n", label, needle, haystack);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
183
tests/unit/Moad0005UnitTest.java
Normal file
183
tests/unit/Moad0005UnitTest.java
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
package unit;
|
||||
|
||||
import support.Moad0005Algorithm;
|
||||
import support.Moad0005Algorithm.DefectiveCache;
|
||||
import support.Moad0005Algorithm.FixedCache;
|
||||
|
||||
/**
|
||||
* Unit tests for MOAD-0005: CWE-362 — A Thundering Herd.
|
||||
*
|
||||
* Proves from first principles:
|
||||
* 1. Defective: N callers all seeing a null cache entry all invoke
|
||||
* expensiveCompute() — N computes instead of 1. This is the herd.
|
||||
* 2. Fixed: ConcurrentHashMap.computeIfAbsent() ensures exactly 1 compute
|
||||
* regardless of how many callers miss simultaneously.
|
||||
* 3. Both return the same (correct) value for any key.
|
||||
* 4. Sequential access on a warm cache: both call compute exactly once.
|
||||
*
|
||||
* The single-threaded simulation models the race window: all callers read
|
||||
* before any caller's put() completes. Integration tests use real threads.
|
||||
*
|
||||
* No build tool required. Compile and run:
|
||||
*
|
||||
* cd tests
|
||||
* java -m jdk.compiler/com.sun.tools.javac.Main -cp . \
|
||||
* support/Moad0005Algorithm.java unit/Moad0005UnitTest.java
|
||||
* java -cp . unit.Moad0005UnitTest
|
||||
*/
|
||||
public class Moad0005UnitTest {
|
||||
|
||||
private static int passed = 0;
|
||||
private static int failed = 0;
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Moad0005UnitTest (A Thundering Herd) ===\n");
|
||||
|
||||
System.out.println("-- Correctness: both caches return correct value --");
|
||||
testDefectiveReturnsCorrectValue();
|
||||
testFixedReturnsCorrectValue();
|
||||
testBothReturnSameValue();
|
||||
|
||||
System.out.println("\n-- Defect: N concurrent misses produce N computes --");
|
||||
testDefectiveConcurrentMissN10();
|
||||
testDefectiveConcurrentMissN50();
|
||||
testDefectiveComputeCountScalesWithCallers();
|
||||
|
||||
System.out.println("\n-- Fix: N concurrent misses produce exactly 1 compute --");
|
||||
testFixedConcurrentMissN10();
|
||||
testFixedConcurrentMissN50();
|
||||
testFixedComputeCountAlwaysOne();
|
||||
|
||||
System.out.println("\n-- Warm cache: both compute exactly once sequentially --");
|
||||
testDefectiveWarmCacheNoRecompute();
|
||||
testFixedWarmCacheNoRecompute();
|
||||
|
||||
System.out.printf("\n%d passed, %d failed%n", passed, failed);
|
||||
if (failed > 0) System.exit(1);
|
||||
}
|
||||
|
||||
// ── Correctness ───────────────────────────────────────────────────────────
|
||||
|
||||
static void testDefectiveReturnsCorrectValue() {
|
||||
Moad0005Algorithm.resetCounter();
|
||||
DefectiveCache cache = new DefectiveCache();
|
||||
String val = cache.getOrCompute("foo");
|
||||
assertEqual("defective: returns 'value-for-foo'", "value-for-foo", val);
|
||||
}
|
||||
|
||||
static void testFixedReturnsCorrectValue() {
|
||||
Moad0005Algorithm.resetCounter();
|
||||
FixedCache cache = new FixedCache();
|
||||
String val = cache.getOrCompute("foo");
|
||||
assertEqual("fixed: returns 'value-for-foo'", "value-for-foo", val);
|
||||
}
|
||||
|
||||
static void testBothReturnSameValue() {
|
||||
Moad0005Algorithm.resetCounter();
|
||||
DefectiveCache def = new DefectiveCache();
|
||||
FixedCache fix = new FixedCache();
|
||||
String dVal = def.getOrCompute("bar");
|
||||
Moad0005Algorithm.resetCounter();
|
||||
String fVal = fix.getOrCompute("bar");
|
||||
assertEqual("both: return same value for 'bar'", dVal, fVal);
|
||||
}
|
||||
|
||||
// ── Defect ────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testDefectiveConcurrentMissN10() {
|
||||
int computes = Moad0005Algorithm.simulateDefectiveConcurrentMiss(10, "key");
|
||||
// DEFECT: all 10 callers see null simultaneously → 10 computes
|
||||
assertEqual("defective: 10 concurrent misses cause 10 computes (herd)", 10, computes);
|
||||
}
|
||||
|
||||
static void testDefectiveConcurrentMissN50() {
|
||||
int computes = Moad0005Algorithm.simulateDefectiveConcurrentMiss(50, "key");
|
||||
// DEFECT: 50 concurrent misses → 50 computes
|
||||
assertEqual("defective: 50 concurrent misses cause 50 computes (herd)", 50, computes);
|
||||
}
|
||||
|
||||
static void testDefectiveComputeCountScalesWithCallers() {
|
||||
int c10 = Moad0005Algorithm.simulateDefectiveConcurrentMiss(10, "key");
|
||||
int c20 = Moad0005Algorithm.simulateDefectiveConcurrentMiss(20, "key");
|
||||
// DEFECT: compute count scales linearly with caller count (N wasted computes)
|
||||
assertTrue("defective: doubling callers doubles computes",
|
||||
c20 == 2 * c10, "c10=" + c10 + " c20=" + c20);
|
||||
}
|
||||
|
||||
// ── Fix ───────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testFixedConcurrentMissN10() {
|
||||
int computes = Moad0005Algorithm.simulateFixedConcurrentMiss(10, "key");
|
||||
// FIX: computeIfAbsent — exactly 1 compute regardless of caller count
|
||||
assertEqual("fixed: 10 concurrent misses cause exactly 1 compute", 1, computes);
|
||||
}
|
||||
|
||||
static void testFixedConcurrentMissN50() {
|
||||
int computes = Moad0005Algorithm.simulateFixedConcurrentMiss(50, "key");
|
||||
assertEqual("fixed: 50 concurrent misses cause exactly 1 compute", 1, computes);
|
||||
}
|
||||
|
||||
static void testFixedComputeCountAlwaysOne() {
|
||||
int c10 = Moad0005Algorithm.simulateFixedConcurrentMiss(10, "key");
|
||||
int c100 = Moad0005Algorithm.simulateFixedConcurrentMiss(100, "key");
|
||||
// FIX: compute count does not scale — always 1
|
||||
assertEqual("fixed: 10 callers — 1 compute", 1, c10);
|
||||
assertEqual("fixed: 100 callers — 1 compute", 1, c100);
|
||||
}
|
||||
|
||||
// ── Warm cache ────────────────────────────────────────────────────────────
|
||||
|
||||
static void testDefectiveWarmCacheNoRecompute() {
|
||||
Moad0005Algorithm.resetCounter();
|
||||
DefectiveCache cache = new DefectiveCache();
|
||||
cache.getOrCompute("warm"); // cold miss — 1 compute
|
||||
int afterFirst = Moad0005Algorithm.COMPUTE_CALLS.get();
|
||||
cache.getOrCompute("warm"); // warm hit — 0 compute
|
||||
int afterSecond = Moad0005Algorithm.COMPUTE_CALLS.get();
|
||||
assertEqual("defective: first call computes once", 1, afterFirst);
|
||||
assertEqual("defective: second sequential call hits cache", 1, afterSecond);
|
||||
}
|
||||
|
||||
static void testFixedWarmCacheNoRecompute() {
|
||||
Moad0005Algorithm.resetCounter();
|
||||
FixedCache cache = new FixedCache();
|
||||
cache.getOrCompute("warm"); // cold miss — 1 compute
|
||||
int afterFirst = Moad0005Algorithm.COMPUTE_CALLS.get();
|
||||
cache.getOrCompute("warm"); // warm hit — 0 compute
|
||||
int afterSecond = Moad0005Algorithm.COMPUTE_CALLS.get();
|
||||
assertEqual("fixed: first call computes once", 1, afterFirst);
|
||||
assertEqual("fixed: second sequential call hits cache", 1, afterSecond);
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
static void assertEqual(String label, int expected, int actual) {
|
||||
if (expected == actual) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — expected %d, got %d%n", label, expected, actual);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
static void assertEqual(String label, String expected, String actual) {
|
||||
if (expected == null ? actual == null : expected.equals(actual)) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — expected '%s', got '%s'%n", label, expected, actual);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
static void assertTrue(String label, boolean condition, String detail) {
|
||||
if (condition) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — %s%n", label, detail);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
238
tests/unit/Moad0006UnitTest.java
Normal file
238
tests/unit/Moad0006UnitTest.java
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
package unit;
|
||||
|
||||
import support.Moad0006Algorithm;
|
||||
import support.Moad0006Algorithm.DefectiveCredentialStore;
|
||||
import support.Moad0006Algorithm.FixedCredentialStore;
|
||||
|
||||
/**
|
||||
* Unit tests for MOAD-0006: CWE-257 — A Glass Safe.
|
||||
*
|
||||
* Proves from first principles:
|
||||
* 1. Defective: Base64 encoding is reversible — stored credential decodes
|
||||
* to the original password. An attacker with DB read access extracts
|
||||
* every password in plaintext.
|
||||
* 2. Defective: stored value is deterministic — same password always produces
|
||||
* same encoded string, enabling rainbow table attacks.
|
||||
* 3. Fixed: SHA-256 + salt is one-way — the stored hash cannot be decoded
|
||||
* to recover the original password.
|
||||
* 4. Fixed: salt randomization — same password stored twice produces
|
||||
* different hash values (defeats rainbow tables).
|
||||
* 5. Both correctly verify a correct password and reject an incorrect one.
|
||||
*
|
||||
* No build tool required. Compile and run:
|
||||
*
|
||||
* cd tests
|
||||
* java -m jdk.compiler/com.sun.tools.javac.Main -cp . \
|
||||
* support/Moad0006Algorithm.java unit/Moad0006UnitTest.java
|
||||
* java -cp . unit.Moad0006UnitTest
|
||||
*/
|
||||
public class Moad0006UnitTest {
|
||||
|
||||
private static int passed = 0;
|
||||
private static int failed = 0;
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Moad0006UnitTest (A Glass Safe) ===\n");
|
||||
|
||||
System.out.println("-- Correctness: verification works for both --");
|
||||
testDefectiveVerifyCorrect();
|
||||
testDefectiveRejectWrong();
|
||||
testFixedVerifyCorrect();
|
||||
testFixedRejectWrong();
|
||||
|
||||
System.out.println("\n-- Defect: stored value is reversible (glass safe opens) --");
|
||||
testDefectivePasswordExtractable();
|
||||
testDefectiveDeterministicStorage();
|
||||
testDefectiveMultipleUsersExtractable();
|
||||
|
||||
System.out.println("\n-- Fix: stored value is one-way (hash cannot be reversed) --");
|
||||
testFixedPasswordNotExtractable();
|
||||
testFixedSaltRandomization();
|
||||
testFixedHashNotEqualToPassword();
|
||||
|
||||
System.out.printf("\n%d passed, %d failed%n", passed, failed);
|
||||
if (failed > 0) System.exit(1);
|
||||
}
|
||||
|
||||
// ── Correctness ───────────────────────────────────────────────────────────
|
||||
|
||||
static void testDefectiveVerifyCorrect() {
|
||||
DefectiveCredentialStore store = new DefectiveCredentialStore();
|
||||
store.storePassword("alice", "hunter2");
|
||||
assertTrue("defective: correct password verified", store.verify("alice", "hunter2"));
|
||||
}
|
||||
|
||||
static void testDefectiveRejectWrong() {
|
||||
DefectiveCredentialStore store = new DefectiveCredentialStore();
|
||||
store.storePassword("alice", "hunter2");
|
||||
assertFalse("defective: wrong password rejected", store.verify("alice", "wrongpass"));
|
||||
}
|
||||
|
||||
static void testFixedVerifyCorrect() {
|
||||
FixedCredentialStore store = new FixedCredentialStore();
|
||||
store.storePassword("bob", "correct-horse-battery-staple");
|
||||
assertTrue("fixed: correct password verified", store.verify("bob", "correct-horse-battery-staple"));
|
||||
}
|
||||
|
||||
static void testFixedRejectWrong() {
|
||||
FixedCredentialStore store = new FixedCredentialStore();
|
||||
store.storePassword("bob", "correct-horse-battery-staple");
|
||||
assertFalse("fixed: wrong password rejected", store.verify("bob", "wrong-answer"));
|
||||
}
|
||||
|
||||
// ── Defect ────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testDefectivePasswordExtractable() {
|
||||
DefectiveCredentialStore store = new DefectiveCredentialStore();
|
||||
store.storePassword("alice", "s3cr3t!");
|
||||
|
||||
// DEFECT: attacker with DB access calls extractPassword()
|
||||
String extracted = store.extractPassword("alice");
|
||||
assertEqual("defective: original password extracted from DB (glass safe opens)",
|
||||
"s3cr3t!", extracted);
|
||||
}
|
||||
|
||||
static void testDefectiveDeterministicStorage() {
|
||||
DefectiveCredentialStore store1 = new DefectiveCredentialStore();
|
||||
DefectiveCredentialStore store2 = new DefectiveCredentialStore();
|
||||
store1.storePassword("u1", "password123");
|
||||
store2.storePassword("u2", "password123");
|
||||
|
||||
// DEFECT: same password → same stored value → rainbow table attack works
|
||||
String raw1 = store1.rawStored("u1");
|
||||
String raw2 = store2.rawStored("u2");
|
||||
assertEqual("defective: same password produces identical stored values (no salt)",
|
||||
raw1, raw2);
|
||||
}
|
||||
|
||||
static void testDefectiveMultipleUsersExtractable() {
|
||||
DefectiveCredentialStore store = new DefectiveCredentialStore();
|
||||
store.storePassword("admin", "admin123");
|
||||
store.storePassword("finance", "Q4RevenueReport!");
|
||||
|
||||
// DEFECT: all users' passwords extractable from one DB dump
|
||||
assertEqual("defective: admin password extracted", "admin123", store.extractPassword("admin"));
|
||||
assertEqual("defective: finance password extracted", "Q4RevenueReport!", store.extractPassword("finance"));
|
||||
}
|
||||
|
||||
// ── Fix ───────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testFixedPasswordNotExtractable() {
|
||||
FixedCredentialStore store = new FixedCredentialStore();
|
||||
store.storePassword("alice", "s3cr3t!");
|
||||
|
||||
// FIX: raw hash is stored — cannot reverse to original password
|
||||
byte[] hash = store.rawHash("alice");
|
||||
byte[] salt = store.rawSalt("alice");
|
||||
|
||||
assertNotNull("fixed: hash exists in store", hash);
|
||||
assertNotNull("fixed: salt exists in store", salt);
|
||||
|
||||
// Verify hash does not equal password bytes
|
||||
String hashHex = bytesToHex(hash);
|
||||
assertNotContains("fixed: hash does not contain plaintext password",
|
||||
hashHex, "s3cr3t!");
|
||||
|
||||
// FIX: no extractPassword() method exists on FixedCredentialStore
|
||||
// (This is verified by the fact that FixedCredentialStore does not have that method —
|
||||
// the API surface itself proves the fix.)
|
||||
assertTrue("fixed: FixedCredentialStore has no extractPassword() method (API enforces fix)",
|
||||
!hasExtractMethod(store));
|
||||
}
|
||||
|
||||
static void testFixedSaltRandomization() {
|
||||
// Store the same password for two users
|
||||
FixedCredentialStore store = new FixedCredentialStore();
|
||||
store.storePassword("u1", "shared-password");
|
||||
store.storePassword("u2", "shared-password");
|
||||
|
||||
byte[] hash1 = store.rawHash("u1");
|
||||
byte[] hash2 = store.rawHash("u2");
|
||||
byte[] salt1 = store.rawSalt("u1");
|
||||
byte[] salt2 = store.rawSalt("u2");
|
||||
|
||||
// FIX: different salts → different hashes — rainbow table attack fails
|
||||
assertNotEqualBytes("fixed: same password produces different hashes (random salt)",
|
||||
hash1, hash2);
|
||||
assertNotEqualBytes("fixed: unique salt per user", salt1, salt2);
|
||||
}
|
||||
|
||||
static void testFixedHashNotEqualToPassword() {
|
||||
FixedCredentialStore store = new FixedCredentialStore();
|
||||
String password = "mypassword";
|
||||
store.storePassword("user", password);
|
||||
byte[] hash = store.rawHash("user");
|
||||
// Hash bytes should not equal password bytes
|
||||
byte[] passwordBytes = password.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
assertFalseBytes("fixed: stored hash does not equal password bytes", hash, passwordBytes);
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
static void assertEqual(String label, String expected, String actual) {
|
||||
if (expected == null ? actual == null : expected.equals(actual)) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — expected '%s', got '%s'%n", label, expected, actual);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
static void assertTrue(String label, boolean condition) {
|
||||
if (condition) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s%n", label);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
static void assertFalse(String label, boolean condition) {
|
||||
assertTrue(label, !condition);
|
||||
}
|
||||
|
||||
static void assertNotNull(String label, Object obj) {
|
||||
assertTrue(label + " (not null)", obj != null);
|
||||
}
|
||||
|
||||
static void assertNotContains(String label, String haystack, String needle) {
|
||||
if (!haystack.contains(needle)) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — found '%s' in '%s'%n", label, needle, haystack);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
static void assertNotEqualBytes(String label, byte[] a, byte[] b) {
|
||||
if (!java.util.Arrays.equals(a, b)) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — byte arrays are equal when they should differ%n", label);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
static void assertFalseBytes(String label, byte[] a, byte[] b) {
|
||||
assertNotEqualBytes(label, a, b);
|
||||
}
|
||||
|
||||
static boolean hasExtractMethod(FixedCredentialStore store) {
|
||||
try {
|
||||
store.getClass().getMethod("extractPassword", String.class);
|
||||
return true;
|
||||
} catch (NoSuchMethodException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) sb.append(String.format("%02x", b));
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
207
tests/unit/Moad0007UnitTest.java
Normal file
207
tests/unit/Moad0007UnitTest.java
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
package unit;
|
||||
|
||||
import support.Moad0007Algorithm;
|
||||
import support.Moad0007Algorithm.SpatialObject;
|
||||
import support.Moad0007Algorithm.Result;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Unit tests for MOAD-0007: CWE-407 — A Flatland Defect.
|
||||
*
|
||||
* Proves from first principles:
|
||||
* 1. Both defective and fixed produce identical query results (same hits).
|
||||
* 2. Defective: queryDefective() visits all N objects for every query —
|
||||
* probeCount == N regardless of how many objects match.
|
||||
* 3. Fixed: queryFixed() visits O(log N + k) objects — probeCount grows as
|
||||
* log₂(N) for empty queries and log₂(N)+k for queries returning k hits.
|
||||
* 4. Doubling N doubles defective probes; fixed probes grow only by 1 (log step).
|
||||
* 5. Empty-range query: defective still visits all N; fixed visits O(log N).
|
||||
*
|
||||
* No build tool required. Compile and run:
|
||||
*
|
||||
* cd tests
|
||||
* java -m jdk.compiler/com.sun.tools.javac.Main -cp . \
|
||||
* support/Moad0007Algorithm.java unit/Moad0007UnitTest.java
|
||||
* java -cp . unit.Moad0007UnitTest
|
||||
*/
|
||||
public class Moad0007UnitTest {
|
||||
|
||||
private static int passed = 0;
|
||||
private static int failed = 0;
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Moad0007UnitTest (A Flatland Defect) ===\n");
|
||||
|
||||
System.out.println("-- Correctness: same query results from both implementations --");
|
||||
testSmallSceneAllMatch();
|
||||
testSmallScenePartialMatch();
|
||||
testSmallSceneNoMatch();
|
||||
|
||||
System.out.println("\n-- Defect: linear scan visits all N objects (O(N) probes) --");
|
||||
testDefectiveProbesAllN100();
|
||||
testDefectiveProbesAllN200();
|
||||
testDefectiveEmptyQueryStillVisitsAllN();
|
||||
|
||||
System.out.println("\n-- Fix: binary search visits O(log N + k) objects --");
|
||||
testFixedProbesLogN100();
|
||||
testFixedProbesLogN200();
|
||||
testFixedEmptyQueryVisitsLogN();
|
||||
|
||||
System.out.println("\n-- Complexity: doubling N doubles defective probes, barely changes fixed --");
|
||||
testDoublingNEffect();
|
||||
|
||||
System.out.printf("\n%d passed, %d failed%n", passed, failed);
|
||||
if (failed > 0) System.exit(1);
|
||||
}
|
||||
|
||||
// ── Correctness ───────────────────────────────────────────────────────────
|
||||
|
||||
static void testSmallSceneAllMatch() {
|
||||
List<SpatialObject> scene = Moad0007Algorithm.buildScene(10, 100.0);
|
||||
SpatialObject[] index = Moad0007Algorithm.buildIndex(scene);
|
||||
|
||||
Result def = Moad0007Algorithm.queryDefective(scene, 0.0, 100.0);
|
||||
Result fix = Moad0007Algorithm.queryFixed(index, 0.0, 100.0);
|
||||
|
||||
assertEqual("correctness: all-match hit counts equal", def.hits.size(), fix.hits.size());
|
||||
assertEqual("correctness: all-match returns 10 hits", 10, def.hits.size());
|
||||
}
|
||||
|
||||
static void testSmallScenePartialMatch() {
|
||||
List<SpatialObject> scene = Moad0007Algorithm.buildScene(10, 100.0);
|
||||
SpatialObject[] index = Moad0007Algorithm.buildIndex(scene);
|
||||
|
||||
// Middle third: positions [33, 66]
|
||||
Result def = Moad0007Algorithm.queryDefective(scene, 33.0, 66.0);
|
||||
Result fix = Moad0007Algorithm.queryFixed(index, 33.0, 66.0);
|
||||
|
||||
assertEqual("correctness: partial-match hit counts equal", def.hits.size(), fix.hits.size());
|
||||
assertTrue("correctness: partial match returns some hits (>0, <10)",
|
||||
def.hits.size() > 0 && def.hits.size() < 10,
|
||||
"hits=" + def.hits.size());
|
||||
}
|
||||
|
||||
static void testSmallSceneNoMatch() {
|
||||
List<SpatialObject> scene = Moad0007Algorithm.buildScene(10, 100.0);
|
||||
SpatialObject[] index = Moad0007Algorithm.buildIndex(scene);
|
||||
|
||||
// Query outside the scene bounds
|
||||
Result def = Moad0007Algorithm.queryDefective(scene, 200.0, 300.0);
|
||||
Result fix = Moad0007Algorithm.queryFixed(index, 200.0, 300.0);
|
||||
|
||||
assertEqual("correctness: no-match — both return 0 hits (defective)", 0, def.hits.size());
|
||||
assertEqual("correctness: no-match — both return 0 hits (fixed)", 0, fix.hits.size());
|
||||
}
|
||||
|
||||
// ── Defect ────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testDefectiveProbesAllN100() {
|
||||
int N = 100;
|
||||
List<SpatialObject> scene = Moad0007Algorithm.buildScene(N, 1000.0);
|
||||
// Even a no-match query visits all N
|
||||
Result result = Moad0007Algorithm.queryDefective(scene, 2000.0, 3000.0);
|
||||
assertEqual("defective N=100: probes all 100 objects (even no-match)", N, result.probeCount);
|
||||
}
|
||||
|
||||
static void testDefectiveProbesAllN200() {
|
||||
int N = 200;
|
||||
List<SpatialObject> scene = Moad0007Algorithm.buildScene(N, 1000.0);
|
||||
Result result = Moad0007Algorithm.queryDefective(scene, 2000.0, 3000.0);
|
||||
assertEqual("defective N=200: probes all 200 objects", N, result.probeCount);
|
||||
}
|
||||
|
||||
static void testDefectiveEmptyQueryStillVisitsAllN() {
|
||||
int N = 150;
|
||||
List<SpatialObject> scene = Moad0007Algorithm.buildScene(N, 500.0);
|
||||
Result result = Moad0007Algorithm.queryDefective(scene, -100.0, -50.0); // no objects here
|
||||
assertEqual("defective: empty-range query still visits all N=" + N, N, result.probeCount);
|
||||
}
|
||||
|
||||
// ── Fix ───────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testFixedProbesLogN100() {
|
||||
int N = 100;
|
||||
List<SpatialObject> scene = Moad0007Algorithm.buildScene(N, 1000.0);
|
||||
SpatialObject[] index = Moad0007Algorithm.buildIndex(scene);
|
||||
// No-match query: probes should be O(log N) = ~7 for N=100
|
||||
Result result = Moad0007Algorithm.queryFixed(index, 2000.0, 3000.0);
|
||||
// Binary search on 100 elements: at most ceil(log2(100)) = 7 steps, plus 1 for the exit check
|
||||
int expectedMax = (int)(Math.ceil(Math.log(N) / Math.log(2))) + 2;
|
||||
assertTrue("fixed N=100: no-match query probes <= " + expectedMax + " (log N)",
|
||||
result.probeCount <= expectedMax,
|
||||
"probeCount=" + result.probeCount);
|
||||
assertTrue("fixed N=100: probes much less than N",
|
||||
result.probeCount < N / 4,
|
||||
"probeCount=" + result.probeCount + " N=" + N);
|
||||
}
|
||||
|
||||
static void testFixedProbesLogN200() {
|
||||
int N = 200;
|
||||
List<SpatialObject> scene = Moad0007Algorithm.buildScene(N, 1000.0);
|
||||
SpatialObject[] index = Moad0007Algorithm.buildIndex(scene);
|
||||
Result result = Moad0007Algorithm.queryFixed(index, 2000.0, 3000.0);
|
||||
int expectedMax = (int)(Math.ceil(Math.log(N) / Math.log(2))) + 2;
|
||||
assertTrue("fixed N=200: no-match query probes <= " + expectedMax,
|
||||
result.probeCount <= expectedMax,
|
||||
"probeCount=" + result.probeCount);
|
||||
}
|
||||
|
||||
static void testFixedEmptyQueryVisitsLogN() {
|
||||
int N = 128; // power of 2 for clean log result
|
||||
List<SpatialObject> scene = Moad0007Algorithm.buildScene(N, 1000.0);
|
||||
SpatialObject[] index = Moad0007Algorithm.buildIndex(scene);
|
||||
Result result = Moad0007Algorithm.queryFixed(index, -100.0, -1.0); // below all objects
|
||||
// Binary search on 128 elements: 7 steps max (log2(128) = 7)
|
||||
assertTrue("fixed N=128: out-of-range query probes at most 9 (log N + overhead)",
|
||||
result.probeCount <= 9,
|
||||
"probeCount=" + result.probeCount);
|
||||
}
|
||||
|
||||
// ── Complexity ────────────────────────────────────────────────────────────
|
||||
|
||||
static void testDoublingNEffect() {
|
||||
// Defective: doubling N doubles probes
|
||||
int N1 = 100, N2 = 200;
|
||||
List<SpatialObject> s1 = Moad0007Algorithm.buildScene(N1, 1000.0);
|
||||
List<SpatialObject> s2 = Moad0007Algorithm.buildScene(N2, 1000.0);
|
||||
SpatialObject[] i1 = Moad0007Algorithm.buildIndex(s1);
|
||||
SpatialObject[] i2 = Moad0007Algorithm.buildIndex(s2);
|
||||
|
||||
Result def1 = Moad0007Algorithm.queryDefective(s1, 2000.0, 3000.0);
|
||||
Result def2 = Moad0007Algorithm.queryDefective(s2, 2000.0, 3000.0);
|
||||
Result fix1 = Moad0007Algorithm.queryFixed(i1, 2000.0, 3000.0);
|
||||
Result fix2 = Moad0007Algorithm.queryFixed(i2, 2000.0, 3000.0);
|
||||
|
||||
// Defective: probes double exactly with N
|
||||
assertEqual("defective: doubling N doubles probes (linear)", def1.probeCount * 2, def2.probeCount);
|
||||
|
||||
// Fixed: doubling N adds at most 1 log step
|
||||
int fixDelta = fix2.probeCount - fix1.probeCount;
|
||||
assertTrue("fixed: doubling N adds at most 1 probe (log growth)",
|
||||
fixDelta <= 1,
|
||||
"fix1.probes=" + fix1.probeCount + " fix2.probes=" + fix2.probeCount + " delta=" + fixDelta);
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
static void assertEqual(String label, int expected, int actual) {
|
||||
if (expected == actual) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — expected %d, got %d%n", label, expected, actual);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
static void assertTrue(String label, boolean condition, String detail) {
|
||||
if (condition) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — %s%n", label, detail);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
174
tests/unit/Moad0009UnitTest.java
Normal file
174
tests/unit/Moad0009UnitTest.java
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
package unit;
|
||||
|
||||
import support.Moad0009Algorithm;
|
||||
import support.Moad0009Algorithm.Event;
|
||||
import support.Moad0009Algorithm.Result;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Unit tests for MOAD-0009: A Metered Heart.
|
||||
*
|
||||
* Proves from first principles:
|
||||
* 1. Defective scheduler fires on every timer tick (N firings for N ticks)
|
||||
* even when only M events occur — N - M firings are wasted.
|
||||
* 2. Fixed event-driven handler fires exactly once per event (M firings for M events).
|
||||
* 3. Both correctly process the same set of events (same events handled).
|
||||
* 4. Defective wastes fire count grows with poll interval; fixed stays constant.
|
||||
* 5. Zero-event scenario: defective fires N times on empty queue; fixed fires 0.
|
||||
*
|
||||
* No build tool required. Compile and run:
|
||||
*
|
||||
* cd tests
|
||||
* java -m jdk.compiler/com.sun.tools.javac.Main -cp . \
|
||||
* support/Moad0009Algorithm.java unit/Moad0009UnitTest.java
|
||||
* java -cp . unit.Moad0009UnitTest
|
||||
*/
|
||||
public class Moad0009UnitTest {
|
||||
|
||||
private static int passed = 0;
|
||||
private static int failed = 0;
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Moad0009UnitTest (A Metered Heart) ===\n");
|
||||
|
||||
System.out.println("-- Correctness: both process the same events --");
|
||||
testBothProcessSameEvents();
|
||||
testBothHandleNoEvents();
|
||||
|
||||
System.out.println("\n-- Defect: timer fires N times regardless of M events --");
|
||||
testDefectiveFiresEveryTick();
|
||||
testDefectiveWastedFiresN100M3();
|
||||
testDefectiveZeroEventsStillFiresN();
|
||||
|
||||
System.out.println("\n-- Fix: fires exactly once per event --");
|
||||
testFixedFiresExactlyMTimes();
|
||||
testFixedZeroEventsZeroFirings();
|
||||
testFixedFireCountEqualsEventCount();
|
||||
|
||||
System.out.println("\n-- Comparison: defective wastes grow with poll count; fixed stays M --");
|
||||
testWastedFiresGrowWithPollCount();
|
||||
|
||||
System.out.printf("\n%d passed, %d failed%n", passed, failed);
|
||||
if (failed > 0) System.exit(1);
|
||||
}
|
||||
|
||||
// ── Correctness ───────────────────────────────────────────────────────────
|
||||
|
||||
static void testBothProcessSameEvents() {
|
||||
List<Event> events = Moad0009Algorithm.buildEvents(3, 10);
|
||||
Result def = Moad0009Algorithm.runDefectiveScheduler(20, events);
|
||||
Result fix = Moad0009Algorithm.runFixedEventDriven(events);
|
||||
|
||||
assertEqual("correctness: both process same event count", def.eventsProcessed, fix.eventsProcessed);
|
||||
assertEqual("correctness: both process all 3 events", 3, def.eventsProcessed);
|
||||
}
|
||||
|
||||
static void testBothHandleNoEvents() {
|
||||
List<Event> events = List.of();
|
||||
Result def = Moad0009Algorithm.runDefectiveScheduler(10, events);
|
||||
Result fix = Moad0009Algorithm.runFixedEventDriven(events);
|
||||
|
||||
assertEqual("correctness: both process 0 events from empty list", 0, def.eventsProcessed);
|
||||
assertEqual("correctness: fixed: 0 events processed", 0, fix.eventsProcessed);
|
||||
}
|
||||
|
||||
// ── Defect ────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testDefectiveFiresEveryTick() {
|
||||
int ticks = 50;
|
||||
List<Event> events = Moad0009Algorithm.buildEvents(2, ticks);
|
||||
Result result = Moad0009Algorithm.runDefectiveScheduler(ticks, events);
|
||||
// DEFECT: fires every tick regardless of events
|
||||
assertEqual("defective: fires exactly N=" + ticks + " times", ticks, result.firings);
|
||||
}
|
||||
|
||||
static void testDefectiveWastedFiresN100M3() {
|
||||
int ticks = 100;
|
||||
int eventCount = 3;
|
||||
List<Event> events = Moad0009Algorithm.buildEvents(eventCount, ticks);
|
||||
Result result = Moad0009Algorithm.runDefectiveScheduler(ticks, events);
|
||||
|
||||
// DEFECT: 100 firings for 3 events = 97 wasted firings (97% waste)
|
||||
int wasted = result.firings - result.eventsProcessed;
|
||||
assertEqual("defective: 97 wasted firings (100 ticks, 3 events)", 97, wasted);
|
||||
assertTrue("defective: waste ratio > 95%",
|
||||
(double) wasted / result.firings > 0.95,
|
||||
"wasted=" + wasted + " total=" + result.firings);
|
||||
}
|
||||
|
||||
static void testDefectiveZeroEventsStillFiresN() {
|
||||
int ticks = 60;
|
||||
List<Event> events = List.of();
|
||||
Result result = Moad0009Algorithm.runDefectiveScheduler(ticks, events);
|
||||
// DEFECT: no events exist, but timer still fires 60 times — pure waste
|
||||
assertEqual("defective: 0 events, fires N=" + ticks + " times (100% waste)", ticks, result.firings);
|
||||
assertEqual("defective: 0 events processed", 0, result.eventsProcessed);
|
||||
}
|
||||
|
||||
// ── Fix ───────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testFixedFiresExactlyMTimes() {
|
||||
List<Event> events = Moad0009Algorithm.buildEvents(5, 100);
|
||||
Result result = Moad0009Algorithm.runFixedEventDriven(events);
|
||||
assertEqual("fixed: fires exactly M=5 times (one per event)", 5, result.firings);
|
||||
}
|
||||
|
||||
static void testFixedZeroEventsZeroFirings() {
|
||||
List<Event> events = List.of();
|
||||
Result result = Moad0009Algorithm.runFixedEventDriven(events);
|
||||
assertEqual("fixed: 0 events → 0 firings (no wasted work)", 0, result.firings);
|
||||
}
|
||||
|
||||
static void testFixedFireCountEqualsEventCount() {
|
||||
for (int m : new int[]{1, 7, 23, 100}) {
|
||||
List<Event> events = Moad0009Algorithm.buildEvents(m, 200);
|
||||
Result result = Moad0009Algorithm.runFixedEventDriven(events);
|
||||
assertEqual("fixed: M=" + m + " events → exactly M=" + m + " firings",
|
||||
m, result.firings);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Comparison ────────────────────────────────────────────────────────────
|
||||
|
||||
static void testWastedFiresGrowWithPollCount() {
|
||||
List<Event> events = Moad0009Algorithm.buildEvents(2, 200);
|
||||
|
||||
// More ticks = more waste in defective; fixed stays at M=2
|
||||
Result def50 = Moad0009Algorithm.runDefectiveScheduler(50, events);
|
||||
Result def200 = Moad0009Algorithm.runDefectiveScheduler(200, events);
|
||||
Result fix = Moad0009Algorithm.runFixedEventDriven(events);
|
||||
|
||||
assertTrue("defective: 50 ticks → 50 firings", def50.firings == 50, "got " + def50.firings);
|
||||
assertTrue("defective: 200 ticks → 200 firings", def200.firings == 200, "got " + def200.firings);
|
||||
assertTrue("fixed: always 2 firings regardless of tick count", fix.firings == 2, "got " + fix.firings);
|
||||
|
||||
// Wasted = defective.firings - events processed
|
||||
int waste50 = def50.firings - def50.eventsProcessed;
|
||||
int waste200 = def200.firings - def200.eventsProcessed;
|
||||
assertTrue("defective: waste grows with tick count (200 > 50)",
|
||||
waste200 > waste50, "waste50=" + waste50 + " waste200=" + waste200);
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
static void assertEqual(String label, int expected, int actual) {
|
||||
if (expected == actual) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — expected %d, got %d%n", label, expected, actual);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
static void assertTrue(String label, boolean condition, String detail) {
|
||||
if (condition) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — %s%n", label, detail);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
210
tests/unit/Moad0011UnitTest.java
Normal file
210
tests/unit/Moad0011UnitTest.java
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
package unit;
|
||||
|
||||
import support.Moad0011Algorithm;
|
||||
import support.Moad0011Algorithm.Result;
|
||||
|
||||
/**
|
||||
* Unit tests for MOAD-0011: CWE-1333 — A Catastrophic Inheritance.
|
||||
*
|
||||
* Proves from first principles:
|
||||
* 1. Structural: hasCatastrophicStructure() detects nested quantifiers in
|
||||
* known-bad patterns and passes known-safe patterns.
|
||||
* 2. Behavioral (step counting): defective NFA for (a+)+b explores
|
||||
* exponentially many states on adversarial input; fixed NFA is linear.
|
||||
* 3. Growth: doubling adversarial input length more than doubles defective
|
||||
* steps (super-linear / exponential growth); fixed steps grow exactly linearly.
|
||||
* 4. Both produce identical match results on matching and non-matching inputs.
|
||||
*
|
||||
* The step-counting NFA proves the algorithmic complexity without relying on
|
||||
* wall-clock timing — suitable for deterministic unit testing. Functional tests
|
||||
* use a timed match on a moderate adversarial input to prove behavioral impact.
|
||||
*
|
||||
* No build tool required. Compile and run:
|
||||
*
|
||||
* cd tests
|
||||
* java -m jdk.compiler/com.sun.tools.javac.Main -cp . \
|
||||
* support/Moad0011Algorithm.java unit/Moad0011UnitTest.java
|
||||
* java -cp . unit.Moad0011UnitTest
|
||||
*/
|
||||
public class Moad0011UnitTest {
|
||||
|
||||
private static int passed = 0;
|
||||
private static int failed = 0;
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Moad0011UnitTest (A Catastrophic Inheritance) ===\n");
|
||||
|
||||
System.out.println("-- Structural detection: hasCatastrophicStructure() --");
|
||||
testDetectsKnownBadPatterns();
|
||||
testPassesKnownSafePatterns();
|
||||
|
||||
System.out.println("\n-- Correctness: both produce identical match results --");
|
||||
testBothMatchMatchingInput();
|
||||
testBothRejectNonMatchingInput();
|
||||
testBothMatchSingleA();
|
||||
|
||||
System.out.println("\n-- Defect: step count grows super-linearly on adversarial input --");
|
||||
testDefectiveStepCountExponentialGrowth();
|
||||
testDefectiveAdversarialN8IsLarge();
|
||||
|
||||
System.out.println("\n-- Fix: step count grows linearly on adversarial input --");
|
||||
testFixedStepCountLinearGrowth();
|
||||
testFixedAdversarialN8IsSmall();
|
||||
|
||||
System.out.println("\n-- Ratio: defective steps >> fixed steps on adversarial input --");
|
||||
testStepRatioAdvantageForFixed();
|
||||
|
||||
System.out.printf("\n%d passed, %d failed%n", passed, failed);
|
||||
if (failed > 0) System.exit(1);
|
||||
}
|
||||
|
||||
// ── Structural detection ──────────────────────────────────────────────────
|
||||
|
||||
static void testDetectsKnownBadPatterns() {
|
||||
String[] bad = {
|
||||
"(a+)+b", // classic ReDoS — outer + wraps inner a+
|
||||
"(a+)+", // same, no terminal
|
||||
"(a*)+", // inner * outer +
|
||||
"(ab+c*)+", // complex inner group with quantifiers
|
||||
Moad0011Algorithm.DEFECTIVE_PATTERN_BLEACH,
|
||||
Moad0011Algorithm.DEFECTIVE_PATTERN_CSS,
|
||||
};
|
||||
for (String p : bad) {
|
||||
assertTrue("detects bad pattern: " + p,
|
||||
Moad0011Algorithm.hasCatastrophicStructure(p),
|
||||
"expected catastrophic, got safe");
|
||||
}
|
||||
}
|
||||
|
||||
static void testPassesKnownSafePatterns() {
|
||||
String[] safe = {
|
||||
"a+b", // simple concatenation — no nesting
|
||||
"a+", // single quantifier
|
||||
"(a|b)+", // alternation with outer +, no inner quantifier
|
||||
"[a-z]+", // character class, single quantifier
|
||||
"(abc)+", // fixed group, no inner quantifier
|
||||
Moad0011Algorithm.FIXED_PATTERN_BLEACH,
|
||||
Moad0011Algorithm.FIXED_PATTERN_CSS,
|
||||
};
|
||||
for (String p : safe) {
|
||||
assertFalse("passes safe pattern: " + p,
|
||||
Moad0011Algorithm.hasCatastrophicStructure(p),
|
||||
"expected safe, got catastrophic");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Correctness ───────────────────────────────────────────────────────────
|
||||
|
||||
static void testBothMatchMatchingInput() {
|
||||
String input = Moad0011Algorithm.matchingInput(5); // "aaaaab"
|
||||
Result def = Moad0011Algorithm.matchDefective(input);
|
||||
Result fix = Moad0011Algorithm.matchFixed(input);
|
||||
assertTrue("defective: matches 'aaaaab'", def.matched, "expected match");
|
||||
assertTrue("fixed: matches 'aaaaab'", fix.matched, "expected match");
|
||||
}
|
||||
|
||||
static void testBothRejectNonMatchingInput() {
|
||||
String input = Moad0011Algorithm.adversarialInput(5); // "aaaaac" — no 'b'
|
||||
Result def = Moad0011Algorithm.matchDefective(input);
|
||||
Result fix = Moad0011Algorithm.matchFixed(input);
|
||||
assertFalse("defective: rejects 'aaaaac'", def.matched, "expected no match");
|
||||
assertFalse("fixed: rejects 'aaaaac'", fix.matched, "expected no match");
|
||||
}
|
||||
|
||||
static void testBothMatchSingleA() {
|
||||
String input = "ab";
|
||||
Result def = Moad0011Algorithm.matchDefective(input);
|
||||
Result fix = Moad0011Algorithm.matchFixed(input);
|
||||
assertTrue("defective: matches 'ab'", def.matched, "expected match");
|
||||
assertTrue("fixed: matches 'ab'", fix.matched, "expected match");
|
||||
}
|
||||
|
||||
// ── Defect ────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testDefectiveStepCountExponentialGrowth() {
|
||||
// Adversarial input: N a's + 'c' — non-matching, forces maximum backtracking
|
||||
long steps4 = Moad0011Algorithm.matchDefective(Moad0011Algorithm.adversarialInput(4)).steps;
|
||||
long steps6 = Moad0011Algorithm.matchDefective(Moad0011Algorithm.adversarialInput(6)).steps;
|
||||
long steps8 = Moad0011Algorithm.matchDefective(Moad0011Algorithm.adversarialInput(8)).steps;
|
||||
|
||||
// Exponential growth: steps8 >> steps6 >> steps4
|
||||
// Ratio should be super-linear (greater than linear doubling)
|
||||
double ratio64 = (double) steps6 / steps4;
|
||||
double ratio86 = (double) steps8 / steps6;
|
||||
|
||||
assertTrue("defective: steps grow super-linearly from N=4 to N=6 (ratio > 2.0)",
|
||||
ratio64 > 2.0, "ratio64=" + ratio64);
|
||||
assertTrue("defective: steps grow super-linearly from N=6 to N=8 (ratio > 2.0)",
|
||||
ratio86 > 2.0, "ratio86=" + ratio86);
|
||||
|
||||
System.out.printf(" steps N=4: %d, N=6: %d, N=8: %d (ratios: %.1fx, %.1fx)%n",
|
||||
steps4, steps6, steps8, ratio64, ratio86);
|
||||
}
|
||||
|
||||
static void testDefectiveAdversarialN8IsLarge() {
|
||||
long steps = Moad0011Algorithm.matchDefective(Moad0011Algorithm.adversarialInput(8)).steps;
|
||||
// N=8 adversarial input: expect hundreds or thousands of steps (super-linear)
|
||||
assertTrue("defective: N=8 adversarial input requires many steps (>50)",
|
||||
steps > 50, "steps=" + steps);
|
||||
}
|
||||
|
||||
// ── Fix ───────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testFixedStepCountLinearGrowth() {
|
||||
long steps4 = Moad0011Algorithm.matchFixed(Moad0011Algorithm.adversarialInput(4)).steps;
|
||||
long steps8 = Moad0011Algorithm.matchFixed(Moad0011Algorithm.adversarialInput(8)).steps;
|
||||
long steps16 = Moad0011Algorithm.matchFixed(Moad0011Algorithm.adversarialInput(16)).steps;
|
||||
|
||||
// Linear growth: doubling N should roughly double steps (ratio ~2x)
|
||||
double ratio84 = (double) steps8 / steps4;
|
||||
double ratio168 = (double) steps16 / steps8;
|
||||
|
||||
// Allow ratio up to 3.0 to be generous with small constants, but it must not be exponential
|
||||
assertTrue("fixed: doubling N from 4 to 8 roughly doubles steps (ratio 1.0-3.0)",
|
||||
ratio84 >= 1.0 && ratio84 <= 3.0, "ratio84=" + ratio84);
|
||||
assertTrue("fixed: doubling N from 8 to 16 roughly doubles steps (ratio 1.0-3.0)",
|
||||
ratio168 >= 1.0 && ratio168 <= 3.0, "ratio168=" + ratio168);
|
||||
|
||||
System.out.printf(" steps N=4: %d, N=8: %d, N=16: %d (ratios: %.1fx, %.1fx)%n",
|
||||
steps4, steps8, steps16, ratio84, ratio168);
|
||||
}
|
||||
|
||||
static void testFixedAdversarialN8IsSmall() {
|
||||
long steps = Moad0011Algorithm.matchFixed(Moad0011Algorithm.adversarialInput(8)).steps;
|
||||
// Fixed NFA: O(N) steps — N=8 means steps should be a small multiple of 8
|
||||
assertTrue("fixed: N=8 adversarial requires <= 20 steps (linear)",
|
||||
steps <= 20, "steps=" + steps);
|
||||
}
|
||||
|
||||
// ── Ratio ─────────────────────────────────────────────────────────────────
|
||||
|
||||
static void testStepRatioAdvantageForFixed() {
|
||||
String adversarial = Moad0011Algorithm.adversarialInput(10);
|
||||
long defSteps = Moad0011Algorithm.matchDefective(adversarial).steps;
|
||||
long fixSteps = Moad0011Algorithm.matchFixed(adversarial).steps;
|
||||
|
||||
// At N=10, defective explores exponentially many states vs fixed linear scan
|
||||
double ratio = (double) defSteps / fixSteps;
|
||||
assertTrue("defective steps >> fixed steps at N=10 adversarial (ratio > 10x)",
|
||||
ratio > 10.0, "defSteps=" + defSteps + " fixSteps=" + fixSteps + " ratio=" + ratio);
|
||||
|
||||
System.out.printf(" N=10 adversarial: defective=%d steps, fixed=%d steps (%.0fx advantage)%n",
|
||||
defSteps, fixSteps, ratio);
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
static void assertTrue(String label, boolean condition, String detail) {
|
||||
if (condition) {
|
||||
System.out.printf(" PASS: %s%n", label);
|
||||
passed++;
|
||||
} else {
|
||||
System.out.printf(" FAIL: %s — %s%n", label, detail);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
static void assertFalse(String label, boolean condition, String detail) {
|
||||
assertTrue(label, !condition, detail);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue