tests/engine_ab_equivalence.rs
//! Engine A ≡ Engine B — randomized equivalence contract.
//!
//! The whole point of the A/B split is that the two matching strategies are
//! *interchangeable*: identical verdicts on identical inputs, differing only in cost.
//! This test proves that over a large pseudo-random corpus (no `rand` dep — a tiny
//! SplitMix64 keeps the lean build dependency-free and the corpus reproducible), and
//! also pins both against the shipped [`OsvScanner`] so the abstraction can never
//! silently drift from modgunn's original semantics.
#![cfg(feature = "scan")]
use modgunn::core::{ArtifactRef, Severity};
use modgunn::engine::{build, AdvisoryEngine, EngineKind, IndexedEngine, LinearEngine};
use modgunn::scan::{Advisory, AdvisoryDb, Artifact, OsvScanner, Policy, Provenance, ScanEngine};
use modgunn::versions::RangeEvent::{Fixed, Introduced};
/// Reproducible PRNG — SplitMix64. No external crate; deterministic corpus.
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn below(&mut self, n: u64) -> u64 {
self.next() % n
}
}
const ECOS: [&str; 4] = ["cargo", "npm", "pypi", "maven"];
fn gen_db(rng: &mut Rng, n_adv: usize, n_pkg: usize) -> AdvisoryDb {
let sev = [
Severity::Low,
Severity::Medium,
Severity::High,
Severity::Critical,
];
let mut advisories = Vec::with_capacity(n_adv);
for i in 0..n_adv {
let eco = ECOS[rng.below(ECOS.len() as u64) as usize];
let pkg = format!("pkg{}", rng.below(n_pkg as u64));
let severity = sev[rng.below(sev.len() as u64) as usize];
let mut adv = match rng.below(3) {
// exact-version advisory
0 => {
let v = format!("1.{}.0", rng.below(5));
Advisory::basic(format!("ADV-{i}"), eco, &pkg, vec![v], severity, "exact")
}
// range advisory [1.x, 1.y)
1 => {
let lo = rng.below(3);
let hi = lo + 1 + rng.below(3);
Advisory::basic(format!("ADV-{i}"), eco, &pkg, vec![], severity, "range")
.with_ranges(vec![
Introduced(format!("1.{lo}.0")),
Fixed(format!("1.{hi}.0")),
])
}
// all-versions advisory
_ => Advisory::basic(format!("ADV-{i}"), eco, &pkg, vec![], severity, "all"),
};
// ~1 in 8 withdrawn (must never match either engine).
if rng.below(8) == 0 {
adv.withdrawn = true;
}
// ~1 in 16 KEV (escalation exercised identically by both).
if rng.below(16) == 0 {
adv.kev = true;
}
advisories.push(adv);
}
AdvisoryDb::new("db-rev-rand", advisories)
}
fn gen_artifacts(rng: &mut Rng, n: usize, n_pkg: usize) -> Vec<Artifact> {
let mut out = Vec::with_capacity(n);
for _ in 0..n {
let eco = ECOS[rng.below(ECOS.len() as u64) as usize];
let pkg = format!("pkg{}", rng.below((n_pkg + n_pkg / 4) as u64)); // some misses
let version = format!("1.{}.{}", rng.below(6), rng.below(4));
let license = match rng.below(4) {
0 => None,
1 => Some("GPL-3.0".to_string()),
2 => Some("MIT".to_string()),
_ => Some("Apache-2.0".to_string()),
};
let prov = if rng.below(2) == 0 {
Provenance::signed_by("ci")
} else {
Provenance::unsigned()
};
out.push(Artifact::new(
ArtifactRef {
ecosystem: eco.into(),
name: pkg.clone(),
version,
blob_sha256: format!("{pkg}-sha"),
},
license,
prov,
));
}
out
}
fn policy() -> Policy {
Policy {
version: "rand-policy".into(),
license: modgunn::scan::LicensePolicy {
allow: vec!["MIT".into(), "Apache-2.0".into()],
deny: vec!["GPL-3.0".into()],
on_unknown: modgunn::core::Decision::Warn,
},
provenance: modgunn::scan::ProvenancePolicy::Warn,
epss_block_threshold: Some(0.5),
..Default::default()
}
}
#[test]
fn engines_agree_on_a_large_random_corpus() {
let mut rng = Rng(0xDEAD_BEEF_CAFE_F00D);
let db = gen_db(&mut rng, 4_000, 400);
let artifacts = gen_artifacts(&mut rng, 20_000, 400);
let policy = policy();
let a = LinearEngine::new(&db);
let b = IndexedEngine::new(&db);
let scanner = OsvScanner::new(policy.clone(), db.clone());
let mut hits = 0usize;
for art in &artifacts {
let va = a.verdict(&policy, art);
let vb = b.verdict(&policy, art);
assert_eq!(
va, vb,
"A vs B disagree on {} {}",
art.artifact.name, art.artifact.version
);
// And both equal the shipped per-artifact scanner (Engine A's convenience API).
assert_eq!(va, scanner.scan(art), "engine drifted from OsvScanner");
if !va.findings.is_empty() {
hits += 1;
}
}
// Sanity: the corpus is non-trivial — a healthy fraction of artifacts hit
// something (else the equivalence would be vacuous).
assert!(
hits > 1_000,
"corpus too clean to be a meaningful equivalence proof (hits={hits})"
);
}
#[test]
fn boxed_runtime_swap_agrees() {
let mut rng = Rng(0x0123_4567_89AB_CDEF);
let db = gen_db(&mut rng, 1_000, 100);
let artifacts = gen_artifacts(&mut rng, 3_000, 100);
let policy = policy();
let ea: Box<dyn AdvisoryEngine> = build(EngineKind::A, &db);
let eb: Box<dyn AdvisoryEngine> = build(EngineKind::B, &db);
let fa = ea.fleet(&policy, &artifacts);
let fb = eb.fleet(&policy, &artifacts);
assert_eq!(fa, fb, "boxed A/B fleet scans must be identical");
}