diff --git a/crates/engine/src/file_access.rs b/crates/engine/src/file_access.rs index 8e6fdaf17..500054705 100644 --- a/crates/engine/src/file_access.rs +++ b/crates/engine/src/file_access.rs @@ -499,8 +499,11 @@ mod tests { let mut offenders = vec![]; for path in files { let name = path.file_name().and_then(|n| n.to_str()).unwrap_or(""); - // `io` and this module are the checked gateway; `tests*.rs` are test-only modules. - if path.parent() == Some(src.as_path()) && (name == "io.rs" || name == "file_access.rs" || name.starts_with("tests")) + // `io` and this module are the checked gateway; `tests*.rs` files and `tests/` + // directories are test-only modules. + if path.parent() == Some(src.as_path()) && (name == "io.rs" || name == "file_access.rs") + || name.starts_with("tests") + || path.strip_prefix(&src).is_ok_and(|rel| rel.components().any(|c| c.as_os_str() == "tests")) || path.starts_with(src.join("io")) || path == src.join("recovery/desktop.rs") { diff --git a/crates/ui-egui/src/i18n/audit.rs b/crates/ui-egui/src/i18n/audit.rs index 4b0eb0c68..8871d5564 100644 --- a/crates/ui-egui/src/i18n/audit.rs +++ b/crates/ui-egui/src/i18n/audit.rs @@ -523,9 +523,7 @@ fn literals(code: &str) -> Vec { /// reasons resolved. fn disabled_reasons() -> std::collections::BTreeSet { let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../engine/src/cmd"); - let mut files: Vec<_> = - std::fs::read_dir(&dir).unwrap().map(|e| e.unwrap().path()).filter(|p| p.extension().is_some_and(|e| e == "rs")).collect(); - files.sort(); + let files = crate::rust_sources(&dir); let all: String = files.iter().map(|p| std::fs::read_to_string(p).unwrap()).collect::>().join("\n"); // A named check's bodies (any file's `fn name(…) -> Option<&'static str>`). let checks = |name: &str| -> Vec<&str> { diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index dca58282f..d082453cd 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -3412,6 +3412,26 @@ fn control_click_as_secondary(events: &mut [egui::Event], held: &mut bool) { #[cfg(test)] mod tests_perf; +/// Every `.rs` file under `dir`, subdirectories included, in a fixed order: tests that scan the +/// sources must keep seeing code that moves into a module directory. +#[cfg(test)] +pub(crate) fn rust_sources(dir: &std::path::Path) -> Vec { + let mut files = vec![]; + let mut dirs = vec![dir.to_path_buf()]; + while let Some(dir) = dirs.pop() { + for entry in std::fs::read_dir(&dir).unwrap() { + let path = entry.unwrap().path(); + if path.is_dir() { + dirs.push(path); + } else if path.extension().is_some_and(|e| e == "rs") { + files.push(path); + } + } + } + files.sort(); + files +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/ui-egui/src/ribbon.rs b/crates/ui-egui/src/ribbon.rs index eb3366e7d..9e6a16da9 100644 --- a/crates/ui-egui/src/ribbon.rs +++ b/crates/ui-egui/src/ribbon.rs @@ -3467,10 +3467,14 @@ mod tests { app.run("layout.pageSetup", json!({})).unwrap(); assert!(matches!(app.dialog, Some(crate::dialogs::Dialog::PageSetup { .. }))); // `ui.dialog` needs a dialog name, so it can't be a launcher. - let launchers: Vec<&str> = include_str!("ribbon.rs") - .split("group(ui, \"") - .filter_map(|g| g.split_once('"')?.1.strip_prefix(", Some(\"")?.split('"').next()) + let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); + let ribbon: String = crate::rust_sources(&src) + .iter() + .filter(|p| **p == src.join("ribbon.rs") || p.starts_with(src.join("ribbon"))) + .map(|p| std::fs::read_to_string(p).unwrap()) .collect(); + let launchers: Vec<&str> = + ribbon.split("group(ui, \"").filter_map(|g| g.split_once('"')?.1.strip_prefix(", Some(\"")?.split('"').next()).collect(); assert!(launchers.contains(&"layout.pageSetup"), "{launchers:?}"); assert!(!launchers.contains(&"ui.dialog"), "{launchers:?}"); } diff --git a/crates/ui-egui/src/theme.rs b/crates/ui-egui/src/theme.rs index b3495822d..4a70c5626 100644 --- a/crates/ui-egui/src/theme.rs +++ b/crates/ui-egui/src/theme.rs @@ -983,10 +983,9 @@ mod tests { fn interface_colours_come_from_the_theme() { let banned = ["fill(crate::theme::APP_COLOR)", "0x58, 0x65, 0xF2", "0x47, 0x52, 0xC4", "0xFF, 0xF1, 0x76", "0xD1, 0x3B, 0x3B"]; let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); - for entry in std::fs::read_dir(&dir).unwrap() { - let path = entry.unwrap().path(); + for path in crate::rust_sources(&dir) { let name = path.file_name().unwrap().to_string_lossy().to_string(); - if !name.ends_with(".rs") || name == "theme.rs" || name == "icons.rs" { + if path == dir.join("theme.rs") || path == dir.join("icons.rs") { continue; } let src = std::fs::read_to_string(&path).unwrap(); @@ -1028,11 +1027,8 @@ mod tests { ctx.run_ui(egui::RawInput::default(), |_| {}).drop_without_applying_deltas(); let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); let mut symbols = BTreeSet::new(); - for entry in std::fs::read_dir(&dir).unwrap() { - let path = entry.unwrap().path(); - if path.extension().is_none_or(|e| e != "rs") { - continue; - } + // The i18n module's literals are catalog and pseudo-locale test text, not interface text. + for path in crate::rust_sources(&dir).into_iter().filter(|p| !p.starts_with(dir.join("i18n"))) { let code = std::fs::read_to_string(&path).unwrap(); // Characters inside string literals, line by line (good enough for this code base). for line in code.lines().filter(|l| !l.trim_start().starts_with("//")) { @@ -1041,7 +1037,7 @@ mod tests { symbols.extend( part.chars() .filter(|c| !c.is_ascii() && !c.is_alphabetic() && !c.is_whitespace()) - .map(|c| (c, path.file_name().unwrap().to_string_lossy().to_string())), + .map(|c| (c, path.strip_prefix(&dir).unwrap().to_string_lossy().replace('\\', "/"))), ); } } @@ -1052,7 +1048,7 @@ mod tests { // The ➢ bullet is a value for `para.bullets`; its menu tile draws the shape instead. let exempt = |c: char, file: &str| match file { "equation_tab.rs" => matches!(c, '≜' | '┬'), - "ribbon.rs" => c == '➢', + _ if file == "ribbon.rs" || file.starts_with("ribbon/") => c == '➢', _ => false, }; symbols.retain(|(c, file)| !exempt(*c, file));