148 lines
6.6 KiB
Java
148 lines
6.6 KiB
Java
package unit;
|
|
|
|
import java.util.*;
|
|
import java.util.stream.*;
|
|
|
|
/**
|
|
* Standalone unit test for kylin-0001:
|
|
* JdbcJobScheduler — List<String>.contains() in stream filter → O(J²).
|
|
*
|
|
* Simulates the releaseJobLock() pattern:
|
|
* jobInfoIds collected as List<String>, then used in stream filter with .contains().
|
|
*
|
|
* Compile: javac -d . JobSchedulerAlgorithm.java
|
|
* Run: java unit.JobSchedulerAlgorithm
|
|
*/
|
|
public class JobSchedulerAlgorithm {
|
|
|
|
// ── Result ───────────────────────────────────────────────────────────────
|
|
|
|
static class Result {
|
|
final List<String> toRemoveLocks;
|
|
final long ns;
|
|
Result(List<String> locks, long ns) { this.toRemoveLocks = locks; this.ns = ns; }
|
|
}
|
|
|
|
// ── Defective: collects jobInfoIds into List → O(J) .contains per jobId ─
|
|
|
|
static class DefectiveAlgorithm {
|
|
/**
|
|
* @param jobIds all job IDs found in the lock table
|
|
* @param jobInfoIds job IDs that have corresponding JobInfo records (as List)
|
|
*/
|
|
Result releaseJobLock(List<String> jobIds, List<String> jobInfoIds) {
|
|
long t0 = System.nanoTime();
|
|
// Mirrors the defective code:
|
|
// List<String> jobInfoIds = jobs.stream().map(JobInfo::getJobId).collect(Collectors.toList());
|
|
// ... filter(jobId -> !jobInfoIds.contains(jobId)) ...
|
|
List<String> toRemoveLocks = jobIds.stream()
|
|
.filter(jobId -> !jobInfoIds.contains(jobId)) // CWE-407 site: O(J) per call
|
|
.collect(Collectors.toList());
|
|
return new Result(toRemoveLocks, System.nanoTime() - t0);
|
|
}
|
|
}
|
|
|
|
// ── Fixed: uses Set<String> → O(1) .contains ────────────────────────────
|
|
|
|
static class FixedAlgorithm {
|
|
Result releaseJobLock(List<String> jobIds, List<String> jobInfoIdsList) {
|
|
long t0 = System.nanoTime();
|
|
// Fix: collect into HashSet for O(1) membership
|
|
Set<String> jobInfoIds = new HashSet<>(jobInfoIdsList);
|
|
List<String> toRemoveLocks = jobIds.stream()
|
|
.filter(jobId -> !jobInfoIds.contains(jobId)) // O(1)
|
|
.collect(Collectors.toList());
|
|
return new Result(toRemoveLocks, System.nanoTime() - t0);
|
|
}
|
|
}
|
|
|
|
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
|
|
static List<String> makeJobIds(int N) {
|
|
List<String> ids = new ArrayList<>(N);
|
|
for (int i = 0; i < N; i++) ids.add("job-" + i);
|
|
return ids;
|
|
}
|
|
|
|
// ── Assertions ───────────────────────────────────────────────────────────
|
|
|
|
static void assertEquals(Object expected, Object actual, String msg) {
|
|
if (!expected.equals(actual))
|
|
throw new AssertionError(msg + ": expected=" + expected + " actual=" + actual);
|
|
}
|
|
|
|
static void assertTrue(boolean cond, String msg) {
|
|
if (!cond) throw new AssertionError(msg);
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== kylin-0001: JobSchedulerAlgorithm ===");
|
|
|
|
DefectiveAlgorithm defAlg = new DefectiveAlgorithm();
|
|
FixedAlgorithm fixAlg = new FixedAlgorithm();
|
|
|
|
// Correctness test 1: some jobs have info, some don't
|
|
{
|
|
List<String> jobIds = makeJobIds(20);
|
|
// Only even-numbered jobs have a JobInfo record
|
|
List<String> jobInfoIds = jobIds.stream()
|
|
.filter(id -> Integer.parseInt(id.replace("job-", "")) % 2 == 0)
|
|
.collect(Collectors.toList());
|
|
|
|
Result dr = defAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobInfoIds));
|
|
Result fr = fixAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobInfoIds));
|
|
|
|
// Sort both for comparison (order may differ)
|
|
List<String> dSorted = new ArrayList<>(dr.toRemoveLocks); Collections.sort(dSorted);
|
|
List<String> fSorted = new ArrayList<>(fr.toRemoveLocks); Collections.sort(fSorted);
|
|
|
|
assertEquals(dSorted, fSorted, "toRemoveLocks contents");
|
|
// All odd-numbered jobs should be in toRemoveLocks
|
|
assertTrue(dr.toRemoveLocks.size() == 10, "expected 10 orphan locks, got " + dr.toRemoveLocks.size());
|
|
System.out.println("PASS correctness (J=20, half missing)");
|
|
}
|
|
|
|
// Correctness test 2: all jobs have info records (no locks to remove)
|
|
{
|
|
List<String> jobIds = makeJobIds(10);
|
|
Result dr = defAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobIds));
|
|
Result fr = fixAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobIds));
|
|
assertEquals(0, dr.toRemoveLocks.size(), "no orphan locks (defective)");
|
|
assertEquals(0, fr.toRemoveLocks.size(), "no orphan locks (fixed)");
|
|
System.out.println("PASS correctness all-present (J=10)");
|
|
}
|
|
|
|
// Benchmark at J=2000
|
|
{
|
|
int J = 2000;
|
|
List<String> jobIds = makeJobIds(J);
|
|
// Half the jobs have info records
|
|
List<String> jobInfoIds = jobIds.subList(0, J / 2);
|
|
|
|
// warm up
|
|
for (int i = 0; i < 5; i++) {
|
|
defAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobInfoIds));
|
|
fixAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobInfoIds));
|
|
}
|
|
|
|
long defNs = 0, fixNs = 0;
|
|
int reps = 30;
|
|
for (int i = 0; i < reps; i++) {
|
|
defNs += defAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobInfoIds)).ns;
|
|
fixNs += fixAlg.releaseJobLock(new ArrayList<>(jobIds), new ArrayList<>(jobInfoIds)).ns;
|
|
}
|
|
defNs /= reps; fixNs /= reps;
|
|
double ratio = (double) defNs / Math.max(1, fixNs);
|
|
|
|
System.out.printf("BENCH J=%d reps=%d%n", J, reps);
|
|
System.out.printf(" defective avg: %,d ns%n", defNs);
|
|
System.out.printf(" fixed avg: %,d ns%n", fixNs);
|
|
System.out.printf(" speedup: %.1fx%n", ratio);
|
|
|
|
assertTrue(ratio >= 2.0, "Expected fixed >= 2x faster, got " + ratio + "x");
|
|
System.out.println("PASS speedup >= 2x");
|
|
}
|
|
|
|
System.out.println("=== ALL PASS ===");
|
|
}
|
|
}
|