1use std::path::{Path, PathBuf};
2use std::sync::OnceLock;
3
4pub fn normalize_test_id(id: &str) -> String {
6 id.split_whitespace()
7 .collect::<Vec<_>>()
8 .join(" ")
9 .to_lowercase()
10}
11
12pub fn build_test_id<S: AsRef<str>>(file: &str, parts: &[S]) -> String {
15 let mut id = relative_to_root(file);
16 for p in parts {
17 id.push_str(" > ");
18 id.push_str(p.as_ref());
19 }
20 id
21}
22
23pub fn relative_to_root(file: &str) -> String {
26 let path = Path::new(file);
27 let rel = if path.is_absolute() {
28 path.strip_prefix(project_root()).unwrap_or(path)
29 } else {
30 path
31 };
32 rel.to_string_lossy().replace('\\', "/")
33}
34
35fn project_root() -> &'static Path {
36 static ROOT: OnceLock<PathBuf> = OnceLock::new();
37 ROOT.get_or_init(|| {
38 let start = std::env::var_os("CARGO_MANIFEST_DIR")
39 .map(PathBuf::from)
40 .or_else(|| std::env::current_dir().ok())
41 .unwrap_or_default();
42 find_project_root(&start)
43 })
44}
45
46pub fn find_project_root(start: &Path) -> PathBuf {
49 let mut nearest = None;
50 let mut workspace = None;
51 for dir in start.ancestors() {
52 let Ok(manifest) = std::fs::read_to_string(dir.join("Cargo.toml")) else {
53 continue;
54 };
55 nearest.get_or_insert_with(|| dir.to_path_buf());
56 if manifest.lines().any(|l| l.trim() == "[workspace]") {
57 workspace = Some(dir.to_path_buf());
58 }
59 }
60 workspace.or(nearest).unwrap_or_else(|| start.to_path_buf())
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn normalizes() {
69 assert_eq!(
70 normalize_test_id(" Tests/Auth.rs >\tLogin > Works "),
71 "tests/auth.rs > login > works"
72 );
73 }
74
75 #[test]
76 fn builds_with_forward_slashes() {
77 assert_eq!(
78 build_test_id("tests\\auth.rs", &["mod_a", "it works"]),
79 "tests/auth.rs > mod_a > it works"
80 );
81 }
82
83 #[test]
84 fn finds_outermost_workspace() {
85 let dir = tempfile::tempdir().unwrap();
86 let ws = dir.path();
87 let member = ws.join("crates/a");
88 std::fs::create_dir_all(member.join("src")).unwrap();
89 std::fs::write(ws.join("Cargo.toml"), "[workspace]\nmembers=[]\n").unwrap();
90 std::fs::write(member.join("Cargo.toml"), "[package]\nname=\"a\"\n").unwrap();
91 assert_eq!(find_project_root(&member.join("src")), ws);
92
93 std::fs::write(ws.join("Cargo.toml"), "# not a workspace\n").unwrap();
94 assert_eq!(find_project_root(&member.join("src")), member);
95 }
96}