package unit; import java.util.*; /** * Standalone unit tests for helm CWE-407 defects. * * helm-0001: processDependencyEnabled — O(n²) nested dependency lookup * Pattern A: for each existing dep, scan all metadata deps — O(E × M). * Pattern B: getAliasDependency called per metadata dep — O(M × C). * slow() counts ops for both patterns with nested loops. * fast() counts ops using a pre-built name→entry map for O(1) lookups. * Assert: slowOps > fastOps * 5x for D=200 dependencies. * * helm-0002: filterReleases / filterPlugins — O(R×M) slices.Contains in filter loops * Slow: slices.Contains(ignoredNames, name) called per release/plugin — O(R×M). * Fast: pre-built map[name]struct{} — O(R+M). * Assert: slowOps > fastOps * 5x for R=500, M=100. * * helm-0003: checkRequestedRepos / isRepoRequested — O(n×m) nested + per-iteration scan * Slow: nested loop O(M×R) + isRepoRequested O(R×M) per repo. * Fast: pre-built map[name]struct{} for O(1) lookups. * Assert: slowOps > fastOps * 5x for R=300, M=100. */ public class HelmTest { static class ChartDep { String name; String version; ChartDep(String name, String version) { this.name = name; this.version = version; } } // ── helm-0001 ───────────────────────────────────────────────────────────── /** * Slow path — Pattern A: O(existing × metaDeps). * Pattern B: O(metaDeps × charts) where getAliasDependency scans charts linearly. */ static long slowProcessDependencies(List existing, List metaDeps) { long ops = 0; // Pattern A: filter existing not in metaDeps List chartDeps = new ArrayList<>(); outer: for (ChartDep ex : existing) { for (ChartDep req : metaDeps) { // O(M) per existing item ops++; if (ex.name.equals(req.name)) { continue outer; } } chartDeps.add(ex); } // Pattern B: for each metaDep, scan existing (getAliasDependency linear scan) for (ChartDep req : metaDeps) { for (ChartDep ch : existing) { // O(C) per metaDep ops++; if (ch.name.equals(req.name)) { chartDeps.add(ch); // alias copy break; } } } return ops; } /** * Fast path — build name→ChartDep maps once; O(1) lookups. */ static long fastProcessDependencies(List existing, List metaDeps) { long ops = 0; // Build index: O(E) + O(M) Map metaByName = new HashMap<>(metaDeps.size()); for (ChartDep req : metaDeps) { ops++; metaByName.put(req.name, req); } Map chartsByName = new HashMap<>(existing.size()); for (ChartDep ch : existing) { ops++; chartsByName.put(ch.name, ch); } // Pattern A replacement — O(E) with O(1) lookup List chartDeps = new ArrayList<>(); for (ChartDep ex : existing) { ops++; if (!metaByName.containsKey(ex.name)) { chartDeps.add(ex); } } // Pattern B replacement — O(M) with O(1) lookup for (ChartDep req : metaDeps) { ops++; ChartDep ch = chartsByName.get(req.name); if (ch != null) { chartDeps.add(ch); } } return ops; } static void testProcessDependencies() { int D = 200; // number of dependencies List existing = new ArrayList<>(D); List metaDeps = new ArrayList<>(D); for (int i = 0; i < D; i++) { existing.add(new ChartDep("chart-" + i, "1.0." + i)); metaDeps.add(new ChartDep("chart-" + i, ">=1.0.0")); } long sOps = slowProcessDependencies(existing, metaDeps); long fOps = fastProcessDependencies(existing, metaDeps); int Nx = 5; boolean pass = sOps > fOps * Nx; System.out.printf("helm-0001 [D=%d]: slow=%d fast=%d ratio=%.1fx — %s%n", D, sOps, fOps, (double) sOps / fOps, pass ? "PASS" : "FAIL"); if (!pass) throw new AssertionError("helm-0001 FAIL: slow=" + sOps + " fast=" + fOps); } // ── helm-0002 ───────────────────────────────────────────────────────────── /** * Slow: filterReleases — slices.Contains(ignoredNames, name) per release — O(R×M). * R = number of releases, M = size of ignoredNames list. */ static long slowFilterReleases(List releases, List ignoredNames) { long ops = 0; List filtered = new ArrayList<>(); for (String rel : releases) { // slices.Contains — O(M) linear scan per release boolean found = false; for (int i = 0; i < ignoredNames.size(); i++) { ops++; if (ignoredNames.get(i).equals(rel)) { found = true; break; } } if (!found) filtered.add(rel); } return ops; } /** * Fast: build map[name]struct{} once — O(M) build, O(1) per release lookup. */ static long fastFilterReleases(List releases, List ignoredNames) { long ops = 0; // O(M) build Set ignoreSet = new HashSet<>(ignoredNames.size()); for (String name : ignoredNames) { ops++; ignoreSet.add(name); } // O(R) filter with O(1) lookup List filtered = new ArrayList<>(); for (String rel : releases) { ops++; // O(1) map lookup if (!ignoreSet.contains(rel)) filtered.add(rel); } return ops; } static void testFilterReleases() { int R = 500; // releases int M = 100; // ignored names // ignoredNames all miss — worst case, full scan every release List releases = new ArrayList<>(R); for (int i = 0; i < R; i++) releases.add("release-" + i); List ignoredNames = new ArrayList<>(M); for (int i = 0; i < M; i++) ignoredNames.add("ignored-" + i); long sOps = slowFilterReleases(releases, ignoredNames); long fOps = fastFilterReleases(releases, ignoredNames); int Nx = 5; boolean pass = sOps > fOps * Nx; System.out.printf("helm-0002 [R=%d M=%d]: slow=%d fast=%d ratio=%.1fx — %s%n", R, M, sOps, fOps, (double) sOps / fOps, pass ? "PASS" : "FAIL"); if (!pass) throw new AssertionError("helm-0002 FAIL: slow=" + sOps + " fast=" + fOps); } // ── helm-0003 ───────────────────────────────────────────────────────────── /** * Slow: checkRequestedRepos nested loop O(M×R) + isRepoRequested inside outer loop O(R×M). * R = configured repos, M = requested repo names. */ static long slowRepoUpdate(List allRepos, List requestedRepos) { long ops = 0; // checkRequestedRepos — O(M × R) nested loop for (String req : requestedRepos) { boolean found = false; for (String repo : allRepos) { // O(R) per requested name ops++; if (req.equals(repo)) { found = true; break; } } // (error if !found — not counted) } // runUpdate loop — isRepoRequested(cfg.Name, o.names) O(M) per repo for (String repo : allRepos) { boolean requested = false; for (String req : requestedRepos) { // O(M) per repo — slices.Contains ops++; if (repo.equals(req)) { requested = true; break; } } if (requested) { // would build ChartRepository } } return ops; } /** * Fast: build map[name]struct{} once; O(1) membership for both passes. */ static long fastRepoUpdate(List allRepos, List requestedRepos) { long ops = 0; // Build set from requestedRepos — O(M) Set requestedSet = new HashSet<>(requestedRepos.size()); for (String req : requestedRepos) { ops++; requestedSet.add(req); } // Build set from allRepos — O(R) for checkRequestedRepos Set validSet = new HashSet<>(allRepos.size()); for (String repo : allRepos) { ops++; validSet.add(repo); } // checkRequestedRepos — O(M) with O(1) lookup for (String req : requestedRepos) { ops++; // validSet.contains(req) — O(1) } // runUpdate loop — O(R) with O(1) isRepoRequested for (String repo : allRepos) { ops++; // O(1) map lookup if (requestedSet.contains(repo)) { // would build ChartRepository } } return ops; } static void testRepoUpdate() { int R = 300; // configured repositories int M = 100; // requested repo names (all valid, none matching — worst case) List allRepos = new ArrayList<>(R); for (int i = 0; i < R; i++) allRepos.add("repo-" + i); List requestedRepos = new ArrayList<>(M); for (int i = R; i < R + M; i++) requestedRepos.add("repo-" + i); // disjoint — full scans long sOps = slowRepoUpdate(allRepos, requestedRepos); long fOps = fastRepoUpdate(allRepos, requestedRepos); int Nx = 5; boolean pass = sOps > fOps * Nx; System.out.printf("helm-0003 [R=%d M=%d]: slow=%d fast=%d ratio=%.1fx — %s%n", R, M, sOps, fOps, (double) sOps / fOps, pass ? "PASS" : "FAIL"); if (!pass) throw new AssertionError("helm-0003 FAIL: slow=" + sOps + " fast=" + fOps); } // ── main ────────────────────────────────────────────────────────────────── public static void main(String[] args) { testProcessDependencies(); testFilterReleases(); testRepoUpdate(); System.out.println("3/3 PASS"); } }