use crate::history::{check_history_snapshots, HistorySnapshot, HistoryViolation};
use crate::runner::{TestOutcome, TestResult};
use crate::status::{StatusFile, TestEntry, TestState};
use std::collections::BTreeSet;
pub const GATEKEEPER_TEST_NAME: &str = "tdd_ratchet_gatekeeper";
#[derive(Debug, Clone)]
pub struct EvalResult {
pub violations: Vec<Violation>,
pub updated: StatusFile,
}
#[derive(Debug, Clone)]
pub enum Violation {
NewTestPassed { test: String },
Regression { test: String },
TestDisappeared { test: String },
SkippedPending { test: String, commit: String },
MissingGatekeeper,
}
pub fn evaluate(
status: &StatusFile,
results: &[TestResult],
history_snapshots: &[HistorySnapshot],
) -> EvalResult {
let mut violations = Vec::new();
let mut updated = status.clone();
let has_gatekeeper = results
.iter()
.any(|r| r.name.ends_with(GATEKEEPER_TEST_NAME));
if !has_gatekeeper {
violations.push(Violation::MissingGatekeeper);
}
let seen_names: BTreeSet<&str> = results.iter().map(|r| r.name.as_str()).collect();
for result in results {
match (
status.tests.get(&result.name).map(|e| e.state()),
result.outcome,
) {
(None, TestOutcome::Failed) => {
updated
.tests
.insert(result.name.clone(), TestEntry::Simple(TestState::Pending));
}
(None, TestOutcome::Passed) => {
if result.name.ends_with(GATEKEEPER_TEST_NAME) {
updated
.tests
.insert(result.name.clone(), TestEntry::Simple(TestState::Passing));
} else {
violations.push(Violation::NewTestPassed {
test: result.name.clone(),
});
}
}
(None, TestOutcome::Ignored) => {}
(Some(TestState::Pending), TestOutcome::Failed) => {}
(Some(TestState::Pending), TestOutcome::Passed) => {
updated
.tests
.insert(result.name.clone(), TestEntry::Simple(TestState::Passing));
}
(Some(TestState::Pending), TestOutcome::Ignored) => {}
(Some(TestState::Passing), TestOutcome::Passed) => {}
(Some(TestState::Passing), TestOutcome::Failed) => {
violations.push(Violation::Regression {
test: result.name.clone(),
});
}
(Some(TestState::Passing), TestOutcome::Ignored) => {}
}
}
for name in status.tests.keys() {
if !seen_names.contains(name.as_str()) {
violations.push(Violation::TestDisappeared { test: name.clone() });
}
}
let history_violations = check_history_snapshots(history_snapshots, status.baseline.is_some());
for hv in history_violations {
match hv {
HistoryViolation::SkippedPending { test, commit } => {
violations.push(Violation::SkippedPending { test, commit });
}
}
}
EvalResult {
violations,
updated,
}
}
#[derive(Debug, Clone)]
pub struct RatchetOutcome {
pub violations: Vec<RatchetViolation>,
pub updated: StatusFile,
}
#[derive(Debug, Clone)]
pub enum RatchetViolation {
NewTestPassed { test: String },
Regression { test: String },
TestDisappeared { test: String },
}
pub fn check_ratchet(status: &StatusFile, results: &[TestResult]) -> RatchetOutcome {
let mut violations = Vec::new();
let mut updated = status.clone();
let seen_names: BTreeSet<&str> = results.iter().map(|r| r.name.as_str()).collect();
for result in results {
match (
status.tests.get(&result.name).map(|e| e.state()),
result.outcome,
) {
(None, TestOutcome::Failed) => {
updated
.tests
.insert(result.name.clone(), TestEntry::Simple(TestState::Pending));
}
(None, TestOutcome::Passed) => {
if result.name.ends_with(GATEKEEPER_TEST_NAME) {
updated
.tests
.insert(result.name.clone(), TestEntry::Simple(TestState::Passing));
} else {
violations.push(RatchetViolation::NewTestPassed {
test: result.name.clone(),
});
}
}
(None, TestOutcome::Ignored) => {}
(Some(TestState::Pending), TestOutcome::Failed) => {}
(Some(TestState::Pending), TestOutcome::Passed) => {
updated
.tests
.insert(result.name.clone(), TestEntry::Simple(TestState::Passing));
}
(Some(TestState::Pending), TestOutcome::Ignored) => {}
(Some(TestState::Passing), TestOutcome::Passed) => {}
(Some(TestState::Passing), TestOutcome::Failed) => {
violations.push(RatchetViolation::Regression {
test: result.name.clone(),
});
}
(Some(TestState::Passing), TestOutcome::Ignored) => {}
}
}
for name in status.tests.keys() {
if !seen_names.contains(name.as_str()) {
violations.push(RatchetViolation::TestDisappeared { test: name.clone() });
}
}
RatchetOutcome {
violations,
updated,
}
}