use super::super::Runner;
use crate::metrics_api::Metric;
use crate::model::{EvalConfig, TestCase, TestStatus};
use std::sync::Arc;
pub(crate) fn check_baseline_regressions_impl(
runner: &Runner,
tc: &TestCase,
cfg: &EvalConfig,
details: &serde_json::Value,
metrics: &[Arc<dyn Metric>],
baseline: &crate::baseline::Baseline,
) -> Option<(TestStatus, String)> {
let suite_defaults = cfg.settings.thresholding.as_ref();
for m in metrics {
let metric_name = m.name();
let Some(score) = details["metrics"][metric_name]["score"].as_f64() else {
continue;
};
let exercised = details["metrics"][metric_name]["exercised"].as_str();
let is_exercised = exercised.is_none_or(|e| e == "exercised");
if coverage_regressed(exercised, baseline.was_exercised(&tc.id, metric_name)) {
return Some((
TestStatus::Warn,
format!(
"coverage regression: {} was exercised in the baseline and is {} now",
metric_name,
exercised.unwrap_or("not exercised")
),
));
}
if !is_exercised {
continue;
}
let (mode, max_drop) =
resolve_threshold_config_impl(runner, tc, metric_name, suite_defaults);
if mode == "relative" {
if let Some(base_score) = baseline.get_score(&tc.id, metric_name) {
let delta = score - base_score;
if let Some(drop_limit) = max_drop {
if delta < -drop_limit {
return Some((
TestStatus::Fail,
format!(
"regression: {} dropped {:.3} (limit: {:.3})",
metric_name, -delta, drop_limit
),
));
}
}
} else {
return Some((
TestStatus::Warn,
format!("missing baseline for {}/{}", tc.id, metric_name),
));
}
}
}
None
}
pub(crate) fn resolve_threshold_config_impl(
_runner: &Runner,
tc: &TestCase,
metric_name: &str,
suite_defaults: Option<&crate::model::ThresholdingSettings>,
) -> (String, Option<f64>) {
let mut mode = "absolute".to_string();
let mut max_drop = None;
if let Some(s) = suite_defaults {
if let Some(m) = &s.mode {
mode = m.clone();
}
max_drop = s.max_drop;
}
if let Some(t) = tc.expected.thresholding_for_metric(metric_name) {
if t.max_drop.is_some() {
max_drop = t.max_drop;
}
}
(mode, max_drop)
}
fn coverage_regressed(current: Option<&str>, baseline_exercised: Option<bool>) -> bool {
let now_exercised = current.is_none_or(|e| e == "exercised");
!now_exercised && baseline_exercised == Some(true)
}
#[cfg(test)]
mod coverage_regression_tests {
use super::coverage_regressed;
#[test]
fn a_metric_that_stopped_being_evaluated_is_a_regression() {
assert!(coverage_regressed(Some("not_applicable"), Some(true)));
assert!(coverage_regressed(Some("not_exercised"), Some(true)));
}
#[test]
fn a_metric_that_still_runs_is_not_a_regression() {
assert!(!coverage_regressed(Some("exercised"), Some(true)));
}
#[test]
fn a_metric_that_never_ran_is_not_a_regression() {
assert!(!coverage_regressed(Some("not_applicable"), Some(false)));
}
#[test]
fn a_baseline_that_says_nothing_is_not_a_regression() {
assert!(!coverage_regressed(Some("not_applicable"), None));
assert!(!coverage_regressed(Some("not_exercised"), None));
}
#[test]
fn a_current_result_that_says_nothing_is_not_a_regression() {
assert!(!coverage_regressed(None, Some(true)));
}
}