use camino::Utf8Path;
use crate::model::{Mutant, Outcome};
pub(super) fn findings(mutants: &[Mutant]) -> Vec<&Mutant> {
mutants
.iter()
.filter(|mutant| {
matches!(
mutant.outcome,
Outcome::Survived | Outcome::Timeout | Outcome::OutOfMemory | Outcome::NoCoverage
)
})
.collect()
}
pub(super) fn relative(path: &Utf8Path, root: &Utf8Path) -> String {
let path = path.strip_prefix(root).unwrap_or(path).as_str();
#[cfg(windows)]
let relative = path.replace('\\', "/");
#[cfg(not(windows))]
let relative = path.to_owned();
relative
}
pub(super) fn describe(mutant: &Mutant) -> String {
match mutant.outcome {
Outcome::NoCoverage => format!("No test reaches this code: {}.", mutant.summary()),
Outcome::Timeout => format!("{} and the test run timed out before an assertion rejected it.", mutant.summary()),
Outcome::OutOfMemory => {
format!(
"{} and the test run exceeded its memory limit before an assertion rejected it.",
mutant.summary()
)
}
_other => format!("{} and no test failed.", mutant.summary()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::ci_fixture::{mutant, root};
#[test]
fn every_undetected_outcome_is_a_finding() {
let mutants = vec![
mutant("/w/src/a.rs", 1, "relational.gt_to_ge", Outcome::Killed),
mutant("/w/src/a.rs", 2, "relational.gt_to_ge", Outcome::Survived),
mutant("/w/src/a.rs", 3, "relational.gt_to_ge", Outcome::Timeout),
mutant("/w/src/a.rs", 4, "relational.gt_to_ge", Outcome::NoCoverage),
mutant("/w/src/a.rs", 5, "relational.gt_to_ge", Outcome::CompileError),
mutant("/w/src/a.rs", 6, "relational.gt_to_ge", Outcome::OutOfMemory),
];
let found = findings(&mutants);
assert_eq!(found.len(), 4);
assert_eq!(found[0].line, 2);
assert_eq!(found[1].line, 3);
assert_eq!(found[2].line, 4);
assert_eq!(found[3].line, 6);
}
#[test]
fn a_path_outside_the_root_is_left_alone() {
assert_eq!(relative(Utf8Path::new("/elsewhere/a.rs"), &root()), "/elsewhere/a.rs");
}
#[cfg(not(windows))]
#[test]
fn two_paths_differing_only_by_a_backslash_do_not_collide() {
use crate::ci::{Level, sarif};
let mutants = vec![
mutant(r"/w/src/a\b.rs", 1, "relational.gt_to_ge", Outcome::Survived),
mutant("/w/src/a/b.rs", 1, "relational.gt_to_ge", Outcome::Survived),
];
assert_ne!(
relative(&mutants[0].file, &root()),
relative(&mutants[1].file, &root()),
"two files must not share one name"
);
let (log, _truncation) = sarif(&mutants, &root(), Level::Note).expect("a sarif log");
let document: serde_json::Value = serde_json::from_str(&log).expect("valid json");
let uris: Vec<String> = document["runs"][0]["results"]
.as_array()
.expect("results")
.iter()
.map(|result| result["locations"][0]["physicalLocation"]["artifactLocation"]["uri"].to_string())
.collect();
assert_eq!(uris.len(), 2, "{document}");
assert_ne!(uris[0], uris[1], "{document}");
let table = crate::ci::summary(&mutants, &root());
let rows = table.lines().filter(|line| line.contains("b.rs")).count();
assert_eq!(rows, 2, "{table}");
}
}