java-topology/defects/helm/unit/HelmTest.java

275 lines
10 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.*;
/**
* 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<ChartDep> existing, List<ChartDep> metaDeps) {
long ops = 0;
// Pattern A: filter existing not in metaDeps
List<ChartDep> 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<ChartDep> existing, List<ChartDep> metaDeps) {
long ops = 0;
// Build index: O(E) + O(M)
Map<String, ChartDep> metaByName = new HashMap<>(metaDeps.size());
for (ChartDep req : metaDeps) {
ops++;
metaByName.put(req.name, req);
}
Map<String, ChartDep> 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<ChartDep> 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<ChartDep> existing = new ArrayList<>(D);
List<ChartDep> 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<String> releases, List<String> ignoredNames) {
long ops = 0;
List<String> 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<String> releases, List<String> ignoredNames) {
long ops = 0;
// O(M) build
Set<String> ignoreSet = new HashSet<>(ignoredNames.size());
for (String name : ignoredNames) { ops++; ignoreSet.add(name); }
// O(R) filter with O(1) lookup
List<String> 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<String> releases = new ArrayList<>(R);
for (int i = 0; i < R; i++) releases.add("release-" + i);
List<String> 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<String> allRepos, List<String> 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<String> allRepos, List<String> requestedRepos) {
long ops = 0;
// Build set from requestedRepos — O(M)
Set<String> requestedSet = new HashSet<>(requestedRepos.size());
for (String req : requestedRepos) { ops++; requestedSet.add(req); }
// Build set from allRepos — O(R) for checkRequestedRepos
Set<String> 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<String> allRepos = new ArrayList<>(R);
for (int i = 0; i < R; i++) allRepos.add("repo-" + i);
List<String> 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");
}
}