java-topology/defects/love2d/unit/Love2dWindowSizeDedupTest.java

203 lines
7.4 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.

package unit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.TreeSet;
/**
* love2d-0002: Window::getFullscreenSizes O(n²) dedup — CWE-407
* love2d-0003: Filesystem::allowMountingForPath O(n) per call — CWE-407
*
* Two defects in one test class, each with slow/fast variants.
*/
public class Love2dWindowSizeDedupTest {
// Represents {width, height} — SDL DisplayMode size key
static final class WindowSize {
final int width, height;
WindowSize(int w, int h) { this.width = w; this.height = h; }
@Override public boolean equals(Object o) {
if (!(o instanceof WindowSize)) return false;
WindowSize ws = (WindowSize) o;
return width == ws.width && height == ws.height;
}
@Override public int hashCode() { return width * 31 + height; }
@Override public String toString() { return width + "x" + height; }
}
// --- love2d-0002 ---
/**
* Slow: mirrors love2d Window::getFullscreenSizes().
* Builds a fake SDL mode list with duplicates and deduplicates with std::find.
* Returns total comparison count.
*/
static long slowGetFullscreenSizes(List<WindowSize> modes) {
List<WindowSize> sizes = new ArrayList<>();
long comparisons = 0;
for (WindowSize w : modes) {
// std::find: iterate over existing sizes — O(n)
boolean found = false;
for (WindowSize s : sizes) {
comparisons++;
if (s.equals(w)) { found = true; break; }
}
if (!found) sizes.add(w);
}
return comparisons;
}
/**
* Fast: set-based dedup — O(1) amortized per lookup.
* Returns number of set.contains() calls (each O(1)).
*/
static long fastGetFullscreenSizes(List<WindowSize> modes) {
HashSet<WindowSize> seen = new HashSet<>(); // FIX love2d-0002
List<WindowSize> sizes = new ArrayList<>();
long probes = 0;
for (WindowSize w : modes) {
probes++; // O(1) hash lookup
if (seen.add(w)) sizes.add(w);
}
return probes;
}
// --- love2d-0003 ---
/**
* Slow: mirrors Filesystem::allowMountingForPath — O(n) list scan per add.
* Returns total comparison count across all N insertions.
*/
static long slowAllowMountPaths(List<String> paths) {
List<String> allowedMountPaths = new ArrayList<>();
long comparisons = 0;
for (String path : paths) {
// std::find on vector: O(n) — CWE-407
boolean found = false;
for (String existing : allowedMountPaths) {
comparisons++;
if (existing.equals(path)) { found = true; break; }
}
if (!found) allowedMountPaths.add(path);
}
return comparisons;
}
/**
* Fast: unordered_set for O(1) membership — FIX love2d-0003.
* Returns number of set.add() calls (each O(1) amortized).
*/
static long fastAllowMountPaths(List<String> paths) {
HashSet<String> allowedMountPaths = new HashSet<>(); // FIX love2d-0003
long probes = 0;
for (String path : paths) {
probes++; // O(1) amortized
allowedMountPaths.add(path);
}
return probes;
}
// Helper: build display mode list with D duplicates per unique size
static List<WindowSize> buildModeList(int uniqueCount, int dupsPerSize) {
List<WindowSize> modes = new ArrayList<>();
for (int i = 0; i < uniqueCount; i++) {
int w = 640 + i * 4;
int h = 480 + i * 3;
for (int d = 0; d < dupsPerSize; d++) {
modes.add(new WindowSize(w, h));
}
}
return modes;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// --- love2d-0002 tests ---
// Test 1: small mode list (50 unique × 3 dup = 150 entries)
{
total++;
List<WindowSize> modes = buildModeList(50, 3);
long slow = slowGetFullscreenSizes(modes);
long fast = fastGetFullscreenSizes(modes);
boolean ok = slow > fast * 10;
System.out.printf("[0002] Test 1 (50 unique × 3 dup): slow=%d, fast=%d, ratio=%.1fx — %s%n",
slow, fast, (double) slow / fast, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 2: large mode list (200 unique × 5 dup = 1000 entries)
{
total++;
List<WindowSize> modes = buildModeList(200, 5);
long slow = slowGetFullscreenSizes(modes);
long fast = fastGetFullscreenSizes(modes);
boolean ok = slow > fast * 100;
System.out.printf("[0002] Test 2 (200 unique × 5 dup): slow=%d, fast=%d, ratio=%.1fx — %s%n",
slow, fast, (double) slow / fast, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 3: worst case — all same size (100% duplicates)
{
total++;
List<WindowSize> modes = new ArrayList<>();
for (int i = 0; i < 500; i++) modes.add(new WindowSize(1920, 1080));
long slow = slowGetFullscreenSizes(modes);
long fast = fastGetFullscreenSizes(modes);
// Slow: each of 500 entries scans [0..1) already-found sizes (always 1 entry)
// Actually worst case is when unique sizes accumulate — but this is degenerate
// slow = 0+1+1+...+1 = 499; fast = 500 probes. Different pattern.
// For fully-duplicate list slow scans the 1-element seen list each time = 499 scans
// fast = 500 probes each O(1). Not a huge ratio but slow still > fast.
boolean ok = fast == 500 && slow == 499;
System.out.printf("[0002] Test 3 (all same): slow=%d, fast=%d — %s%n",
slow, fast, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// --- love2d-0003 tests ---
// Test 4: 100 unique paths + 50 repeated
{
total++;
List<String> paths = new ArrayList<>();
for (int i = 0; i < 100; i++) paths.add("/game/mod/pack" + i + ".zip");
for (int i = 0; i < 50; i++) paths.add("/game/mod/pack" + i + ".zip"); // duplicates
long slow = slowAllowMountPaths(paths);
long fast = fastAllowMountPaths(paths);
boolean ok = slow > fast * 20;
System.out.printf("[0003] Test 4 (100 unique+50 dup paths): slow=%d, fast=%d, ratio=%.1fx — %s%n",
slow, fast, (double) slow / fast, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Test 5: 500 unique paths — growing O(n²) vs O(n)
{
total++;
List<String> paths = new ArrayList<>();
for (int i = 0; i < 500; i++) paths.add("/usr/share/love/mods/pack" + i);
long slow = slowAllowMountPaths(paths);
long fast = fastAllowMountPaths(paths);
boolean ok = slow > fast * 100;
System.out.printf("[0003] Test 5 (500 unique paths): slow=%d, fast=%d, ratio=%.1fx — %s%n",
slow, fast, (double) slow / fast, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
System.out.printf("%n%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}