use cargo_metadata::{DependencyKind, Metadata};
const HARNESSES: [&str; 2] = ["trybuild", "compiletest_rs"];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompileFailTarget {
pub package: String,
pub target: String,
pub harness: String,
}
pub(super) fn compile_fail_targets(metadata: &Metadata) -> Vec<CompileFailTarget> {
let mut found = Vec::new();
for package in metadata.workspace_packages() {
let Some(harness) = package
.dependencies
.iter()
.filter(|dependency| dependency.kind == DependencyKind::Development)
.map(|dependency| dependency.name.as_str())
.find(|name| HARNESSES.contains(name))
else {
continue;
};
for target in package.targets.iter().filter(|target| target.test) {
let Ok(source) = std::fs::read_to_string(&target.src_path) else {
continue;
};
if mentions(&source, harness) {
found.push(CompileFailTarget {
package: package.name.as_str().to_owned(),
target: target.name.clone(),
harness: harness.to_owned(),
});
}
}
}
found.sort_by(|left, right| left.target.cmp(&right.target));
found.dedup();
found
}
fn mentions(source: &str, harness: &str) -> bool {
source.contains(harness)
}
#[must_use]
pub fn advice(targets: &[CompileFailTarget]) -> Option<String> {
if targets.is_empty() {
return None;
}
let named: Vec<String> = targets
.iter()
.map(|target| format!("`{}` in {} ({})", target.target, target.package, target.harness))
.collect();
let flags: Vec<String> = targets.iter().map(|target| format!("--exclude-test {}", target.target)).collect();
Some(format!(
"{} {} the compiler once per case, so every mutant pays for a full rustc run and almost none are convicted by it: {}.\n\
If {} is not part of what should be judging these mutants, exclude it with `{}`.\n\
Left in, it is likely to make this run take hours.",
crate::report::quantity(targets.len(), "test target"),
if targets.len() == 1 { "invokes" } else { "invoke" },
named.join(", "),
if targets.len() == 1 { "it" } else { "any of them" },
flags.join(" "),
))
}
#[cfg(test)]
mod tests {
use super::*;
fn target(name: &str) -> CompileFailTarget {
CompileFailTarget {
package: "routerama".to_owned(),
target: name.to_owned(),
harness: "trybuild".to_owned(),
}
}
#[test]
fn a_source_naming_the_harness_is_recognized() {
assert!(mentions("fn main() { trybuild::TestCases::new(); }", "trybuild"));
assert!(mentions("use compiletest_rs as compiletest;", "compiletest_rs"));
}
#[test]
fn an_ordinary_test_source_is_not_recognized() {
assert!(!mentions("#[test]\nfn routes_are_matched() {}", "trybuild"));
}
#[test]
fn no_compile_fail_target_produces_no_advice() {
assert_eq!(advice(&[]), None);
}
#[test]
fn the_advice_names_the_target_and_the_flag_that_removes_it() {
let text = advice(&[target("router_compile_fail")]).expect("one target is enough to advise about");
assert!(text.contains("`router_compile_fail` in routerama (trybuild)"), "{text}");
assert!(text.contains("--exclude-test router_compile_fail"), "{text}");
assert!(text.contains("1 test target invokes"), "{text}");
}
#[test]
fn several_targets_are_advised_about_together() {
let text = advice(&[target("router_compile_fail"), target("feature_gates")]).expect("two targets are advised about");
assert!(text.contains("2 test targets invoke"), "{text}");
assert!(
text.contains("--exclude-test router_compile_fail --exclude-test feature_gates"),
"{text}"
);
}
#[test]
fn the_advice_says_it_is_the_callers_decision() {
let text = advice(&[target("router_compile_fail")]).expect("one target is enough to advise about");
assert!(text.contains("If it is not part of what should be judging"), "{text}");
}
}