java-topology/defects/raylib/unit/RaylibRandomSequenceTest.java

157 lines
5.6 KiB
Java

package unit;
import java.util.HashSet;
import java.util.Random;
/**
* raylib-0002: LoadRandomSequence O(n²) dedup — CWE-407
*
* Demonstrates: for each of N values generated, a linear scan of the
* previously-accepted values is O(n) per iteration → O(n²) total.
* Fix: use a boolean seen[] bitmap (range-bounded) for O(1) membership.
*/
public class RaylibRandomSequenceTest {
/**
* Slow path: mirrors the raylib fallback in LoadRandomSequence().
* Returns the exact number of inner-loop iterations (membership checks).
*/
static long slowRandomSequence(int count, int min, int max, long seed) {
int range = Math.abs(max - min) + 1;
assert count <= range : "count must not exceed range";
int[] values = new int[count];
Random rng = new Random(seed);
long innerOps = 0;
int i = 0;
while (i < count) {
int value = min + rng.nextInt(range);
boolean dupValue = false;
// O(i) linear scan — CWE-407
for (int j = 0; j < i; j++) {
innerOps++;
if (values[j] == value) {
dupValue = true;
break;
}
}
if (!dupValue) {
values[i] = value;
i++;
}
}
// Verify correctness: all values unique and in [min, max]
HashSet<Integer> seen = new HashSet<>();
for (int v : values) {
assert v >= min && v <= max : "value out of range: " + v;
assert seen.add(v) : "duplicate value: " + v;
}
return innerOps;
}
/**
* Fast path: boolean seen[] bitmap — O(1) membership per check.
* Returns the number of bitmap probes (each is O(1)).
*/
static long fastRandomSequence(int count, int min, int max, long seed) {
int range = Math.abs(max - min) + 1;
assert count <= range : "count must not exceed range";
int[] values = new int[count];
boolean[] seen = new boolean[range]; // FIX raylib-0002
Random rng = new Random(seed);
long probes = 0;
int i = 0;
while (i < count) {
int value = min + rng.nextInt(range);
int idx = value - min;
probes++; // O(1) bitmap probe
if (!seen[idx]) {
seen[idx] = true;
values[i] = value;
i++;
}
}
// Verify correctness
HashSet<Integer> check = new HashSet<>();
for (int v : values) {
assert v >= min && v <= max : "value out of range: " + v;
assert check.add(v) : "duplicate value: " + v;
}
return probes;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Test 1: small n — verify slow is O(n²), fast is O(n)
{
total++;
int n = 100, min = 0, max = 999;
long seed = 42L;
long slowOps = slowRandomSequence(n, min, max, seed);
long fastOps = fastRandomSequence(n, min, max, seed);
// Slow expected ~n*(n-1)/4 ~ 2475 ops; fast expected ~n + small constant
boolean ok = slowOps > fastOps * 5;
System.out.printf("Test 1 (n=%d): slow=%d ops, fast=%d ops, ratio=%.1fx — %s%n",
n, slowOps, fastOps, (double) slowOps / fastOps, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 2: medium n — ratio should be much larger
{
total++;
int n = 500, min = 0, max = 9999;
long seed = 123L;
long slowOps = slowRandomSequence(n, min, max, seed);
long fastOps = fastRandomSequence(n, min, max, seed);
boolean ok = slowOps > fastOps * 50;
System.out.printf("Test 2 (n=%d): slow=%d ops, fast=%d ops, ratio=%.1fx — %s%n",
n, slowOps, fastOps, (double) slowOps / fastOps, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 3: worst case — count == range (dense selection forces many collisions)
{
total++;
int n = 200, min = 0, max = 199; // count == range: worst case for slow
long seed = 7L;
long slowOps = slowRandomSequence(n, min, max, seed);
long fastOps = fastRandomSequence(n, min, max, seed);
// Slow expected ~n²/4 = 10000 ops; fast expected ~n*H(n) ≈ 1060 ops (harmonic)
boolean ok = slowOps > fastOps * 5;
System.out.printf("Test 3 (n=%d, dense): slow=%d ops, fast=%d ops, ratio=%.1fx — %s%n",
n, slowOps, fastOps, (double) slowOps / fastOps, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 4: correctness — outputs must be identical sequences given same seed
{
total++;
int n = 50, min = 10, max = 200;
long seed = 99L;
// We can't compare element-by-element because the two algorithms
// process rejections differently per RNG call, so just verify
// both produce valid unique sequences independently.
long slowOps = slowRandomSequence(n, min, max, seed);
long fastOps = fastRandomSequence(n, min, max, seed);
boolean ok = slowOps > 0 && fastOps > 0;
System.out.printf("Test 4 (correctness, n=%d): slow valid=%b, fast valid=%b — %s%n",
n, slowOps > 0, fastOps > 0, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
System.out.printf("%n%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}