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 snapModules, List 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 snapModules, List manifest) { fastOps = 0; Map 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 snapModules = new LinkedHashMap<>(); List 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 modules = (Map) data[0]; List manifest = (List) 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 modules = (Map) data[0]; List manifest = (List) 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) data[0], (List) 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) data[0], (List) 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 modules = (Map) data[0]; List manifest = (List) 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); } }