use std::path::Path;
use fairlead_core::config::{Config, Invoke};
const SELECTORS: [&str; 5] = ["{files}", "{packages}", "{class}", "{classes}", "{dir}"];
pub fn runner_warnings(config: &Config) -> Vec<String> {
config
.tests
.runners
.items()
.iter()
.filter(|r| r.invoke == Invoke::Once)
.filter(|r| {
!r.command
.iter()
.any(|arg| SELECTORS.iter().any(|s| arg.contains(s)))
})
.map(|r| {
format!(
"tests.runners `{}`: its command names no `{{files}}`, so it runs its whole suite whenever any of its tests is selected; put `{{files}}` where the test files go",
r.id
)
})
.collect()
}
pub fn smoke_warnings(files: &[String], config: &Config) -> Vec<String> {
use fairlead_core::pattern::Pattern;
let mut out = Vec::new();
for runner in config.tests.runners.items() {
let mine: Vec<Pattern> = (runner.matches.iter())
.filter_map(|g| Pattern::new(g).ok())
.collect();
for glob in &runner.smoke {
let Ok(pattern) = Pattern::new(glob) else {
continue;
};
let found = files
.iter()
.any(|f| pattern.is_match(f) && mine.iter().any(|m| m.is_match(f)));
if !found {
out.push(format!(
"tests.runners `{}`: its smoke entry `{glob}` names no test file the runner matches, so it stands for nothing; name files that exist, such as after a rename",
runner.id
));
}
}
}
out
}
pub fn appeared(root: &Path, config: &Config) -> Option<(String, &'static str)> {
if config.profile.is_full() {
return None;
}
let ids = crate::init_cmd::runners_now(root);
if ids.is_empty() {
return None;
}
Some((
format!(
"this repository has tests `{}` can run, and the guard-only profile plans none",
ids.join(", ")
),
"`fairlead init --dry-run` prints the runner; add it to fairlead.toml and remove `profile = \"guard\"`",
))
}
pub fn tests_appeared(root: &Path, config: &Config) -> Option<String> {
appeared(root, config).map(|(what, fix)| format!("tests: {what}. {fix}\n"))
}
#[cfg(test)]
mod tests {
use super::*;
fn config(text: &str) -> Config {
toml::from_str(text).unwrap()
}
#[test]
fn a_runner_that_names_no_test_is_warned_about_and_one_that_does_is_not() {
let c = config(
r#"
[[tests.runners]]
id = "whole"
match = ["e2e/**"]
command = ["npx", "playwright", "test"]
[[tests.runners]]
id = "named"
match = ["test/**"]
command = ["npx", "vitest", "run", "{files}"]
[[tests.runners]]
id = "go"
match = ["**/*_test.go"]
command = ["go", "test", "{packages}"]
[[tests.runners]]
id = "per"
match = ["packages/**"]
invoke = "per-module"
cwd = "{module}"
command = ["npm", "test"]
"#,
);
let warnings = runner_warnings(&c);
assert_eq!(warnings.len(), 1, "{warnings:?}");
assert!(warnings[0].contains("`whole`"), "{warnings:?}");
}
}