275 lines
10 KiB
Java
275 lines
10 KiB
Java
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");
|
||
}
|
||
}
|