162 lines
6.3 KiB
Java
162 lines
6.3 KiB
Java
package unit;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.HashMap;
|
||
import java.util.LinkedHashMap;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
|
||
/**
|
||
* opentofu-0002: readConfigSnapshot manifest validation O(M²) linear scan
|
||
*
|
||
* Slow: for k := range snap.Modules { for _, record := range manifest { if record.Key == k — O(M×R)
|
||
* Fast: pre-build manifestKeys map[string]bool, then if !manifestKeys[k] — O(M+R)
|
||
*
|
||
* Verifies: slow_ops >= M*(M-1)/2, fast_ops == M+R, ratio >= 10x at M=200
|
||
*/
|
||
public class OpenTofuSnapshotManifestAlgorithm {
|
||
|
||
static long slowOps = 0;
|
||
static long fastOps = 0;
|
||
|
||
static class ManifestRecord {
|
||
final String key;
|
||
ManifestRecord(String key) { this.key = key; }
|
||
}
|
||
|
||
/**
|
||
* Slow version: mirrors the original Go validation loop
|
||
* for k := range snap.Modules { found := false; for _, record := range manifest { if record.Key == k ... } }
|
||
*/
|
||
static boolean slowValidate(Map<String, Object> snapModules, List<ManifestRecord> manifest) {
|
||
slowOps = 0;
|
||
for (String k : snapModules.keySet()) {
|
||
boolean found = false;
|
||
for (ManifestRecord record : manifest) {
|
||
slowOps++;
|
||
if (record.key.equals(k)) {
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!found) {
|
||
return false; // module not in manifest
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Fast version: pre-build a set of manifest keys
|
||
* manifestKeys := make(map[string]bool); for _, record := range manifest { manifestKeys[record.Key] = true }
|
||
* then: for k := range snap.Modules { if !manifestKeys[k] ... }
|
||
*/
|
||
static boolean fastValidate(Map<String, Object> snapModules, List<ManifestRecord> manifest) {
|
||
fastOps = 0;
|
||
Map<String, Boolean> manifestKeys = new HashMap<>(manifest.size() * 2);
|
||
for (ManifestRecord record : manifest) {
|
||
fastOps++;
|
||
manifestKeys.put(record.key, true);
|
||
}
|
||
for (String k : snapModules.keySet()) {
|
||
fastOps++;
|
||
if (!manifestKeys.containsKey(k)) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/** Build snap.Modules and manifest with M entries each */
|
||
static Object[] buildData(int M) {
|
||
Map<String, Object> snapModules = new LinkedHashMap<>();
|
||
List<ManifestRecord> manifest = new ArrayList<>();
|
||
for (int i = 0; i < M; i++) {
|
||
String key = "module_" + i;
|
||
snapModules.put(key, new Object());
|
||
manifest.add(new ManifestRecord(key));
|
||
}
|
||
return new Object[]{snapModules, manifest};
|
||
}
|
||
|
||
@SuppressWarnings("unchecked")
|
||
public static void main(String[] args) {
|
||
int passed = 0;
|
||
int total = 0;
|
||
|
||
// Test 1: correctness — both return true when all modules are in manifest
|
||
{
|
||
total++;
|
||
Object[] data = buildData(20);
|
||
Map<String, Object> modules = (Map<String, Object>) data[0];
|
||
List<ManifestRecord> manifest = (List<ManifestRecord>) data[1];
|
||
boolean slowResult = slowValidate(modules, manifest);
|
||
boolean fastResult = fastValidate(modules, manifest);
|
||
boolean ok = slowResult && fastResult;
|
||
System.out.println((ok ? "PASS" : "FAIL") + " [correctness M=20]: slow=" + slowResult + " fast=" + fastResult);
|
||
if (ok) passed++;
|
||
}
|
||
|
||
// Test 2: both detect missing module
|
||
{
|
||
total++;
|
||
Object[] data = buildData(10);
|
||
Map<String, Object> modules = (Map<String, Object>) data[0];
|
||
List<ManifestRecord> manifest = (List<ManifestRecord>) data[1];
|
||
modules.put("module_EXTRA", new Object()); // not in manifest
|
||
boolean slowResult = slowValidate(modules, manifest);
|
||
boolean fastResult = fastValidate(modules, manifest);
|
||
boolean ok = !slowResult && !fastResult;
|
||
System.out.println((ok ? "PASS" : "FAIL") + " [missing module detection]: slow=" + slowResult + " fast=" + fastResult);
|
||
if (ok) passed++;
|
||
}
|
||
|
||
// Test 3: slow op count is O(M²)
|
||
{
|
||
total++;
|
||
int M = 200;
|
||
Object[] data = buildData(M);
|
||
slowValidate((Map<String, Object>) data[0], (List<ManifestRecord>) data[1]);
|
||
// Worst case: every module scans full manifest before finding a match
|
||
// Average case: M * M/2 comparisons. At minimum, M*(M-1)/2 for insertion-order mismatch.
|
||
// We verify at least M comparisons (trivially), but also that it's superlinear.
|
||
// Since keys match in order but manifest may be searched fully before finding, min = sum(1..M) = M*(M+1)/2
|
||
long minExpected = (long) M * (M - 1) / 2;
|
||
boolean ok = slowOps >= minExpected;
|
||
System.out.println((ok ? "PASS" : "FAIL") + " [slow O(M²) M=" + M + "]: ops=" + slowOps + " >= " + minExpected);
|
||
if (ok) passed++;
|
||
}
|
||
|
||
// Test 4: fast op count is O(M+R)
|
||
{
|
||
total++;
|
||
int M = 200;
|
||
Object[] data = buildData(M);
|
||
fastValidate((Map<String, Object>) data[0], (List<ManifestRecord>) data[1]);
|
||
// fastOps = R (manifest scan) + M (module validation) = 2M since R=M
|
||
boolean ok = fastOps <= 2L * M + 1;
|
||
System.out.println((ok ? "PASS" : "FAIL") + " [fast O(M+R) M=" + M + "]: ops=" + fastOps + " <= " + (2 * M + 1));
|
||
if (ok) passed++;
|
||
}
|
||
|
||
// Test 5: ratio >= 10x at M=200
|
||
{
|
||
total++;
|
||
int M = 200;
|
||
Object[] data = buildData(M);
|
||
Map<String, Object> modules = (Map<String, Object>) data[0];
|
||
List<ManifestRecord> manifest = (List<ManifestRecord>) data[1];
|
||
slowValidate(modules, manifest);
|
||
long slowCount = slowOps;
|
||
fastValidate(modules, manifest);
|
||
long fastCount = fastOps;
|
||
double ratio = (double) slowCount / fastCount;
|
||
boolean ok = ratio >= 10.0;
|
||
System.out.printf((ok ? "PASS" : "FAIL") + " [ratio M=%d]: slowOps=%d fastOps=%d ratio=%.1fx%n", M, slowCount, fastCount, ratio);
|
||
if (ok) passed++;
|
||
}
|
||
|
||
System.out.println(passed + "/" + total + " PASS");
|
||
if (passed != total) System.exit(1);
|
||
}
|
||
}
|