use dsfb_database::grammar::{replay, MotifClass, MotifEngine, MotifGrammar};
use dsfb_database::perturbation::tpcds_with_perturbations_scaled;
use std::collections::HashMap;
const SCALES: &[f64] = &[0.5, 1.0, 1.5];
#[test]
fn stress_sweep_is_deterministic() {
for &scale in SCALES {
let (s1, _) = tpcds_with_perturbations_scaled(42, scale);
let (s2, _) = tpcds_with_perturbations_scaled(42, scale);
assert_eq!(
s1.fingerprint(),
s2.fingerprint(),
"scale={scale}: residual stream not deterministic across re-runs"
);
let g = MotifGrammar::default();
let e1 = MotifEngine::new(g.clone()).run(&s1);
let e2 = MotifEngine::new(g).run(&s2);
assert_eq!(
replay::fingerprint(&e1),
replay::fingerprint(&e2),
"scale={scale}: episode stream not deterministic across re-runs"
);
}
}
#[test]
fn baseline_scale_emits_expected_episode_count_per_motif() {
let (stream, _windows) = tpcds_with_perturbations_scaled(42, 1.0);
let episodes = MotifEngine::new(MotifGrammar::default()).run(&stream);
let mut counts: HashMap<MotifClass, usize> = HashMap::new();
for ep in &episodes {
*counts.entry(ep.motif).or_insert(0) += 1;
}
let expected: &[(MotifClass, usize)] = &[
(MotifClass::PlanRegressionOnset, 1),
(MotifClass::CardinalityMismatchRegime, 1),
(MotifClass::ContentionRamp, 2),
(MotifClass::CacheCollapse, 1),
(MotifClass::WorkloadPhaseTransition, 1),
];
for (m, want) in expected {
let got = counts.get(m).copied().unwrap_or(0);
assert_eq!(
got, *want,
"baseline scale=1.0: motif {:?} emitted {} episode(s); expected {}",
m, got, want
);
}
let total: usize = expected.iter().map(|(_, c)| c).sum();
assert_eq!(
episodes.len(),
total,
"baseline scale=1.0 should emit exactly {} episodes total; got {}",
total,
episodes.len()
);
}