use super::*;
fn cmp_row(scenario: &str, topo: &str, passed: bool, spread: f64, iters: u64) -> GauntletRow {
let mut r = make_row(scenario, topo, passed, spread);
r.gap_ms = 0;
r.migrations = 0;
r.imbalance_ratio = 0.0;
r.max_dsq_depth = 0;
r.total_iterations = iters;
r
}
#[test]
fn compare_rows_dual_gate_both_must_trigger() {
let rows_a = vec![cmp_row("test_a", "tiny-1llc", true, 10.0, 0)];
let rows_b = vec![cmp_row("test_a", "tiny-1llc", true, 12.0, 0)];
let res = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(res.regressions, 0, "abs gate must block 2.0 < 5.0");
assert_eq!(res.improvements, 0);
assert_eq!(
res.unchanged, 1,
"worst_spread should be classified unchanged"
);
assert!(res.findings.is_empty());
let rows_b2 = vec![cmp_row("test_a", "tiny-1llc", true, 14.0, 0)];
let res2 = compare_rows_by(
&rows_a,
&rows_b2,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(
res2.regressions, 0,
"rel-only is insufficient: abs gate must also fire"
);
assert_eq!(res2.unchanged, 1);
}
#[test]
fn compare_rows_zero_baseline_jump_above_abs_gate_is_a_regression() {
let rows_a = vec![cmp_row("zbase", "tiny-1llc", true, 0.0, 0)];
let rows_b = vec![cmp_row("zbase", "tiny-1llc", true, 10.0, 0)];
let res = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(
res.regressions, 1,
"0 -> 10 worst_spread (>= abs gate 5.0) must be a regression, not \
hidden as unchanged by the zero-baseline relative veto",
);
assert_eq!(res.improvements, 0);
assert_eq!(res.unchanged, 0, "the jump must not be counted unchanged");
assert!(
res.findings
.iter()
.any(|f| f.metric.name == "worst_spread" && f.kind == FindingKind::Regression),
"a worst_spread Regression finding must be emitted; got {:?}",
res.findings
.iter()
.map(|f| (f.metric.name, f.delta))
.collect::<Vec<_>>(),
);
}
#[test]
fn compare_rows_zero_baseline_jump_below_abs_gate_is_unchanged() {
let rows_a = vec![cmp_row("zbase_small", "tiny-1llc", true, 0.0, 0)];
let rows_b = vec![cmp_row("zbase_small", "tiny-1llc", true, 3.0, 0)];
let res = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(
res.regressions, 0,
"0 -> 3 worst_spread (< abs gate 5.0) must stay unchanged",
);
assert_eq!(res.improvements, 0);
assert!(
res.findings.iter().all(|f| f.metric.name != "worst_spread"),
"no worst_spread finding for a sub-abs-gate zero-baseline jump; got {:?}",
res.findings
.iter()
.map(|f| (f.metric.name, f.delta))
.collect::<Vec<_>>(),
);
}
#[test]
fn compare_rows_zero_baseline_jump_above_abs_gate_is_an_improvement() {
let rows_a = vec![cmp_row("zbase_imp", "tiny-1llc", true, 0.0, 0)];
let rows_b = vec![cmp_row("zbase_imp", "tiny-1llc", true, 0.0, 1000)];
let res = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(
res.improvements, 1,
"0 -> 1000 total_iterations (>= abs gate 2, HigherBetter) must be \
an improvement, not hidden as unchanged",
);
assert_eq!(res.regressions, 0);
assert!(
res.findings
.iter()
.any(|f| f.metric.name == "total_iterations" && f.kind == FindingKind::Improvement),
"a total_iterations Improvement finding must be emitted; got {:?}",
res.findings
.iter()
.map(|f| (f.metric.name, f.delta))
.collect::<Vec<_>>(),
);
}
#[test]
fn compare_rows_subinteger_stuck_count_difference_is_unchanged() {
let mut a = make_row("test_a", "tiny-1llc", true, 10.0);
a.stuck_count = 1.4;
let mut b = make_row("test_a", "tiny-1llc", true, 10.0);
b.stuck_count = 1.6;
let res = compare_rows_by(
&[a],
&[b],
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(
res.regressions, 0,
"a 0.2 sub-integer stuck_count delta must NOT be a regression",
);
assert_eq!(res.improvements, 0);
assert!(
res.findings.iter().all(|f| f.metric.name != "stuck_count"),
"stuck_count must not be a finding for a sub-abs delta; got {:?}",
res.findings
.iter()
.map(|f| f.metric.name)
.collect::<Vec<_>>(),
);
}
#[test]
fn compare_rows_genuine_stuck_count_regression_is_flagged() {
let mut a = make_row("test_a", "tiny-1llc", true, 10.0);
a.stuck_count = 1.0;
let mut b = make_row("test_a", "tiny-1llc", true, 10.0);
b.stuck_count = 2.5;
let res = compare_rows_by(
&[a],
&[b],
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(
res.regressions, 1,
"a 1.5 stuck_count delta clears both gates and must be a regression",
);
assert!(
res.findings
.iter()
.any(|f| f.metric.name == "stuck_count" && f.kind == FindingKind::Regression),
"stuck_count must be the flagged regression",
);
}
#[test]
fn compare_rows_synthetic_regression_and_improvement() {
let rows_a = vec![cmp_row("test1", "tiny-1llc", true, 10.0, 1000)];
let rows_b = vec![cmp_row("test1", "tiny-1llc", true, 30.0, 500)];
let res = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::uniform(10.0),
);
assert_eq!(
res.regressions, 2,
"spread up + iterations down both regress"
);
assert_eq!(res.improvements, 0);
assert_eq!(res.excluded_pairs, 0);
let metrics: Vec<&str> = res.findings.iter().map(|d| d.metric.name).collect();
assert!(metrics.contains(&"worst_spread"));
assert!(metrics.contains(&"total_iterations"));
for d in &res.findings {
assert!(
d.kind == FindingKind::Regression,
"all reported deltas should be regressions"
);
assert_eq!(d.scenario, "test1");
assert_eq!(d.topology, "tiny-1llc");
}
let res_imp = compare_rows_by(
&rows_b,
&rows_a,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::uniform(10.0),
);
assert_eq!(res_imp.regressions, 0);
assert_eq!(res_imp.improvements, 2);
for d in &res_imp.findings {
assert!(d.kind != FindingKind::Regression);
}
}
#[test]
fn compare_rows_suppresses_rate_components_not_the_rate() {
let mut a = cmp_row("t", "tiny-1llc", true, 0.0, 1000);
a.ext_metrics
.insert("total_iterations_pooled".to_string(), 1000.0);
a.ext_metrics
.insert("iterations_per_cpu_sec".to_string(), 500.0);
let mut b = cmp_row("t", "tiny-1llc", true, 0.0, 1000);
b.ext_metrics
.insert("total_iterations_pooled".to_string(), 2000.0);
b.ext_metrics
.insert("iterations_per_cpu_sec".to_string(), 1000.0);
let res = compare_rows_by(
&[a],
&[b],
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
let names: Vec<&str> = res.findings.iter().map(|d| d.metric.name).collect();
assert!(
!names.contains(&"total_iterations_pooled"),
"the Rate component must be suppressed from compare findings; got {names:?}",
);
assert!(
names.contains(&"iterations_per_cpu_sec"),
"the user-facing pooled rate must still emit a finding; got {names:?}",
);
}
#[test]
fn compare_rows_higher_is_worse_inversion() {
let rows_a = vec![cmp_row("t", "tiny-1llc", true, 0.0, 1000)];
let rows_b = vec![cmp_row("t", "tiny-1llc", true, 0.0, 500)];
let res = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
let iters_delta = res
.findings
.iter()
.find(|d| d.metric.name == "total_iterations")
.expect("total_iterations should produce a delta");
assert!(
iters_delta.kind == FindingKind::Regression,
"iterations decrease is a regression"
);
assert_eq!(iters_delta.delta, -500.0);
assert_eq!(res.regressions, 1);
assert_eq!(res.improvements, 0);
let rows_a2 = vec![cmp_row("t", "tiny-1llc", true, 10.0, 0)];
let rows_b2 = vec![cmp_row("t", "tiny-1llc", true, 30.0, 0)];
let res_up = compare_rows_by(
&rows_a2,
&rows_b2,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
let spread_up = res_up
.findings
.iter()
.find(|d| d.metric.name == "worst_spread")
.expect("worst_spread should produce a delta");
assert!(
spread_up.kind == FindingKind::Regression,
"spread increase is a regression"
);
assert_eq!(spread_up.delta, 20.0);
let res_down = compare_rows_by(
&rows_b2,
&rows_a2,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
let spread_down = res_down
.findings
.iter()
.find(|d| d.metric.name == "worst_spread")
.expect("worst_spread should produce a delta");
assert!(
spread_down.kind != FindingKind::Regression,
"spread decrease is an improvement"
);
assert_eq!(spread_down.delta, -20.0);
}
#[test]
fn compare_rows_skipped_side_drops_pair_into_excluded_pairs() {
let mut row_a = cmp_row("t", "tiny-1llc", true, 10.0, 100);
let mut row_b = cmp_row("t", "tiny-1llc", true, 10.0, 100);
row_a.skipped = true; let res = compare_rows_by(
&[row_a.clone()],
&[row_b.clone()],
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(res.regressions, 0);
assert_eq!(res.improvements, 0);
assert_eq!(
res.excluded_pairs, 1,
"skipped side must count as excluded_pairs, not produce deltas"
);
row_a.skipped = false;
row_b.skipped = true;
let res = compare_rows_by(
&[row_a],
&[row_b],
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(res.regressions, 0);
assert_eq!(res.improvements, 0);
assert_eq!(res.excluded_pairs, 1);
}
#[test]
fn compare_rows_skips_failed_scenarios() {
let rows_a = vec![
cmp_row("test_ok", "tiny-1llc", true, 10.0, 1000),
cmp_row("test_failed_b", "tiny-1llc", true, 10.0, 1000),
cmp_row("test_failed_a", "tiny-1llc", false, 10.0, 1000),
];
let rows_b = vec![
cmp_row("test_ok", "tiny-1llc", true, 30.0, 500),
cmp_row("test_failed_b", "tiny-1llc", false, 30.0, 500),
cmp_row("test_failed_a", "tiny-1llc", true, 30.0, 500),
];
let res = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::uniform(10.0),
);
assert_eq!(
res.excluded_pairs, 2,
"test_failed_a and test_failed_b skip"
);
assert_eq!(res.regressions, 2);
assert_eq!(res.improvements, 0);
for d in &res.findings {
assert_eq!(d.scenario, "test_ok");
}
}
#[test]
fn compare_rows_filter_substring() {
let rows_a = vec![
cmp_row("alpha", "tiny-1llc", true, 10.0, 0),
cmp_row("beta", "tiny-1llc", true, 10.0, 0),
];
let rows_b = vec![
cmp_row("alpha", "tiny-1llc", true, 30.0, 0),
cmp_row("beta", "tiny-1llc", true, 30.0, 0),
];
let res = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
Some("alpha"),
&ComparisonPolicy::default(),
);
assert_eq!(res.regressions, 1, "only alpha row should compare");
assert_eq!(res.findings.len(), 1);
assert_eq!(res.findings[0].scenario, "alpha");
assert_eq!(res.findings[0].work_type, "SpinWait");
let res_topo = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
Some("tiny"),
&ComparisonPolicy::default(),
);
assert_eq!(res_topo.regressions, 2, "both rows match 'tiny' topology");
assert_eq!(res_topo.findings.len(), 2);
let res_none = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
Some("nomatch"),
&ComparisonPolicy::default(),
);
assert_eq!(res_none.regressions, 0);
assert_eq!(res_none.improvements, 0);
assert_eq!(res_none.unchanged, 0);
assert_eq!(res_none.excluded_pairs, 0);
}
#[test]
fn compare_rows_threshold_override() {
let rows_a = vec![cmp_row("t", "tiny-1llc", true, 100.0, 0)];
let rows_b = vec![cmp_row("t", "tiny-1llc", true, 106.0, 0)];
let res_default = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
let spread_default = res_default
.findings
.iter()
.find(|d| d.metric.name == "worst_spread");
assert!(
spread_default.is_none(),
"default rel 0.25 must classify 6% change as unchanged"
);
let res_override = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::uniform(5.0),
);
let spread_override = res_override
.findings
.iter()
.find(|d| d.metric.name == "worst_spread")
.expect("override 5% must surface 6% spread change");
assert!(spread_override.kind == FindingKind::Regression);
assert_eq!(spread_override.delta, 6.0);
let rows_a_small = vec![cmp_row("t", "tiny-1llc", true, 1.0, 0)];
let rows_b_small = vec![cmp_row("t", "tiny-1llc", true, 1.5, 0)];
let res_small = compare_rows_by(
&rows_a_small,
&rows_b_small,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::uniform(1.0),
);
assert!(
!res_small
.findings
.iter()
.any(|d| d.metric.name == "worst_spread"),
"abs gate must still block tiny absolute moves"
);
}
#[test]
fn comparison_policy_rel_threshold_resolution_priority() {
let empty = ComparisonPolicy::default();
assert_eq!(
empty.rel_threshold("worst_spread", 0.25),
0.25,
"empty policy must fall through to the registry default_rel",
);
let uniform = ComparisonPolicy::uniform(10.0);
assert_eq!(
uniform.rel_threshold("worst_spread", 0.25),
0.10,
"uniform(10.0) must override the registry default_rel \
with 10.0 / 100.0 = 0.10",
);
let mut per_metric = ComparisonPolicy::uniform(10.0);
per_metric
.per_metric_percent
.insert("worst_spread".to_string(), 5.0);
assert_eq!(
per_metric.rel_threshold("worst_spread", 0.25),
0.05,
"per-metric override (5.0) must win over default_percent \
(10.0) and the registry default (0.25)",
);
assert_eq!(
per_metric.rel_threshold("worst_gap_ms", 0.25),
0.10,
"metrics not in the per-metric map must still see the \
default_percent (10.0 → 0.10), not the registry default",
);
}
#[test]
fn wake_latency_tail_ratio_compares_via_ext_metrics() {
let metric = metric_def("worst_wake_latency_tail_ratio")
.expect("worst_wake_latency_tail_ratio must be registered in METRICS");
let key = "worst_wake_latency_tail_ratio";
let low_a = make_row("tail_low", "tiny-1llc", true, 0.0);
let low_b = make_row("tail_low", "tiny-1llc", true, 0.0);
assert!(
metric.read(&low_a).is_none(),
"absent ext key must read as None (accessor is |_| None, no ext entry)",
);
let below = compare_rows_by(
std::slice::from_ref(&low_a),
std::slice::from_ref(&low_b),
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(
below.regressions, 0,
"absent tail-ratio key (identical rows) must surface no regression",
);
assert!(
below.findings.is_empty(),
"absent tail-ratio key (identical rows) must emit no findings",
);
let mut hi_a = make_row("tail_hi", "tiny-1llc", true, 0.0);
let mut hi_b = make_row("tail_hi", "tiny-1llc", true, 0.0);
hi_a.ext_metrics.insert(key.to_string(), 2.0);
hi_b.ext_metrics.insert(key.to_string(), 20.0);
assert_eq!(
metric.read(&hi_a),
Some(2.0),
"present ext key must read via the ext fallback",
);
let above = compare_rows_by(
std::slice::from_ref(&hi_a),
std::slice::from_ref(&hi_b),
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(
above.regressions, 1,
"a present-key 10x tail blow-up must surface as a regression; \
threshold wiring has a gap otherwise",
);
}
#[test]
fn compare_rows_both_absent_ext_key_reads_none_and_is_skipped() {
let metric =
metric_def("worst_wake_latency_tail_ratio").expect("tail ratio metric must be registered");
let row_a = make_row("none_branch", "tiny-1llc", true, 0.0);
let row_b = make_row("none_branch", "tiny-1llc", true, 0.0);
assert!(
metric.read(&row_a).is_none(),
"absent ext key must read None on A — otherwise this test is not \
exercising the None branch of compare_rows",
);
assert!(
metric.read(&row_b).is_none(),
"absent ext key must read None on B",
);
let report = compare_rows_by(
std::slice::from_ref(&row_a),
std::slice::from_ref(&row_b),
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(
report.regressions, 0,
"both-absent must not be a regression",
);
assert_eq!(
report.improvements, 0,
"both-absent must not be an improvement",
);
assert!(
report.findings.is_empty(),
"no findings when the metric is absent on both sides; got: {:?}",
report.findings,
);
assert!(
report.coverage_diffs.is_empty(),
"both-absent is skipped, NOT a coverage diff (that is one-sided-absent)",
);
}
#[test]
fn comparison_policy_load_json_round_trip() {
let mut original = ComparisonPolicy::uniform(10.0);
original
.per_metric_percent
.insert("worst_spread".to_string(), 5.0);
original
.per_metric_percent
.insert("worst_p99_wake_latency_us".to_string(), 20.0);
let json = serde_json::to_string(&original).expect("serialize policy");
let tmp = tempfile::NamedTempFile::new().expect("create tempfile");
std::fs::write(tmp.path(), json).expect("write policy file");
let loaded = ComparisonPolicy::load_json(tmp.path()).expect("load policy");
assert_eq!(
loaded.default_percent,
Some(10.0),
"default_percent must round-trip",
);
assert_eq!(
loaded.per_metric_percent.get("worst_spread"),
Some(&5.0),
"per-metric worst_spread override must round-trip",
);
assert_eq!(
loaded.per_metric_percent.get("worst_p99_wake_latency_us"),
Some(&20.0),
"per-metric worst_p99 override must round-trip",
);
for metric_name in ["worst_spread", "worst_p99_wake_latency_us", "worst_gap_ms"] {
assert_eq!(
loaded.rel_threshold(metric_name, 0.25),
original.rel_threshold(metric_name, 0.25),
"load_json round-trip must preserve threshold \
resolution for {metric_name}",
);
}
}
#[test]
fn comparison_policy_load_json_nonexistent_path_surfaces_path() {
let path = std::path::Path::new("/nonexistent/ktstr/policy-DOES-NOT-EXIST.json");
let err = ComparisonPolicy::load_json(path).expect_err("nonexistent path must fail");
let rendered = format!("{err:#}");
assert!(
rendered.contains(&path.display().to_string()),
"error must name the missing path so a user can see \
which file was expected; got: {rendered}",
);
assert!(
rendered.to_ascii_lowercase().contains("read")
|| rendered.to_ascii_lowercase().contains("no such"),
"error must describe the read failure (either the \
`with_context` \"read comparison policy from ...\" \
prefix or std's underlying \"No such file...\" \
reason); got: {rendered}",
);
}
#[test]
fn comparison_policy_load_json_malformed_json_surfaces_path_and_parse_context() {
let tmp = tempfile::NamedTempFile::new().expect("tempfile");
std::fs::write(tmp.path(), "this is not json at all {{{").expect("write");
let err = ComparisonPolicy::load_json(tmp.path()).expect_err("malformed JSON must fail");
let rendered = format!("{err:#}");
assert!(
rendered.contains(&tmp.path().display().to_string()),
"malformed-JSON error must name the path; got: {rendered}",
);
assert!(
rendered.to_ascii_lowercase().contains("parse")
|| rendered.to_ascii_lowercase().contains("expected"),
"malformed-JSON error must include a parse-context \
hint (either the `with_context` \"parse comparison \
policy from ...\" prefix, or serde_json's \"expected \
...\" reason); got: {rendered}",
);
}
#[test]
fn comparison_policy_load_json_rejects_unknown_fields() {
let tmp = tempfile::NamedTempFile::new().expect("tempfile");
std::fs::write(tmp.path(), r#"{"default_percentage": 10.0}"#).expect("write");
let err = ComparisonPolicy::load_json(tmp.path()).expect_err("unknown field must fail");
let rendered = format!("{err:#}");
assert!(
rendered.contains("default_percentage")
|| rendered.to_ascii_lowercase().contains("unknown"),
"unknown-field error must name the typo so a user \
can fix the policy file; got: {rendered}",
);
}
#[test]
fn comparison_policy_validate_rejects_negative_default_percent() {
let policy = ComparisonPolicy::uniform(-10.0);
let err = policy
.validate()
.expect_err("negative default_percent must fail validation");
let rendered = format!("{err:#}");
assert!(
rendered.contains("default_percent"),
"validation error must name the field; got: {rendered}",
);
assert!(
rendered.contains("-10"),
"validation error must echo the rejected value; got: {rendered}",
);
}
#[test]
fn comparison_policy_validate_rejects_unknown_per_metric_keys() {
let mut policy = ComparisonPolicy::default();
policy
.per_metric_percent
.insert("wrost_spread".to_string(), 5.0); let err = policy
.validate()
.expect_err("unknown per-metric key must fail validation");
let rendered = format!("{err:#}");
assert!(
rendered.contains("wrost_spread"),
"validation error must echo the unknown key so a user \
can see the typo; got: {rendered}",
);
assert!(
rendered.contains("worst_spread"),
"validation error should include the registered \
metric list so users can find the right spelling; \
got: {rendered}",
);
}
#[test]
fn comparison_policy_validate_rejects_negative_per_metric_value() {
let mut policy = ComparisonPolicy::default();
policy
.per_metric_percent
.insert("worst_spread".to_string(), -5.0);
let err = policy
.validate()
.expect_err("negative per-metric percent must fail");
let rendered = format!("{err:#}");
assert!(
rendered.contains("worst_spread") && rendered.contains("-5"),
"validation error must name both the key and the \
rejected value; got: {rendered}",
);
}
#[test]
fn comparison_policy_load_json_accepts_partial_fields() {
let tmp = tempfile::NamedTempFile::new().expect("create tempfile");
std::fs::write(tmp.path(), "{}").expect("write empty policy");
let loaded = ComparisonPolicy::load_json(tmp.path()).expect("load empty policy");
assert_eq!(loaded.default_percent, None);
assert!(loaded.per_metric_percent.is_empty());
std::fs::write(tmp.path(), r#"{"default_percent": 7.5}"#).expect("write partial policy");
let loaded = ComparisonPolicy::load_json(tmp.path()).expect("load partial policy");
assert_eq!(loaded.default_percent, Some(7.5));
assert!(loaded.per_metric_percent.is_empty());
std::fs::write(
tmp.path(),
r#"{"per_metric_percent": {"worst_spread": 3.0}}"#,
)
.expect("write per-metric-only policy");
let loaded = ComparisonPolicy::load_json(tmp.path()).expect("load per-metric-only policy");
assert_eq!(loaded.default_percent, None);
assert_eq!(loaded.per_metric_percent.get("worst_spread"), Some(&3.0),);
}
#[test]
fn comparison_policy_from_cli_flags_resolves_each_branch() {
let p = ComparisonPolicy::from_cli_flags(Some(15.0), None).expect("threshold resolves");
assert_eq!(p.default_percent, Some(15.0));
assert!(p.per_metric_percent.is_empty());
assert!(
ComparisonPolicy::from_cli_flags(Some(-1.0), None).is_err(),
"negative --threshold must be rejected before the dual-gate math",
);
let tmp = tempfile::NamedTempFile::new().expect("tempfile");
std::fs::write(tmp.path(), r#"{"default_percent": 8.0}"#).expect("write policy");
let p = ComparisonPolicy::from_cli_flags(None, Some(tmp.path())).expect("policy file resolves");
assert_eq!(p.default_percent, Some(8.0));
let p = ComparisonPolicy::from_cli_flags(None, None).expect("default resolves");
assert_eq!(p.default_percent, None);
assert!(
ComparisonPolicy::from_cli_flags(Some(10.0), Some(tmp.path())).is_err(),
"--threshold + --policy together must error",
);
}
#[test]
fn compare_rows_per_metric_policy_resolves_each_metric_independently() {
let mut row_a = cmp_row("t", "tiny-1llc", true, 100.0, 0);
row_a
.ext_metrics
.insert("worst_median_wake_latency_us".to_string(), 100.0);
let mut row_b = cmp_row("t", "tiny-1llc", true, 106.0, 0);
row_b
.ext_metrics
.insert("worst_median_wake_latency_us".to_string(), 110.0);
let mut policy = ComparisonPolicy::uniform(20.0);
policy
.per_metric_percent
.insert("worst_spread".to_string(), 5.0);
let res = compare_rows_by(&[row_a], &[row_b], LEGACY_PAIRING_DIMS, None, &policy);
let spread_finding = res
.findings
.iter()
.find(|f| f.metric.name == "worst_spread");
assert!(
spread_finding.is_some(),
"worst_spread per-metric override (5%) must fire on 6% \
delta; got findings: {:?}",
res.findings
.iter()
.map(|f| f.metric.name)
.collect::<Vec<_>>(),
);
let spread_finding = spread_finding.unwrap();
assert!(
spread_finding.kind == FindingKind::Regression,
"6% > 5% → regression"
);
let wake_finding = res
.findings
.iter()
.find(|f| f.metric.name == "worst_median_wake_latency_us");
assert!(
wake_finding.is_none(),
"worst_median_wake_latency_us 10% delta must fall \
under default_percent 20% and be unchanged. The \
regression would indicate `compare_rows` ignored \
default_percent for non-per-metric entries; got \
finding: {wake_finding:?}",
);
assert_eq!(
res.regressions, 1,
"exactly one regression expected — the per-metric \
spread override should win on spread, and the \
default_percent should suppress wake latency. Got: \
regressions={}, improvements={}, unchanged={}",
res.regressions, res.improvements, res.unchanged,
);
}
#[test]
fn compare_rows_duplicate_key_first_match_wins() {
let rows_a = vec![
cmp_row("t", "tiny-1llc", true, 10.0, 0),
cmp_row("t", "tiny-1llc", true, 29.0, 0),
];
let rows_b = vec![cmp_row("t", "tiny-1llc", true, 30.0, 0)];
let res = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(res.regressions, 1, "first match (spread=10) must win");
let spread = res
.findings
.iter()
.find(|d| d.metric.name == "worst_spread")
.expect("worst_spread regression should fire");
assert_eq!(
spread.val_a, 10.0,
"val_a must come from the first matching row"
);
assert_eq!(spread.delta, 20.0);
}
#[test]
fn compare_rows_filter_excludes_failed_from_skip_count() {
let rows_a = vec![
cmp_row("alpha", "tiny-1llc", true, 10.0, 0),
cmp_row("beta", "tiny-1llc", false, 10.0, 0),
];
let rows_b = vec![
cmp_row("alpha", "tiny-1llc", true, 30.0, 0),
cmp_row("beta", "tiny-1llc", true, 30.0, 0),
];
let unfiltered = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(unfiltered.excluded_pairs, 1);
assert_eq!(unfiltered.regressions, 1, "alpha still regresses");
let filtered = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
Some("alpha"),
&ComparisonPolicy::default(),
);
assert_eq!(filtered.excluded_pairs, 0);
assert_eq!(filtered.regressions, 1);
assert_eq!(filtered.findings.len(), 1);
assert_eq!(filtered.findings[0].scenario, "alpha");
}
#[test]
fn compare_rows_filter_substring_matches_scheduler() {
let mut a1 = cmp_row("test1", "tiny-1llc", true, 10.0, 0);
a1.scheduler = "scx_alpha".into();
let mut a2 = cmp_row("test2", "tiny-1llc", true, 10.0, 0);
a2.scheduler = "scx_beta".into();
let mut b1 = cmp_row("test1", "tiny-1llc", true, 30.0, 0);
b1.scheduler = "scx_alpha".into();
let mut b2 = cmp_row("test2", "tiny-1llc", true, 30.0, 0);
b2.scheduler = "scx_beta".into();
let res = compare_rows_by(
&[a1, a2],
&[b1, b2],
LEGACY_PAIRING_DIMS,
Some("scx_alpha"),
&ComparisonPolicy::default(),
);
assert_eq!(res.regressions, 1, "only the scx_alpha row compares");
assert_eq!(res.findings.len(), 1);
assert_eq!(res.findings[0].scenario, "test1");
assert_eq!(res.new_in_b, 0);
assert_eq!(res.removed_from_a, 0);
}
#[test]
fn compare_rows_tracks_new_and_removed_rows() {
let rows_a = vec![
cmp_row("alpha", "tiny-1llc", true, 10.0, 0),
cmp_row("gamma", "tiny-1llc", true, 10.0, 0),
];
let rows_b = vec![
cmp_row("alpha", "tiny-1llc", true, 30.0, 0),
cmp_row("beta", "tiny-1llc", true, 30.0, 0),
];
let res = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(res.regressions, 1, "alpha regresses on worst_spread");
assert_eq!(res.new_in_b, 1, "beta is new on B side");
assert_eq!(res.removed_from_a, 1, "gamma is removed on B side");
assert_eq!(res.excluded_pairs, 0);
}
#[test]
fn compare_rows_filter_applies_to_new_and_removed_counters() {
let rows_a = vec![
cmp_row("alpha", "tiny-1llc", true, 10.0, 0),
cmp_row("gamma", "tiny-1llc", true, 10.0, 0),
];
let rows_b = vec![
cmp_row("alpha", "tiny-1llc", true, 30.0, 0),
cmp_row("beta", "tiny-1llc", true, 30.0, 0),
];
let res = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
Some("alpha"),
&ComparisonPolicy::default(),
);
assert_eq!(res.regressions, 1);
assert_eq!(res.new_in_b, 0, "beta is filtered out, not new");
assert_eq!(res.removed_from_a, 0, "gamma is filtered out, not removed");
}
fn host_ctx(release: &str, kernel_cmdline: Option<&str>) -> crate::host_context::HostContext {
crate::host_context::HostContext {
kernel_name: Some("Linux".to_string()),
kernel_release: Some(release.to_string()),
kernel_cmdline: kernel_cmdline.map(str::to_string),
..Default::default()
}
}
#[test]
fn format_host_delta_both_present_identical() {
let ctx = host_ctx("6.14.0", Some("preempt=lazy"));
let out = format_host_delta(Some(&ctx), Some(&ctx), "a-run", "b-run");
assert_eq!(out, "\nhost: identical between 'a-run' and 'b-run'\n");
}
#[test]
fn format_host_delta_both_present_differ() {
let ha = host_ctx("6.14.0", Some("preempt=lazy"));
let hb = host_ctx("6.15.1", Some("preempt=lazy"));
let out = format_host_delta(Some(&ha), Some(&hb), "a", "b");
assert!(
out.starts_with("\nhost delta ('a' → 'b'):\n"),
"got: {out:?}"
);
let body = &out["\nhost delta ('a' → 'b'):\n".len()..];
assert!(
!body.is_empty(),
"differing contexts must produce a diff body"
);
assert!(
out.ends_with('\n'),
"differ arm must end with a newline for contiguous-section output: {out:?}",
);
}
#[test]
fn format_host_delta_left_only() {
let ctx = host_ctx("6.14.0", Some("preempt=lazy"));
let out = format_host_delta(Some(&ctx), None, "a-run", "b-run");
assert_eq!(out, "\nhost: captured in 'a-run' only, delta unavailable\n");
}
#[test]
fn format_host_delta_right_only() {
let ctx = host_ctx("6.14.0", Some("preempt=lazy"));
let out = format_host_delta(None, Some(&ctx), "a-run", "b-run");
assert_eq!(out, "\nhost: captured in 'b-run' only, delta unavailable\n");
}
#[test]
fn format_host_delta_both_absent_emits_nothing() {
assert_eq!(format_host_delta(None, None, "a", "b"), "");
}
#[test]
fn format_host_delta_identical_with_arch_surfaces_arch() {
let ctx = crate::host_context::HostContext {
kernel_name: Some("Linux".to_string()),
arch: Some("x86_64".to_string()),
..Default::default()
};
let out = format_host_delta(Some(&ctx), Some(&ctx), "a", "b");
assert_eq!(
out,
"\nhost: identical between 'a' and 'b' (arch: x86_64)\n",
);
}
#[test]
fn format_host_delta_identical_partial_arch_falls_back() {
let ha = crate::host_context::HostContext {
kernel_name: Some("Linux".to_string()),
arch: Some("x86_64".to_string()),
..Default::default()
};
let hb = crate::host_context::HostContext {
kernel_name: Some("Linux".to_string()),
arch: None,
..Default::default()
};
let out = format_host_delta(Some(&ha), Some(&hb), "a", "b");
assert!(
out.starts_with("\nhost delta ('a' → 'b'):\n"),
"asymmetric arch must route through differ arm, not \
identical arm: {out:?}",
);
assert!(
out.contains("arch:"),
"differ arm must surface the arch row: {out:?}",
);
}
#[test]
fn format_host_delta_identical_both_arch_none_falls_back() {
let ctx = crate::host_context::HostContext {
kernel_name: Some("Linux".to_string()),
arch: None,
..Default::default()
};
let out = format_host_delta(Some(&ctx), Some(&ctx), "a", "b");
assert_eq!(out, "\nhost: identical between 'a' and 'b'\n");
}
#[test]
fn gauntlet_row_empty_ext_metrics_omits_key() {
let row = make_row("scn", "topo", true, 0.0);
assert!(row.ext_metrics.is_empty());
let json = serde_json::to_string(&row).unwrap();
assert!(
!json.contains("\"ext_metrics\""),
"empty ext_metrics must be omitted from JSON: {json}"
);
}
#[test]
fn gauntlet_row_non_empty_ext_metrics_emits_payload() {
let mut row = make_row("scn", "topo", true, 0.0);
row.ext_metrics.insert("custom_metric".into(), 42.5);
let json = serde_json::to_string(&row).unwrap();
assert!(
json.contains("\"custom_metric\":42.5"),
"ext_metrics payload missing: {json}"
);
}
#[test]
fn gauntlet_row_round_trip_empty_ext_metrics() {
let row = make_row("scn", "topo", true, 1.5);
let json = serde_json::to_string(&row).unwrap();
let back: GauntletRow = serde_json::from_str(&json).unwrap();
assert_eq!(back, row);
assert!(back.ext_metrics.is_empty());
}
#[test]
fn gauntlet_row_round_trip_non_empty_ext_metrics() {
let mut row = make_row("scn", "topo", false, std::f64::consts::PI);
row.ext_metrics.insert("m1".into(), 1.0);
row.ext_metrics.insert("m2".into(), 2.5);
let json = serde_json::to_string(&row).unwrap();
let back: GauntletRow = serde_json::from_str(&json).unwrap();
assert_eq!(back, row);
}
#[test]
fn gauntlet_row_round_trip_populated_cpu_budget() {
let mut row = make_row("scn", "topo", true, 1.0);
row.cpu_budget = Some(4);
row.vcpus = Some(16);
let json = serde_json::to_string(&row).unwrap();
assert!(
json.contains("\"cpu_budget\":4") && json.contains("\"vcpus\":16"),
"populated budget/vcpus must emit numeric JSON keys: {json}"
);
let back: GauntletRow = serde_json::from_str(&json).unwrap();
assert_eq!(back, row);
assert_eq!(back.cpu_budget, Some(4));
assert_eq!(back.vcpus, Some(16));
}
#[test]
fn gauntlet_row_none_cpu_budget_omits_keys() {
let row = make_row("scn", "topo", true, 1.0);
assert!(row.cpu_budget.is_none() && row.vcpus.is_none());
let json = serde_json::to_string(&row).unwrap();
assert!(
!json.contains("\"cpu_budget\"") && !json.contains("\"vcpus\""),
"None budget/vcpus must be omitted from JSON: {json}"
);
let back: GauntletRow = serde_json::from_str(&json).unwrap();
assert_eq!(back, row);
}
#[test]
fn compare_partitions_threads_dir_through_to_pool_collection() {
use crate::test_support::SidecarResult;
let alt_root = tempfile::TempDir::new().expect("create alt-root tempdir");
for (run_key, pc) in [
("__dir_thread_a__", "aaaaaa1"),
("__dir_thread_b__", "bbbbbb2"),
] {
let run_dir = alt_root.path().join(run_key);
std::fs::create_dir_all(&run_dir).expect("create run dir");
let sidecar = SidecarResult {
test_name: "dir_thread_fixture".to_string(),
project_commit: Some(pc.to_string()),
..SidecarResult::test_fixture()
};
let json = serde_json::to_string(&sidecar).expect("serialize fixture sidecar");
let sidecar_path = run_dir.join(format!("{run_key}.ktstr.json"));
std::fs::write(&sidecar_path, json).expect("write fixture sidecar");
}
let filter_a = RowFilter {
project_commits: vec!["aaaaaa1".to_string()],
..RowFilter::default()
};
let filter_b = RowFilter {
project_commits: vec!["bbbbbb2".to_string()],
..RowFilter::default()
};
let exit = compare_partitions(
&filter_a,
&filter_b,
None,
&ComparisonPolicy::default(),
Some(alt_root.path()),
&crate::stats::GateOptions::default(),
)
.expect("compare_partitions must pool sidecars under --dir override");
assert_eq!(
exit, 0,
"byte-identical metrics across the two project-commit \
partitions must yield zero regressions (exit 0). \
A non-zero exit means either the partitions loaded \
different data than written above or compare_rows \
regressed on identical inputs.",
);
}
#[test]
fn compare_partitions_noise_pools_duplicate_pairing_keys() {
use crate::test_support::SidecarResult;
let alt_root = tempfile::TempDir::new().expect("create alt-root tempdir");
for (pc, tag) in [("aaaaaa1", "base"), ("bbbbbb2", "head")] {
for i in 0..3 {
let run_dir = alt_root.path().join(format!("noise_{tag}_{i}"));
std::fs::create_dir_all(&run_dir).expect("create run dir");
let sidecar = SidecarResult {
test_name: "noise_dup_fixture".to_string(),
project_commit: Some(pc.to_string()),
..SidecarResult::test_fixture()
};
let json = serde_json::to_string(&sidecar).expect("serialize fixture sidecar");
std::fs::write(run_dir.join(format!("noise_{tag}_{i}.ktstr.json")), json)
.expect("write fixture sidecar");
}
}
let filter_a = RowFilter {
project_commits: vec!["aaaaaa1".to_string()],
..RowFilter::default()
};
let filter_b = RowFilter {
project_commits: vec!["bbbbbb2".to_string()],
..RowFilter::default()
};
let exit = compare_partitions_noise(
&filter_a,
&filter_b,
Some(alt_root.path()),
1.0,
&PhaseDisplayOptions::default(),
&crate::stats::GateOptions::default(),
)
.expect("noise compare must pool N duplicate-key runs per side, not reject them");
assert_eq!(
exit, 0,
"identical metrics across the two sides must yield no confident \
regression (exit 0); an Err means the old dup-key gate still \
rejects the pooled per-run rows before noise_findings sees them",
);
}
#[test]
fn render_dirty_warning_silent_when_no_dirty_commits() {
let mut row = make_row("scn", "topo", true, 1.0);
row.commit = Some("abcdef1".into());
row.kernel_commit = Some("0123456".into());
let other = row.clone();
assert!(
super::render_dirty_warning(&[row], &[other]).is_none(),
"clean rows on both sides must yield no warning"
);
}
#[test]
fn render_dirty_warning_silent_on_empty_inputs() {
assert!(
super::render_dirty_warning(&[], &[]).is_none(),
"empty inputs must yield no warning"
);
}
#[test]
fn render_dirty_warning_kernel_only_dedupes_values_across_sides() {
let mut a = make_row("scn", "topo", true, 1.0);
a.kernel_commit = Some("aaaaaaa-dirty".into());
a.commit = Some("clean01".into());
let mut a2 = make_row("scn2", "topo", true, 1.0);
a2.kernel_commit = Some("aaaaaaa-dirty".into()); let mut b = make_row("scn", "topo", true, 1.0);
b.kernel_commit = Some("bbbbbbb-dirty".into());
let text = super::render_dirty_warning(&[a, a2], &[b])
.expect("dirty kernel_commit must yield warning");
assert!(
text.contains("warning: comparison includes dirty builds:"),
"missing header in {text:?}"
);
assert_eq!(
text.matches("kernel source: aaaaaaa-dirty").count(),
1,
"duplicate kernel_commit must be deduped, got {text:?}"
);
assert!(
text.contains("kernel source: bbbbbbb-dirty"),
"second distinct dirty kernel_commit must be listed, got {text:?}"
);
assert!(
!text.contains("project:"),
"no -dirty project commit; the project line must not appear: {text:?}"
);
assert!(
text.contains("Dirty runs overwrite previous results with the same HEAD."),
"missing trailer line 1 in {text:?}"
);
assert!(
text.contains("Commit changes for reproducible-ish comparisons."),
"missing trailer line 2 in {text:?}"
);
}
#[test]
fn render_dirty_warning_project_only_omits_kernel_section() {
let mut a = make_row("scn", "topo", true, 1.0);
a.commit = Some("ccccccc-dirty".into());
let text = super::render_dirty_warning(&[a], &[]).expect("dirty commit must yield warning");
assert!(
text.contains("project: ccccccc-dirty"),
"expected project line in {text:?}"
);
assert!(
!text.contains("kernel source:"),
"kernel section must not appear when only project is dirty: {text:?}"
);
}
#[test]
fn render_dirty_warning_both_dimensions_in_stable_order() {
let mut a = make_row("scn", "topo", true, 1.0);
a.kernel_commit = Some("kkkkk22-dirty".into());
a.commit = Some("pppp222-dirty".into());
let mut b = make_row("scn", "topo", true, 1.0);
b.kernel_commit = Some("kkkkk11-dirty".into());
b.commit = Some("pppp111-dirty".into());
let text =
super::render_dirty_warning(&[a], &[b]).expect("both dimensions dirty must yield warning");
let kernel11 = text
.find("kernel source: kkkkk11-dirty")
.expect("kernel11 line absent");
let kernel22 = text
.find("kernel source: kkkkk22-dirty")
.expect("kernel22 line absent");
let project11 = text
.find("project: pppp111-dirty")
.expect("project11 line absent");
let project22 = text
.find("project: pppp222-dirty")
.expect("project22 line absent");
assert!(
kernel11 < kernel22,
"kernel section must list values in lex order: {text:?}"
);
assert!(
project11 < project22,
"project section must list values in lex order: {text:?}"
);
assert!(
kernel22 < project11,
"kernel section must precede project section: {text:?}"
);
}
#[test]
fn render_dirty_warning_skips_none_and_clean_values() {
let mut clean_a = make_row("a", "topo", true, 1.0);
clean_a.commit = Some("clean01".into());
clean_a.kernel_commit = None;
let mut dirty_b = make_row("b", "topo", true, 1.0);
dirty_b.commit = None;
dirty_b.kernel_commit = Some("dddddd1-dirty".into());
let text = super::render_dirty_warning(&[clean_a], &[dirty_b])
.expect("at least one dirty value must yield warning");
assert!(
text.contains("kernel source: dddddd1-dirty"),
"dirty kernel_commit must surface in {text:?}"
);
assert!(
!text.contains("project:"),
"no dirty project commit; project section must be absent in {text:?}"
);
assert!(
!text.contains("clean01"),
"clean commit values must not appear in {text:?}"
);
}
fn budget_row(scenario: &str, budget: Option<u32>, vcpus: Option<u32>) -> GauntletRow {
let mut r = make_row(scenario, "topo", true, 1.0);
r.cpu_budget = budget;
r.vcpus = vcpus;
r
}
#[test]
fn render_overcommit_warning_none_when_clean() {
let pairing: &[Dimension] = &[Dimension::CpuBudget];
let sliced: &[Dimension] = &[];
let a = budget_row("a", Some(16), Some(16));
let b = budget_row("b", Some(32), Some(16)); assert!(
super::render_overcommit_warning(
std::slice::from_ref(&a),
std::slice::from_ref(&b),
pairing,
)
.is_none()
);
assert!(super::render_overcommit_warning(&[a], &[b], sliced).is_none());
}
#[test]
fn render_overcommit_warning_ignores_skip_rows() {
let sliced: &[Dimension] = &[];
let a = budget_row("a", None, None);
let b = budget_row("b", None, None);
assert!(super::render_overcommit_warning(&[a], &[b], sliced).is_none());
}
#[test]
fn render_overcommit_warning_flags_overcommitted_side() {
let pairing: &[Dimension] = &[Dimension::CpuBudget];
let a = budget_row("a", Some(4), Some(16));
let b = budget_row("b", Some(16), Some(16));
let text = super::render_overcommit_warning(&[a], &[b], pairing)
.expect("an overcommitted A row must warn");
assert!(text.contains("side A"), "must name side A: {text}");
assert!(text.contains("4/16"), "must list budget/vcpus: {text}");
assert!(
text.contains("run-delay"),
"warning must list run-delay as confounded: {text}",
);
assert!(
!text.contains("side B"),
"the clean B side must not be flagged: {text}",
);
}
#[test]
fn render_overcommit_warning_mixed_budget_per_group() {
let pairing: &[Dimension] = &[Dimension::CpuBudget];
let sliced = Dimension::pairing_dims(&[Dimension::CpuBudget]);
let a = budget_row("a", Some(16), Some(16));
let b1 = budget_row("b", Some(8), Some(16)); let b2 = budget_row("b", Some(16), Some(16));
let paired = super::render_overcommit_warning(
std::slice::from_ref(&a),
&[b1.clone(), b2.clone()],
pairing,
)
.expect("overcommitted B row still warns");
assert!(
paired.contains("8/16") && !paired.contains("share a pairing group"),
"pairing dim: overcommit flagged, no mixed-fold warning: {paired}",
);
let sliced_same = super::render_overcommit_warning(&[a], &[b1, b2], &sliced)
.expect("mixed budgets in one group on a sliced side must warn");
assert!(
sliced_same.contains("share a pairing group") && sliced_same.contains("side B"),
"sliced same-key: must warn B's budgets share a pairing group: {sliced_same}",
);
let mut s1 = budget_row("c", Some(16), Some(16));
s1.scheduler = "sched_a".to_string();
let mut s2 = budget_row("c", Some(32), Some(32));
s2.scheduler = "sched_b".to_string();
let clean_a = budget_row("d", Some(16), Some(16));
assert!(
super::render_overcommit_warning(&[s1, s2], std::slice::from_ref(&clean_a), &sliced)
.is_none(),
"two budgets differing on a non-budget pairing dim (scheduler) key \
separate groups -> no fold -> no warning",
);
let xa = budget_row("x", Some(16), Some(16));
let ya = budget_row("y", Some(32), Some(32));
let clean_b = budget_row("z", Some(16), Some(16));
assert!(
super::render_overcommit_warning(&[xa, ya], std::slice::from_ref(&clean_b), &sliced)
.is_none(),
"one side spanning budgets across distinct scenarios -> no fold -> no warning",
);
}
#[test]
fn render_overcommit_warning_mixed_no_overcommit_uses_else_banner() {
let sliced = Dimension::pairing_dims(&[Dimension::CpuBudget]);
let b1 = budget_row("m", Some(16), Some(16));
let b2 = budget_row("m", Some(32), Some(32));
let clean = budget_row("n", Some(16), Some(16));
let text = super::render_overcommit_warning(&[b1, b2], std::slice::from_ref(&clean), &sliced)
.expect("two non-overcommit budgets folding into one group must warn");
assert!(
text.contains("mixing two measurement conditions"),
"no-overcommit mixed-budget case must use the else-branch banner, \
not the host-overcommit banner; got: {text}",
);
assert!(
!text.contains("host-overcommitted run"),
"the else branch must NOT claim a host-overcommitted run; got: {text}",
);
assert!(
text.contains("side A") && text.contains("share a pairing group"),
"the folded side must be named with its mixed budgets; got: {text}",
);
}
fn noise_side(scenario: &str, spread: f64, iters: u64) -> Vec<GauntletRow> {
vec![
cmp_row(scenario, "tiny-1llc", true, spread, iters),
cmp_row(scenario, "tiny-1llc", true, spread, iters),
cmp_row(scenario, "tiny-1llc", true, spread, iters),
]
}
#[test]
fn noise_findings_rate_centroid_is_pooled_not_mean_of_ratios() {
let mk = |run_delay: f64, wall: f64| {
let mut r = cmp_row("rate", "tiny-1llc", true, 10.0, 0);
r.ext_metrics
.insert("total_run_delay".to_string(), run_delay);
r.ext_metrics
.insert("total_schedstat_wall_sec".to_string(), wall);
r
};
let a = vec![mk(100.0, 1.0), mk(100.0, 10.0)];
let b = vec![mk(100.0, 1.0), mk(100.0, 10.0)];
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 1.0, true);
let f = rep
.findings
.iter()
.find(|f| f.metric.name == "run_delay_per_sec")
.unwrap_or_else(|| {
panic!(
"run_delay_per_sec must appear: {:?}",
rep.findings
.iter()
.map(|f| f.metric.name)
.collect::<Vec<_>>(),
)
});
assert!(
(f.verdict.a.mean - 200.0 / 11.0).abs() < 1e-6,
"Rate centroid must be pooled Σnum/Σden (18.18), got {} (mean-of-ratios would be 55)",
f.verdict.a.mean,
);
}
#[test]
fn noise_findings_derives_schedstat_rate_from_per_run_components() {
let mk = |run_delay: f64| {
let mut r = cmp_row("sched", "tiny-1llc", true, 10.0, 0);
r.ext_metrics
.insert("total_run_delay".to_string(), run_delay);
r.ext_metrics.insert("total_pcount".to_string(), 1000.0);
r
};
let a = vec![mk(1_000_000.0), mk(1_000_000.0)]; let b = vec![mk(2_000_000.0), mk(2_000_000.0)]; let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 1.0, false);
assert_eq!(rep.paired_scenarios, 1);
let rate = rep
.findings
.iter()
.find(|f| f.metric.name == "total_run_delay_ns_per_sched")
.unwrap_or_else(|| {
panic!(
"derived schedstat rate must appear in noise findings: {:?}",
rep.findings
.iter()
.map(|f| f.metric.name)
.collect::<Vec<_>>(),
)
});
assert_eq!(
rate.kind,
NoiseKind::Regression,
"run-delay-per-schedule doubling (LowerBetter) must gate as a confident regression",
);
}
#[test]
fn noise_findings_excludes_failed_run_from_the_spread_pool() {
let a = noise_side("fx", 10.0, 2000);
let b = vec![
cmp_row("fx", "tiny-1llc", true, 10.0, 2000),
cmp_row("fx", "tiny-1llc", true, 10.0, 2000),
cmp_row("fx", "tiny-1llc", false, 10.0, 990), ];
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 1.0, false);
assert_eq!(rep.paired_scenarios, 1);
assert_eq!(
rep.regressions(),
0,
"a failed per-run row must be excluded from the pool, not gate a false regression: {:?}",
rep.findings
.iter()
.map(|f| (f.metric.name, f.kind))
.collect::<Vec<_>>(),
);
}
#[test]
fn noise_findings_degenerate_single_sample_side_is_noisy_not_confident() {
let a_one = vec![cmp_row("degen", "tiny-1llc", true, 10.0, 2000)];
let b_three = noise_side("degen", 10.0, 1000); let rep = noise_findings(&a_one, &b_three, LEGACY_PAIRING_DIMS, 1.0, false);
assert_eq!(rep.paired_scenarios, 1);
assert_eq!(
rep.regressions(),
0,
"a single-sample baseline side must NOT yield a confident regression: {:?}",
rep.findings
.iter()
.map(|f| (f.metric.name, f.kind))
.collect::<Vec<_>>(),
);
assert!(
rep.noisy() >= 1,
"the shifted metric(s) must be flagged NOISY because side A realized <2 samples",
);
}
fn phased_rows(
scenario: &str,
n: usize,
buckets: &[crate::assert::PhaseBucket],
) -> Vec<GauntletRow> {
(0..n)
.map(|_| {
let mut r = cmp_row(scenario, "tiny-1llc", true, 10.0, 0);
r.phases = buckets.to_vec();
r
})
.collect()
}
#[test]
fn noise_phase_findings_emits_per_phase_spread() {
let a = phased_rows(
"scn",
3,
&[
make_phase_bucket(0, "BASELINE", &[("max_dsq_depth", 5.0)]),
make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 8.0)]),
],
);
let b = phased_rows(
"scn",
3,
&[
make_phase_bucket(0, "BASELINE", &[("max_dsq_depth", 5.0)]),
make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 20.0)]),
],
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, false);
let s1 = rep
.phase_findings
.iter()
.find(|f| f.step_index == 1 && f.metric.name == "max_dsq_depth")
.expect("Step[1] max_dsq_depth per-phase finding");
assert_eq!((s1.verdict.a.mean, s1.verdict.b.mean), (8.0, 20.0));
assert_eq!(
s1.kind,
NoiseKind::Regression,
"LowerBetter 8->20 rose => per-phase regression",
);
}
#[test]
fn noise_phase_scoped_regression_is_render_only_not_gated() {
let a = phased_rows(
"scn",
3,
&[make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 8.0)])],
);
let b = phased_rows(
"scn",
3,
&[make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 20.0)])],
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, false);
assert_eq!(
rep.phase_regressions(),
1,
"the per-phase shift is a phase regression"
);
assert_eq!(
rep.regressions(),
0,
"no aggregate move -> exit basis unaffected (per-phase is render-only)",
);
}
#[test]
fn noise_phase_rate_pooled_centroid_within_phase() {
let bucket = |iters: f64, sec: f64| {
make_phase_bucket(
1,
"Step[0]",
&[
("total_phase_iterations", iters),
("total_phase_duration_sec", sec),
("iteration_rate", iters / sec),
],
)
};
let side = |scn: &str| {
vec![
{
let mut r = cmp_row(scn, "tiny-1llc", true, 10.0, 0);
r.phases = vec![bucket(100.0, 1.0)];
r
},
{
let mut r = cmp_row(scn, "tiny-1llc", true, 10.0, 0);
r.phases = vec![bucket(100.0, 10.0)];
r
},
]
};
let rep = noise_findings(&side("scn"), &side("scn"), LEGACY_PAIRING_DIMS, 1.0, true);
let f = rep
.phase_findings
.iter()
.find(|f| f.step_index == 1 && f.metric.name == "iteration_rate")
.expect("Step[1] iteration_rate per-phase finding");
assert!(
(f.verdict.a.mean - 200.0 / 11.0).abs() < 1e-6,
"per-phase Rate centroid must be pooled (18.18), got {} (mean-of-ratios would be 55)",
f.verdict.a.mean,
);
}
#[test]
fn noise_phase_excludes_non_pass_run() {
let a = phased_rows(
"scn",
3,
&[make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 8.0)])],
);
let mut b = phased_rows(
"scn",
2,
&[make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 8.0)])],
);
let mut failed = cmp_row("scn", "tiny-1llc", false, 10.0, 0); failed.phases = vec![make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 40.0)])];
b.push(failed);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 1.0, false);
assert_eq!(
rep.phase_regressions(),
0,
"the failed run's outlier phase must be excluded, no false per-phase regression",
);
}
#[test]
fn noise_phase_n_lt_2_is_noisy() {
let mut a = phased_rows("scn", 3, &[make_phase_bucket(1, "Step[0]", &[])]);
a[0].phases = vec![make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 8.0)])];
let b = phased_rows(
"scn",
3,
&[make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 40.0)])],
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 1.0, false);
let f = rep
.phase_findings
.iter()
.find(|f| f.step_index == 1 && f.metric.name == "max_dsq_depth");
assert!(
matches!(f.map(|f| f.kind), Some(NoiseKind::Noisy)),
"a <2-sample per-phase side must be Noisy, got {:?}",
f.map(|f| f.kind),
);
}
#[test]
fn noise_phase_one_sided_metric_is_coverage() {
let a = phased_rows(
"scn",
3,
&[make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 8.0)])],
);
let b = phased_rows("scn", 3, &[make_phase_bucket(1, "Step[0]", &[])]);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 1.0, false);
assert!(
rep.phase_coverage
.iter()
.any(|c| c.metric.map(|m| m.name) == Some("max_dsq_depth")
&& c.present_side == ComparePartition::A),
"A-only max_dsq_depth at a matched step must be a coverage row",
);
assert!(
!rep.phase_findings
.iter()
.any(|f| f.metric.name == "max_dsq_depth"),
"a one-sided metric is coverage, never a finding",
);
}
#[test]
fn noise_phase_one_sided_step_is_coverage() {
let a = phased_rows(
"scn",
3,
&[
make_phase_bucket(0, "BASELINE", &[("max_dsq_depth", 5.0)]),
make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 8.0)]),
],
);
let b = phased_rows(
"scn",
3,
&[make_phase_bucket(0, "BASELINE", &[("max_dsq_depth", 5.0)])],
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 1.0, false);
assert!(
rep.phase_coverage
.iter()
.any(|c| c.step_index == 1 && c.present_side == ComparePartition::A),
"the whole one-sided Step[1] must surface as coverage",
);
assert_eq!(
rep.phase_regressions(),
0,
"matched BASELINE unchanged -> no regression"
);
}
#[test]
fn noise_phase_empty_one_sided_step_surfaces_as_shape_coverage() {
let a = phased_rows(
"scn",
3,
&[
make_phase_bucket(0, "BASELINE", &[("max_dsq_depth", 5.0)]),
make_phase_bucket(1, "Step[0]", &[]),
],
);
let b = phased_rows(
"scn",
3,
&[make_phase_bucket(0, "BASELINE", &[("max_dsq_depth", 5.0)])],
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 1.0, false);
assert!(
rep.phase_coverage.iter().any(|c| c.step_index == 1
&& c.metric.is_none()
&& c.present_side == ComparePartition::A),
"an empty one-sided Step[1] must surface as a metric-less coverage row: {:?}",
rep.phase_coverage
.iter()
.map(|c| (c.step_index, c.metric.map(|m| m.name)))
.collect::<Vec<_>>(),
);
}
#[test]
fn noise_phase_empty_phases_skip() {
let a = phased_rows(
"scn",
3,
&[make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 8.0)])],
);
let b: Vec<GauntletRow> = (0..3)
.map(|_| cmp_row("scn", "tiny-1llc", true, 10.0, 0))
.collect();
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 1.0, false);
assert!(
rep.phase_findings.is_empty() && rep.phase_coverage.is_empty(),
"no per-phase data when a side has no phases",
);
}
#[test]
fn format_noise_phase_findings_lines_honors_flags() {
let a = phased_rows(
"scn",
3,
&[
make_phase_bucket(0, "BASELINE", &[("max_dsq_depth", 5.0)]),
make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 8.0)]),
],
);
let b = phased_rows(
"scn",
3,
&[
make_phase_bucket(0, "BASELINE", &[("max_dsq_depth", 20.0)]),
make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 20.0)]),
],
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
let render = |opts: &PhaseDisplayOptions| {
format_noise_phase_findings_lines(
&rep.phase_findings,
&rep.phase_coverage,
opts,
"base",
"head",
true,
)
.join("\n")
};
assert!(
format_noise_phase_findings_lines(
&rep.phase_findings,
&rep.phase_coverage,
&PhaseDisplayOptions {
no_phases: true,
..Default::default()
},
"base",
"head",
true,
)
.is_empty(),
"no_phases suppresses the per-phase block",
);
let s = render(&PhaseDisplayOptions {
steps_only: true,
..Default::default()
});
assert!(
s.contains("1: Step[0]") && !s.contains("0: BASELINE"),
"steps_only suppresses BASELINE:\n{s}",
);
let p = render(&PhaseDisplayOptions {
phase: Some(0),
..Default::default()
});
assert!(
p.contains("0: BASELINE") && !p.contains("1: Step[0]"),
"phase=0 shows only BASELINE:\n{p}",
);
assert!(
render(&PhaseDisplayOptions::default()).contains("REGRESSION"),
"a per-phase regression renders the REGRESSION verdict",
);
}
#[test]
fn passes_noise_spread_threshold_edges() {
let v = |a: f64, b: f64| noise_verdict(&[a, a], &[b, b], 1.0);
assert!(PhaseDisplayOptions::default().passes_noise_spread_threshold(&v(100.0, 200.0)));
let o = PhaseDisplayOptions {
phase_threshold: Some(10.0),
..Default::default()
};
assert!(
!o.passes_noise_spread_threshold(&v(100.0, 105.0)),
"5% < 10% -> filtered"
);
assert!(
o.passes_noise_spread_threshold(&v(100.0, 120.0)),
"20% >= 10% -> shown"
);
assert!(
o.passes_noise_spread_threshold(&v(0.0, 50.0)),
"zero baseline + move -> shown"
);
assert!(
!o.passes_noise_spread_threshold(&v(0.0, 0.0)),
"both ~zero -> filtered"
);
}
#[test]
fn format_noise_phase_findings_lines_renders_coverage() {
let a = phased_rows(
"scn",
3,
&[
make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 8.0)]),
make_phase_bucket(2, "Step[1]", &[]),
],
);
let b = phased_rows("scn", 3, &[make_phase_bucket(1, "Step[0]", &[])]);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 1.0, false);
let out = format_noise_phase_findings_lines(
&rep.phase_findings,
&rep.phase_coverage,
&PhaseDisplayOptions::default(),
"base",
"head",
false,
)
.join("\n");
assert!(
out.contains("per-phase coverage asymmetry"),
"coverage header rendered:\n{out}",
);
assert!(
out.contains("max_dsq_depth"),
"one-sided metric name rendered:\n{out}"
);
assert!(
out.contains("—"),
"the metric-less empty one-sided step renders `—`:\n{out}"
);
}
#[test]
fn format_noise_phase_findings_lines_honors_phase_threshold() {
let a = phased_rows(
"scn",
3,
&[
make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 100.0)]),
make_phase_bucket(2, "Step[1]", &[("max_dsq_depth", 100.0)]),
],
);
let b = phased_rows(
"scn",
3,
&[
make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", 103.0)]),
make_phase_bucket(2, "Step[1]", &[("max_dsq_depth", 150.0)]),
],
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 1.0, true);
let out = format_noise_phase_findings_lines(
&rep.phase_findings,
&rep.phase_coverage,
&PhaseDisplayOptions {
phase_threshold: Some(10.0),
..Default::default()
},
"base",
"head",
true,
)
.join("\n");
assert!(
out.contains("2: Step[1]") && !out.contains("1: Step[0]"),
"--phase-threshold 10 keeps the +50% step and suppresses the +3% step:\n{out}",
);
}
#[test]
fn format_noise_phase_findings_lines_default_hides_stable_rows() {
let a = phased_rows(
"scn",
3,
&[make_phase_bucket(
1,
"Step[0]",
&[("max_dsq_depth", 5.0), ("schbench_loop_count", 100.0)],
)],
);
let b = phased_rows(
"scn",
3,
&[make_phase_bucket(
1,
"Step[0]",
&[("max_dsq_depth", 20.0), ("schbench_loop_count", 100.0)],
)],
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
assert!(
rep.phase_findings
.iter()
.any(|f| f.metric.name == "max_dsq_depth" && f.kind == NoiseKind::Regression),
"fixture must carry a per-phase regression: {:?}",
rep.phase_findings
.iter()
.map(|f| (f.metric.name, f.kind))
.collect::<Vec<_>>(),
);
assert!(
rep.phase_findings
.iter()
.any(|f| f.metric.name == "schbench_loop_count" && f.kind == NoiseKind::Stable),
"fixture must carry a per-phase stable row to hide",
);
let default = format_noise_phase_findings_lines(
&rep.phase_findings,
&rep.phase_coverage,
&PhaseDisplayOptions::default(),
"base",
"head",
false,
)
.join("\n");
assert!(
default.contains("max_dsq_depth") && default.contains("REGRESSION"),
"the meaningful regression row shows by default:\n{default}",
);
assert!(
!default.contains("schbench_loop_count"),
"a stable per-phase row is hidden without --all-metrics:\n{default}",
);
let full = format_noise_phase_findings_lines(
&rep.phase_findings,
&rep.phase_coverage,
&PhaseDisplayOptions::default(),
"base",
"head",
true,
)
.join("\n");
assert!(
full.contains("schbench_loop_count"),
"--all-metrics restores the suppressed stable per-phase row:\n{full}",
);
}
#[test]
fn format_noise_phase_findings_lines_collapses_when_all_stable() {
let a = phased_rows(
"scn",
3,
&[make_phase_bucket(
1,
"Step[0]",
&[("schbench_loop_count", 100.0)],
)],
);
let b = phased_rows(
"scn",
3,
&[make_phase_bucket(
1,
"Step[0]",
&[("schbench_loop_count", 100.0)],
)],
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
assert!(
!rep.phase_findings.is_empty()
&& rep
.phase_findings
.iter()
.all(|f| f.kind == NoiseKind::Stable),
"fixture must be all-stable per-phase findings: {:?}",
rep.phase_findings
.iter()
.map(|f| (f.metric.name, f.kind))
.collect::<Vec<_>>(),
);
let out = format_noise_phase_findings_lines(
&rep.phase_findings,
&rep.phase_coverage,
&PhaseDisplayOptions::default(),
"base",
"head",
false,
)
.join("\n");
assert!(
out.contains("none meaningfully changed") && out.contains("--all-metrics"),
"all-stable per-phase collapses to the one-line summary:\n{out}",
);
let full = format_noise_phase_findings_lines(
&rep.phase_findings,
&rep.phase_coverage,
&PhaseDisplayOptions::default(),
"base",
"head",
true,
)
.join("\n");
assert!(
full.contains("schbench_loop_count") && !full.contains("none meaningfully changed"),
"--all-metrics shows the stable rows instead of collapsing:\n{full}",
);
}
#[test]
fn format_noise_phase_findings_lines_suppressed_spread_with_coverage_shows_hint() {
let a = phased_rows(
"scn",
3,
&[make_phase_bucket(
1,
"Step[0]",
&[("schbench_loop_count", 100.0), ("max_dsq_depth", 8.0)],
)],
);
let b = phased_rows(
"scn",
3,
&[make_phase_bucket(
1,
"Step[0]",
&[("schbench_loop_count", 100.0)],
)],
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
assert!(
rep.phase_findings
.iter()
.any(|f| f.metric.name == "schbench_loop_count" && f.kind == NoiseKind::Stable),
"fixture must carry a matched stable spread row to suppress: {:?}",
rep.phase_findings
.iter()
.map(|f| (f.metric.name, f.kind))
.collect::<Vec<_>>(),
);
assert!(
!rep.phase_coverage.is_empty(),
"fixture must carry a one-sided coverage row"
);
let out = format_noise_phase_findings_lines(
&rep.phase_findings,
&rep.phase_coverage,
&PhaseDisplayOptions::default(),
"base",
"head",
false,
)
.join("\n");
assert!(
out.contains("none meaningfully changed") && out.contains("--all-metrics"),
"the suppressed-spread hint appears even when a coverage table follows:\n{out}",
);
assert!(
out.contains("per-phase coverage asymmetry"),
"the coverage table still renders alongside the hint:\n{out}",
);
assert!(
!out.contains("schbench_loop_count"),
"the stable spread row itself stays hidden without --all-metrics:\n{out}",
);
}
#[test]
fn noise_report_composite_counts_regressed_improved_stable() {
let base = noise_side("scn", 10.0, 2000);
let regressed = noise_findings(
&base,
&noise_side("scn", 15.0, 2000),
LEGACY_PAIRING_DIMS,
1.0,
true,
);
assert_eq!(regressed.regressions(), 1, "worst_spread 10->15 regresses");
assert_eq!(
regressed.improvements(),
0,
"nothing improved on the regressing side"
);
assert!(
regressed.stable() >= 1,
"unchanged total_iterations is counted stable"
);
assert_eq!(
regressed.informational(),
0,
"no informational metric in the fixture"
);
let improved = noise_findings(
&base,
&noise_side("scn", 5.0, 2000),
LEGACY_PAIRING_DIMS,
1.0,
true,
);
assert_eq!(improved.improvements(), 1, "worst_spread 10->5 improves");
assert_eq!(
improved.regressions(),
0,
"nothing regressed on the improving side"
);
assert!(
improved.stable() >= 1,
"unchanged total_iterations is counted stable"
);
}
#[test]
fn verdict_label_stays_stable_below_cutoff_cites_direction_above() {
assert_eq!(
verdict_label(false, 0, None),
"STABLE",
"no moves -> stable"
);
assert_eq!(
verdict_label(false, 4, None),
"STABLE",
"4 improvements < 5 cutoff -> still stable (likely noise)"
);
assert_eq!(
verdict_label(false, 5, None),
"IMPROVED",
"improvements clear the cutoff -> IMPROVED"
);
assert_eq!(
verdict_label(true, 0, None),
"REGRESSED",
"a failing run reads REGRESSED even with no improvements"
);
assert_eq!(
verdict_label(true, 5, None),
"REGRESSED + IMPROVED",
"both directions clear the cutoff -> combined verdict"
);
assert_eq!(verdict_label(false, 1, Some(1)), "IMPROVED");
assert_eq!(verdict_label(false, 100, Some(0)), "STABLE");
}
#[test]
fn noise_phase_informational_metric_shows_but_never_gates() {
let a = phased_rows(
"scn",
3,
&[make_phase_bucket(
1,
"Step[0]",
&[("total_ttwu_count", 1000.0)],
)],
);
let b = phased_rows(
"scn",
3,
&[make_phase_bucket(
1,
"Step[0]",
&[("total_ttwu_count", 5000.0)],
)],
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 1.0, false);
let f = rep
.phase_findings
.iter()
.find(|f| f.step_index == 1 && f.metric.name == "total_ttwu_count")
.expect("Step[1] total_ttwu_count per-phase finding (noise SHOWS Informational)");
assert_eq!(
f.kind,
NoiseKind::Informational,
"an Informational per-phase metric shows as Informational, not a regression",
);
assert_eq!(rep.phase_regressions(), 0, "Informational never gates");
}
#[test]
fn noise_phase_findings_disambiguate_by_pairing_key_across_topologies() {
let phased = |topo: &'static str, depth: f64| {
(0..3)
.map(|_| {
let mut r = cmp_row("scn", topo, true, 10.0, 0);
r.phases = vec![make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", depth)])];
r
})
.collect::<Vec<_>>()
};
let mut a = phased("tiny-1llc", 8.0);
a.extend(phased("large-4llc", 8.0));
let mut b = phased("tiny-1llc", 20.0);
b.extend(phased("large-4llc", 20.0));
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, false);
let labels: Vec<&str> = rep
.phase_findings
.iter()
.filter(|f| f.metric.name == "max_dsq_depth")
.map(|f| f.pairing_label.as_str())
.collect();
assert_eq!(
labels.len(),
2,
"one per-phase finding per topology group, distinct labels: {labels:?}",
);
assert!(
labels.iter().any(|l| l.contains("tiny-1llc"))
&& labels.iter().any(|l| l.contains("large-4llc")),
"pairing labels must include the topology to disambiguate: {labels:?}",
);
assert_ne!(
labels[0], labels[1],
"the two topology groups must render distinct labels"
);
}
#[test]
fn summarize_side_runs_categorizes_by_exclusion() {
let mut skip = cmp_row("s", "tiny-1llc", false, 0.0, 0);
skip.skipped = true;
let fail = cmp_row("s", "tiny-1llc", false, 0.0, 0);
let pass = cmp_row("s", "tiny-1llc", true, 0.0, 0);
let (ok, desc) = summarize_side_runs(&[skip.clone(), skip.clone(), skip.clone()]);
assert_eq!(ok, 0, "all skipped -> 0 comparable: {desc}");
assert!(
desc.contains("3 run(s)") && desc.contains("0 comparable") && desc.contains("3 skipped"),
"breakdown names the skipped runs: {desc}"
);
let (ok2, desc2) = summarize_side_runs(&[pass, skip, fail]);
assert_eq!(ok2, 1, "one pass is comparable: {desc2}");
assert!(
desc2.contains("1 comparable") && desc2.contains("1 skipped") && desc2.contains("1 failed"),
"mixed breakdown names each excluded category: {desc2}"
);
}
#[test]
fn noise_findings_classifies_both_polarities() {
let rep = noise_findings(
&noise_side("regress", 10.0, 2000),
&noise_side("regress", 15.0, 1000),
LEGACY_PAIRING_DIMS,
1.0,
false,
);
assert_eq!(rep.paired_scenarios, 1);
assert_eq!(
rep.regressions(),
2,
"LowerBetter rose + HigherBetter dropped = 2 regressions: {:?}",
rep.findings
.iter()
.map(|f| (f.metric.name, f.kind))
.collect::<Vec<_>>(),
);
assert_eq!(rep.noisy(), 0);
assert!(rep.findings.iter().all(|f| f.kind == NoiseKind::Regression));
let rep = noise_findings(
&noise_side("improve", 15.0, 1000),
&noise_side("improve", 10.0, 2000),
LEGACY_PAIRING_DIMS,
1.0,
false,
);
assert_eq!(rep.regressions(), 0);
assert_eq!(
rep.findings
.iter()
.filter(|f| f.kind == NoiseKind::Improvement)
.count(),
2,
"LowerBetter dropped + HigherBetter rose = 2 improvements",
);
}
#[test]
fn noise_findings_high_spread_annotates_but_does_not_suppress_regression() {
let a = vec![
cmp_row("noisy", "tiny-1llc", true, 10.0, 2000),
cmp_row("noisy", "tiny-1llc", true, 20.0, 2000),
cmp_row("noisy", "tiny-1llc", true, 15.0, 2000),
];
let rep = noise_findings(
&a,
&noise_side("noisy", 30.0, 2000),
LEGACY_PAIRING_DIMS,
5.0,
false,
);
let ws = rep
.findings
.iter()
.find(|f| f.metric.name == "worst_spread")
.expect("worst_spread finding present");
assert_eq!(
ws.kind,
NoiseKind::Regression,
"wide A spread must NOT suppress a separated + material worsening move",
);
assert!(
ws.verdict.high_spread,
"A's ~67% spread exceeds the 5% advisory gate -> high_spread annotation",
);
assert_eq!(
rep.regressions(),
1,
"the separated, material worsening metric gates as a confident regression",
);
assert!(
rep.findings.iter().all(|f| f.metric.name == "worst_spread"),
"unchanged-and-clean metrics are omitted: {:?}",
rep.findings
.iter()
.map(|f| f.metric.name)
.collect::<Vec<_>>(),
);
}
#[test]
fn noise_findings_separated_but_immaterial_stays_stable() {
let a = noise_side("imm", 10.0, 2000);
let b = noise_side("imm", 10.5, 2000);
let gate = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, false);
assert_eq!(
gate.regressions(),
0,
"a separated but immaterial (0.5 < abs 5.0) move must not gate: {:?}",
gate.findings
.iter()
.map(|f| (f.metric.name, f.kind))
.collect::<Vec<_>>(),
);
let show = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
let ws = show
.findings
.iter()
.find(|f| f.metric.name == "worst_spread")
.expect("worst_spread row present under include_stable");
assert_eq!(
ws.kind,
NoiseKind::Stable,
"separated-but-immaterial worst_spread is Stable, not a regression",
);
assert!(
ws.verdict.separated,
"premise: the [10,10] vs [10.5,10.5] bands are disjoint => separated",
);
}
#[test]
fn noise_findings_skips_all_zero_and_omits_unchanged() {
let rep = noise_findings(
&noise_side("zero", 0.0, 0),
&noise_side("zero", 0.0, 0),
LEGACY_PAIRING_DIMS,
1.0,
false,
);
assert!(
rep.findings.is_empty(),
"all-zero scenario yields no findings"
);
assert_eq!(rep.paired_scenarios, 1);
let rep = noise_findings(
&noise_side("same", 12.0, 1500),
&noise_side("same", 12.0, 1500),
LEGACY_PAIRING_DIMS,
1.0,
false,
);
assert!(
rep.findings.is_empty(),
"unchanged-clean scenario yields no findings"
);
assert_eq!((rep.regressions(), rep.noisy()), (0, 0));
}
#[test]
fn noise_findings_include_stable_shows_unchanged_metrics() {
let rep = noise_findings(
&noise_side("same", 12.0, 1500),
&noise_side("same", 12.0, 1500),
LEGACY_PAIRING_DIMS,
1.0,
true,
);
assert!(
!rep.findings.is_empty(),
"include_stable surfaces the unchanged metrics"
);
assert!(
rep.findings.iter().all(|f| f.kind == NoiseKind::Stable),
"unchanged-clean metrics are Stable: {:?}",
rep.findings
.iter()
.map(|f| (f.metric.name, f.kind))
.collect::<Vec<_>>(),
);
assert_eq!((rep.regressions(), rep.noisy()), (0, 0));
let rep_zero = noise_findings(
&noise_side("zero", 0.0, 0),
&noise_side("zero", 0.0, 0),
LEGACY_PAIRING_DIMS,
1.0,
true,
);
assert!(
rep_zero.findings.is_empty(),
"both-zero metrics are omitted (never Stable) even with include_stable: {:?}",
rep_zero
.findings
.iter()
.map(|f| f.metric.name)
.collect::<Vec<_>>(),
);
assert_eq!(rep_zero.paired_scenarios, 1);
}
#[test]
fn format_noise_findings_table_renders_rows_and_verdicts() {
let rep = noise_findings(
&noise_side("mix", 10.0, 2000),
&noise_side("mix", 15.0, 2000),
LEGACY_PAIRING_DIMS,
1.0,
true,
);
let out = format_noise_findings_table(&rep.findings, "base", "head", true);
assert!(
out.contains("TEST / METRIC") && out.contains("VERDICT"),
"header present: {out}"
);
assert!(
out.contains("mix/tiny-1llc/SpinWait / worst_spread") && out.contains("REGRESSION"),
"worsened metric row + verdict: {out}"
);
assert!(
out.contains("stable"),
"unchanged total_iterations renders as a stable row: {out}"
);
}
#[test]
fn format_noise_findings_table_renders_noisy_improvement_and_advisory_spread() {
let a = vec![cmp_row("nz", "tiny-1llc", true, 10.0, 2000)];
let rep = noise_findings(
&a,
&noise_side("nz", 30.0, 2000),
LEGACY_PAIRING_DIMS,
5.0,
true,
);
let out = format_noise_findings_table(&rep.findings, "base", "head", true);
assert!(
out.contains("NOISY (<2 runs)"),
"insufficient-samples verdict rendered: {out}"
);
let rep = noise_findings(
&noise_side("imp", 15.0, 2000),
&noise_side("imp", 10.0, 2000),
LEGACY_PAIRING_DIMS,
5.0,
true,
);
let out = format_noise_findings_table(&rep.findings, "base", "head", true);
assert!(
out.contains("improvement"),
"improvement verdict rendered: {out}"
);
let a = vec![
cmp_row("adv", "tiny-1llc", true, 10.0, 2000),
cmp_row("adv", "tiny-1llc", true, 20.0, 2000),
cmp_row("adv", "tiny-1llc", true, 15.0, 2000),
];
let rep = noise_findings(
&a,
&noise_side("adv", 30.0, 2000),
LEGACY_PAIRING_DIMS,
5.0,
true,
);
let out = format_noise_findings_table(&rep.findings, "base", "head", true);
assert!(
out.contains("REGRESSION (noisy spread)"),
"high_spread annotates the reported regression, never suppresses it: {out}"
);
}
#[test]
fn format_noise_findings_table_default_hides_stable_and_noisy_rows() {
let rep = noise_findings(
&noise_side("mix", 10.0, 2000),
&noise_side("mix", 15.0, 2000),
LEGACY_PAIRING_DIMS,
1.0,
true,
);
assert!(
rep.findings.iter().any(|f| f.kind == NoiseKind::Stable),
"fixture must contain a Stable finding to hide: {:?}",
rep.findings.iter().map(|f| f.kind).collect::<Vec<_>>()
);
let out = format_noise_findings_table(&rep.findings, "base", "head", false);
assert!(
out.contains("worst_spread") && out.contains("REGRESSION"),
"the meaningful regression row still shows under default suppression: {out}"
);
assert!(
!out.contains("stable"),
"Stable rows are hidden without --all-metrics: {out}"
);
let a = vec![cmp_row("nz", "tiny-1llc", true, 10.0, 2000)];
let rep = noise_findings(
&a,
&noise_side("nz", 30.0, 2000),
LEGACY_PAIRING_DIMS,
5.0,
true,
);
assert!(
rep.findings
.iter()
.all(|f| matches!(f.kind, NoiseKind::Stable | NoiseKind::Noisy)),
"fixture must contain only suppressed (Stable/Noisy) findings: {:?}",
rep.findings.iter().map(|f| f.kind).collect::<Vec<_>>()
);
let out = format_noise_findings_table(&rep.findings, "base", "head", false);
assert!(
out.contains("none meaningfully changed") && out.contains("--all-metrics"),
"all-suppressed collapses to the one-line summary: {out}"
);
let full = format_noise_findings_table(&rep.findings, "base", "head", true);
assert!(
full.contains("NOISY (<2 runs)"),
"--all-metrics restores the suppressed rows: {full}"
);
}
fn perf_gate(
metric: &str,
max_regression_pct: Option<f64>,
min_abs: Option<f64>,
direction: Option<crate::test_support::Polarity>,
phase: Option<u16>,
) -> crate::test_support::PerfDeltaAssertionRecord {
crate::test_support::PerfDeltaAssertionRecord {
metric: metric.to_string(),
direction,
max_regression_pct,
min_abs,
phase,
}
}
fn with_gate(
mut rows: Vec<GauntletRow>,
gate: crate::test_support::PerfDeltaAssertionRecord,
) -> Vec<GauntletRow> {
for r in &mut rows {
r.perf_delta_assertions.push(gate.clone());
}
rows
}
#[test]
fn noise_findings_declared_gate_tightens_immaterial_move_to_regression() {
let a = noise_side("gate", 10.0, 0);
let ungated = noise_findings(
&a,
&noise_side("gate", 11.0, 0),
LEGACY_PAIRING_DIMS,
5.0,
true,
);
let uw = ungated
.findings
.iter()
.find(|f| f.metric.name == "worst_spread")
.expect("worst_spread row");
assert_eq!(
uw.kind,
NoiseKind::Stable,
"10->11 is immaterial under the registry default",
);
assert!(
!uw.gated_by_assertion,
"no declared gate on the ungated rows"
);
assert_eq!(ungated.regressions(), 0);
let b = with_gate(
noise_side("gate", 11.0, 0),
perf_gate("worst_spread", Some(5.0), Some(0.5), None, None),
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
let w = rep
.findings
.iter()
.find(|f| f.metric.name == "worst_spread")
.expect("worst_spread row");
assert_eq!(
w.kind,
NoiseKind::Regression,
"the tighter declared gate flags the move",
);
assert!(
w.gated_by_assertion,
"the row was classified by the declared gate",
);
assert_eq!(
rep.regressions(),
1,
"the declared-gate regression gates the exit",
);
assert!(
rep.assertion_coverage.is_empty(),
"the gate matched a metric that had data",
);
}
#[test]
fn noise_findings_declared_direction_override_flips_polarity() {
let a = noise_side("dir", 10.0, 0);
let b = with_gate(
noise_side("dir", 11.0, 0),
perf_gate(
"worst_spread",
Some(5.0),
Some(0.5),
Some(crate::test_support::Polarity::HigherBetter),
None,
),
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
let w = rep
.findings
.iter()
.find(|f| f.metric.name == "worst_spread")
.expect("worst_spread row");
assert_eq!(
w.kind,
NoiseKind::Improvement,
"HigherBetter reclassifies the 10->11 rise as an improvement",
);
assert!(w.gated_by_assertion);
assert_eq!(rep.regressions(), 0);
}
#[test]
fn noise_findings_unmatched_whole_run_gate_is_reported() {
let a = noise_side("cov", 10.0, 0);
let b = with_gate(
noise_side("cov", 10.0, 0),
perf_gate("run_delay_per_sec", Some(5.0), None, None, None),
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
assert_eq!(
rep.assertion_coverage.len(),
1,
"the absent-metric gate is reported as un-evaluated",
);
let c = &rep.assertion_coverage[0];
assert_eq!(c.assertion.metric, "run_delay_per_sec");
assert_eq!(c.assertion.phase, None);
assert_eq!(
rep.regressions(),
0,
"an un-evaluated gate never gates the exit",
);
}
#[test]
fn noise_findings_unmatched_phase_gate_is_reported() {
let a = noise_side("pcov", 10.0, 0);
let b = with_gate(
noise_side("pcov", 10.0, 0),
perf_gate("worst_spread", Some(5.0), None, None, Some(5)),
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
assert_eq!(rep.assertion_coverage.len(), 1);
assert_eq!(rep.assertion_coverage[0].assertion.phase, Some(5));
let w = rep
.findings
.iter()
.find(|f| f.metric.name == "worst_spread")
.expect("aggregate worst_spread row");
assert!(
!w.gated_by_assertion,
"a phase-scoped gate does not annotate the aggregate row",
);
}
#[test]
fn format_noise_findings_table_marks_declared_gate() {
let a = noise_side("mark", 10.0, 0);
let b = with_gate(
noise_side("mark", 11.0, 0),
perf_gate("worst_spread", Some(5.0), Some(0.5), None, None),
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
let out = format_noise_findings_table(&rep.findings, "base", "head", true);
assert!(
out.contains("REGRESSION (declared gate)"),
"declared-gate regression is annotated: {out}",
);
}
#[test]
fn format_noise_assertion_coverage_lines_lists_unevaluated_gates() {
let a = noise_side("fcov", 10.0, 0);
let b = with_gate(
noise_side("fcov", 10.0, 0),
perf_gate("run_delay_per_sec", Some(5.0), Some(0.5), None, None),
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
let out = format_noise_assertion_coverage_lines(&rep.assertion_coverage).join("\n");
assert!(
out.contains("NOT evaluated"),
"warning header present: {out}"
);
assert!(
out.contains("run_delay_per_sec"),
"the un-evaluated metric is named: {out}",
);
assert!(
out.contains("max_regression_pct=5") && out.contains("min_abs=0.5"),
"the declared thresholds are described: {out}",
);
assert!(
format_noise_assertion_coverage_lines(&[]).is_empty(),
"no coverage rows -> no lines",
);
}
#[test]
fn noise_findings_declared_phase_gate_gates_the_exit() {
let buckets = |v: f64| {
vec![
make_phase_bucket(0, "BASELINE", &[("max_dsq_depth", 5.0)]),
make_phase_bucket(1, "Step[0]", &[("max_dsq_depth", v)]),
]
};
let a = phased_rows("pg", 3, &buckets(8.0));
let mut b = phased_rows("pg", 3, &buckets(9.0));
for r in &mut b {
r.perf_delta_assertions.push(perf_gate(
"max_dsq_depth",
Some(5.0),
Some(0.5),
None,
Some(1),
));
}
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
let s1 = rep
.phase_findings
.iter()
.find(|f| f.step_index == 1 && f.metric.name == "max_dsq_depth")
.expect("Step[0] max_dsq_depth per-phase finding");
assert_eq!(
s1.kind,
NoiseKind::Regression,
"the declared phase gate flags the otherwise-immaterial move",
);
assert!(s1.gated_by_assertion);
assert_eq!(
rep.declared_phase_regressions(),
1,
"a declared phase gate contributes to the exit basis",
);
assert_eq!(
rep.regressions(),
0,
"no AGGREGATE regression — the move is phase-scoped only",
);
assert!(
rep.assertion_coverage.is_empty(),
"the phase gate matched Step[0]",
);
assert_eq!(
noise_exit_code(&rep, &crate::stats::GateOptions::default()),
1,
"a declared phase regression fails the run under the default gate",
);
assert_eq!(
noise_exit_code(
&rep,
&crate::stats::GateOptions {
fail_threshold: Some(0),
..Default::default()
},
),
1,
"a declared phase regression fails even with the count gate disabled",
);
}
#[test]
fn noise_findings_declared_whole_run_gate_gates_the_exit() {
let a = noise_side("wr", 10.0, 2000);
let b = with_gate(
noise_side("wr", 15.0, 2000),
perf_gate("worst_spread", Some(5.0), Some(0.5), None, None),
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
let agg = rep
.findings
.iter()
.find(|f| f.metric.name == "worst_spread")
.expect("worst_spread aggregate finding");
assert_eq!(agg.kind, NoiseKind::Regression);
assert!(
agg.gated_by_assertion,
"a whole-run declared gate marks the AGGREGATE finding"
);
assert_eq!(rep.declared_regressions(), 1);
assert_eq!(
rep.declared_phase_regressions(),
0,
"the gate is whole-run, not phase-scoped"
);
assert_eq!(
rep.regressions(),
1,
"exactly one aggregate regression (below the default count gate)"
);
assert_eq!(
noise_exit_code(&rep, &crate::stats::GateOptions::default()),
1,
"a declared whole-run regression fails even below --fail-threshold",
);
assert_eq!(
noise_exit_code(
&rep,
&crate::stats::GateOptions {
fail_threshold: Some(0),
..Default::default()
},
),
1,
"a declared whole-run regression gates even with the count gate disabled",
);
let rep_undeclared = noise_findings(
&a,
&noise_side("wr", 15.0, 2000),
LEGACY_PAIRING_DIMS,
5.0,
true,
);
assert_eq!(rep_undeclared.regressions(), 1);
assert_eq!(rep_undeclared.declared_regressions(), 0);
assert_eq!(
noise_exit_code(&rep_undeclared, &crate::stats::GateOptions::default()),
0,
"a lone UNdeclared regression is below the default count gate (>=5)",
);
}
#[test]
fn scalar_declared_gate_warning_flags_present_gates() {
let plain = vec![cmp_row("s", "tiny-1llc", true, 10.0, 0)];
assert!(
scalar_declared_gate_warning(&plain).is_none(),
"no declared gates -> no warning",
);
let gated = with_gate(
vec![cmp_row("s", "tiny-1llc", true, 10.0, 0)],
perf_gate("worst_spread", Some(5.0), None, None, None),
);
let w = scalar_declared_gate_warning(&gated).expect("declared gate -> warning");
assert!(
w.contains("--noise-adjust") && w.contains("NOT evaluate"),
"warning must name --noise-adjust and that gates are not evaluated: {w}",
);
}
#[test]
fn classify_noise_ignores_target_value_direction_on_the_sidecar_path() {
let a = noise_side("tv", 10.0, 1000);
let b = with_gate(
noise_side("tv", 10.0, 1100),
perf_gate(
"total_iterations",
Some(5.0),
Some(50.0),
Some(crate::test_support::Polarity::TargetValue(5.0)),
None,
),
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
let f = rep
.findings
.iter()
.find(|f| f.metric.name == "total_iterations")
.expect("total_iterations row");
assert_eq!(
f.kind,
NoiseKind::Improvement,
"a TargetValue direction on the Record must be ignored -> inherit \
HigherBetter -> a rise is an improvement, not a regression",
);
assert!(f.gated_by_assertion);
}
#[test]
fn classify_noise_ignores_out_of_range_thresholds_on_the_sidecar_path() {
let a = noise_side("oor", 10.0, 2000);
let b = with_gate(
noise_side("oor", 10.5, 2000),
perf_gate("worst_spread", Some(-5.0), Some(-10.0), None, None),
);
let rep = noise_findings(&a, &b, LEGACY_PAIRING_DIMS, 5.0, true);
assert_eq!(
rep.regressions(),
0,
"negative sidecar thresholds must NOT manufacture a phantom regression: {:?}",
rep.findings
.iter()
.map(|f| (f.metric.name, f.kind))
.collect::<Vec<_>>(),
);
let w = rep
.findings
.iter()
.find(|f| f.metric.name == "worst_spread")
.expect("worst_spread row");
assert_eq!(
w.kind,
NoiseKind::Stable,
"out-of-range declared thresholds fall back to the registry default -> \
0.5 < default_abs 5.0 -> immaterial -> Stable",
);
}
#[test]
fn compare_rows_informational_metric_shows_but_never_gates() {
let mk = |ttwu: f64| {
let mut r = cmp_row("t", "tiny-1llc", true, 10.0, 100);
r.ext_metrics.insert("total_ttwu_count".into(), ttwu);
r
};
let res = compare_rows_by(
&[mk(1000.0)],
&[mk(5000.0)],
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(
res.regressions, 0,
"informational metric must not be a regression"
);
assert_eq!(res.improvements, 0, "...nor an improvement");
assert_eq!(
res.informational, 1,
"the 5x total_ttwu_count move is classified informational"
);
let f = res
.findings
.iter()
.find(|f| f.metric.name == "total_ttwu_count")
.expect("total_ttwu_count finding present");
assert_eq!(f.kind, FindingKind::Informational);
}
#[test]
fn compare_rows_one_sided_absent_is_coverage_diff_not_verdict() {
let present = {
let mut r = cmp_row("t", "tiny-1llc", true, 10.0, 100);
r.ext_metrics.insert("avg_nr_running".into(), 5.0);
r
};
let absent = cmp_row("t", "tiny-1llc", true, 10.0, 100);
let res = compare_rows_by(
std::slice::from_ref(&present),
std::slice::from_ref(&absent),
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(
res.regressions, 0,
"one-sided-absent must not be a regression"
);
assert_eq!(res.improvements, 0, "...nor an improvement");
assert!(
!res.findings
.iter()
.any(|f| f.metric.name == "avg_nr_running"),
"no phantom avg_nr_running finding for a one-sided-absent metric"
);
assert_eq!(res.coverage_diffs.len(), 1, "recorded as a coverage diff");
assert_eq!(res.coverage_diffs[0].metric.name, "avg_nr_running");
assert_eq!(res.coverage_diffs[0].present_side, ComparePartition::A);
assert_eq!(res.coverage_diffs[0].value, 5.0);
let res2 = compare_rows_by(
&[absent],
&[present],
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert_eq!(res2.regressions, 0, "mirror: not a regression");
assert_eq!(res2.improvements, 0);
assert_eq!(res2.coverage_diffs.len(), 1);
assert_eq!(res2.coverage_diffs[0].present_side, ComparePartition::B);
}
#[test]
fn compare_rows_both_absent_skipped_present_zero_still_compared() {
let res_absent = compare_rows_by(
&[cmp_row("t", "tiny-1llc", true, 10.0, 100)],
&[cmp_row("t", "tiny-1llc", true, 10.0, 100)],
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert!(
res_absent.coverage_diffs.is_empty(),
"metric absent on both sides is not a coverage diff"
);
assert!(
!res_absent
.findings
.iter()
.any(|f| f.metric.name == "avg_nr_running"),
"metric absent on both sides produces no finding"
);
let zero_a = {
let mut r = cmp_row("t", "tiny-1llc", true, 10.0, 100);
r.ext_metrics.insert("avg_nr_running".into(), 0.0);
r
};
let nonzero_b = {
let mut r = cmp_row("t", "tiny-1llc", true, 10.0, 100);
r.ext_metrics.insert("avg_nr_running".into(), 5.0);
r
};
let res_zero = compare_rows_by(
&[zero_a],
&[nonzero_b],
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert!(
res_zero.coverage_diffs.is_empty(),
"present-0.0 is NOT absent — no coverage diff"
);
assert_eq!(
res_zero.regressions, 1,
"0 -> 5 on a LowerBetter metric (both present) is a regression"
);
}
#[test]
fn coverage_diff_lines_map_present_absent_labels_by_side() {
let present = {
let mut r = cmp_row("t", "tiny-1llc", true, 10.0, 100);
r.ext_metrics.insert("avg_nr_running".into(), 5.0);
r
};
let absent = cmp_row("t", "tiny-1llc", true, 10.0, 100);
let report = compare_rows_by(
std::slice::from_ref(&present),
std::slice::from_ref(&absent),
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
let joined = format_coverage_diff_lines(&report, "runA", "runB").join("\n");
assert!(
joined.contains("avg_nr_running"),
"names the metric: {joined}"
);
assert!(
joined.contains("= 5.00 in 'runA'"),
"present value + side-A label (runA): {joined}"
);
assert!(
joined.contains("absent in 'runB'"),
"absent side is the OTHER label (runB): {joined}"
);
let report2 = compare_rows_by(
std::slice::from_ref(&absent),
std::slice::from_ref(&present),
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
let joined2 = format_coverage_diff_lines(&report2, "runA", "runB").join("\n");
assert!(
joined2.contains("= 5.00 in 'runB'"),
"present on side B maps to runB: {joined2}"
);
assert!(
joined2.contains("absent in 'runA'"),
"absent maps to runA: {joined2}"
);
}
#[test]
fn compare_rows_scale_varying_low_throughput_regression_is_material() {
let rows_a = vec![cmp_row("lowtput", "tiny-1llc", true, 10.0, 200)];
let rows_b = vec![cmp_row("lowtput", "tiny-1llc", true, 10.0, 120)];
let res = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert!(
res.findings
.iter()
.any(|f| f.metric.name == "total_iterations" && f.kind == FindingKind::Regression),
"200 -> 120 total_iterations (40% drop, |delta| 80 < old floor 100) must \
be a regression after the near-idle floor recalibration; got {:?}",
res.findings
.iter()
.map(|f| (f.metric.name, f.delta))
.collect::<Vec<_>>(),
);
}
#[test]
fn compare_rows_scale_varying_high_throughput_noise_is_unchanged() {
let rows_a = vec![cmp_row("hitput", "tiny-1llc", true, 10.0, 100_000)];
let rows_b = vec![cmp_row("hitput", "tiny-1llc", true, 10.0, 101_000)];
let res = compare_rows_by(
&rows_a,
&rows_b,
LEGACY_PAIRING_DIMS,
None,
&ComparisonPolicy::default(),
);
assert!(
res.findings
.iter()
.all(|f| f.metric.name != "total_iterations"),
"100000 -> 101000 total_iterations (1% move) must stay unchanged: the \
relative gate still filters high-throughput noise; got {:?}",
res.findings
.iter()
.map(|f| (f.metric.name, f.delta))
.collect::<Vec<_>>(),
);
}