1use std::collections::BTreeSet;
5use std::ffi::OsStr;
6use std::fs;
7use std::io::Write;
8use std::path::{Path, PathBuf};
9
10const ISSUES_TXT_HEADER: &str = r#"============================================================
11 ⚠️⚠️⚠️NOTHING SHOULD EVER BE ADDED TO THIS LIST⚠️⚠️⚠️
12============================================================
13"#;
14
15pub fn check(root_path: &Path, bless: bool, bad: &mut bool) {
16 let path = &root_path.join("tests");
17
18 let mut prev_line = "";
21 let mut is_sorted = true;
22 let allowed_issue_names: BTreeSet<_> = include_str!("issues.txt")
23 .strip_prefix(ISSUES_TXT_HEADER)
24 .unwrap()
25 .lines()
26 .inspect(|&line| {
27 if prev_line > line {
28 is_sorted = false;
29 }
30
31 prev_line = line;
32 })
33 .collect();
34
35 if !is_sorted && !bless {
36 tidy_error!(
37 bad,
38 "`src/tools/tidy/src/issues.txt` is not in order, mostly because you modified it manually,
39 please only update it with command `x test tidy --bless`"
40 );
41 }
42
43 deny_new_top_level_ui_tests(bad, &path.join("ui"));
44
45 let remaining_issue_names = recursively_check_ui_tests(bad, path, &allowed_issue_names);
46
47 if bless && (!remaining_issue_names.is_empty() || !is_sorted) {
51 let tidy_src = root_path.join("src/tools/tidy/src");
52 let blessed_issues_path = tidy_src.join("issues_blessed.txt");
55 let mut blessed_issues_txt = fs::File::create(&blessed_issues_path).unwrap();
56 blessed_issues_txt.write_all(ISSUES_TXT_HEADER.as_bytes()).unwrap();
57 for filename in allowed_issue_names.difference(&remaining_issue_names) {
59 writeln!(blessed_issues_txt, "{filename}").unwrap();
60 }
61 let old_issues_path = tidy_src.join("issues.txt");
62 fs::rename(blessed_issues_path, old_issues_path).unwrap();
63 } else {
64 for file_name in remaining_issue_names {
65 let mut p = PathBuf::from(path);
66 p.push(file_name);
67 tidy_error!(
68 bad,
69 "file `{}` no longer exists and should be removed from the exclusions in `src/tools/tidy/src/issues.txt`",
70 p.display()
71 );
72 }
73 }
74}
75
76fn deny_new_top_level_ui_tests(bad: &mut bool, tests_path: &Path) {
77 let top_level_ui_tests = walkdir::WalkDir::new(tests_path)
84 .min_depth(1)
85 .max_depth(1)
86 .follow_links(false)
87 .same_file_system(true)
88 .into_iter()
89 .flatten()
90 .filter(|e| {
91 let file_name = e.file_name();
92 file_name != ".gitattributes" && file_name != "README.md"
93 })
94 .filter(|e| !e.file_type().is_dir());
95 for entry in top_level_ui_tests {
96 tidy_error!(
97 bad,
98 "ui tests should be added under meaningful subdirectories: `{}`",
99 entry.path().display()
100 )
101 }
102}
103
104fn recursively_check_ui_tests<'issues>(
105 bad: &mut bool,
106 path: &Path,
107 allowed_issue_names: &'issues BTreeSet<&'issues str>,
108) -> BTreeSet<&'issues str> {
109 let mut remaining_issue_names: BTreeSet<&str> = allowed_issue_names.clone();
110
111 let (ui, ui_fulldeps) = (path.join("ui"), path.join("ui-fulldeps"));
112 let paths = [ui.as_path(), ui_fulldeps.as_path()];
113 crate::walk::walk_no_read(&paths, |_, _| false, &mut |entry| {
114 let file_path = entry.path();
115 if let Some(ext) = file_path.extension().and_then(OsStr::to_str) {
116 check_unexpected_extension(bad, file_path, ext);
117
118 let testname =
121 file_path.file_name().unwrap().to_str().unwrap().split_once('.').unwrap().0;
122 if ext == "stderr" || ext == "stdout" || ext == "fixed" {
123 check_stray_output_snapshot(bad, file_path, testname);
124 check_empty_output_snapshot(bad, file_path);
125 }
126
127 deny_new_nondescriptive_test_names(
128 bad,
129 path,
130 &mut remaining_issue_names,
131 file_path,
132 testname,
133 ext,
134 );
135 }
136 });
137 remaining_issue_names
138}
139
140fn check_unexpected_extension(bad: &mut bool, file_path: &Path, ext: &str) {
141 const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[
142 "rs", "stderr", "svg", "stdout", "fixed", "md", "ftl", ];
150
151 const EXTENSION_EXCEPTION_PATHS: &[&str] = &[
152 "tests/ui/asm/named-asm-labels.s", "tests/ui/codegen/mismatched-data-layout.json", "tests/ui/check-cfg/my-awesome-platform.json", "tests/ui/argfile/commandline-argfile-badutf8.args", "tests/ui/argfile/commandline-argfile.args", "tests/ui/crate-loading/auxiliary/libfoo.rlib", "tests/ui/include-macros/data.bin", "tests/ui/include-macros/file.txt", "tests/ui/macros/macro-expanded-include/file.txt", "tests/ui/macros/not-utf8.bin", "tests/ui/macros/syntax-extension-source-utils-files/includeme.fragment", "tests/ui/proc-macro/auxiliary/included-file.txt", "tests/ui/unpretty/auxiliary/data.txt", "tests/ui/invalid/foo.natvis.xml", "tests/ui/sanitizer/dataflow-abilist.txt", "tests/ui/shell-argfiles/shell-argfiles.args", "tests/ui/shell-argfiles/shell-argfiles-badquotes.args", "tests/ui/shell-argfiles/shell-argfiles-via-argfile-shell.args", "tests/ui/shell-argfiles/shell-argfiles-via-argfile.args", "tests/ui/std/windows-bat-args1.bat", "tests/ui/std/windows-bat-args2.bat", "tests/ui/std/windows-bat-args3.bat", ];
175
176 if !(EXPECTED_TEST_FILE_EXTENSIONS.contains(&ext)
179 || EXTENSION_EXCEPTION_PATHS.iter().any(|path| file_path.ends_with(path)))
180 {
181 tidy_error!(bad, "file {} has unexpected extension {}", file_path.display(), ext);
182 }
183}
184
185fn check_stray_output_snapshot(bad: &mut bool, file_path: &Path, testname: &str) {
186 if !file_path.with_file_name(testname).with_extension("rs").exists()
198 && !testname.contains("ignore-tidy")
199 {
200 tidy_error!(bad, "Stray file with UI testing output: {:?}", file_path);
201 }
202}
203
204fn check_empty_output_snapshot(bad: &mut bool, file_path: &Path) {
205 if let Ok(metadata) = fs::metadata(file_path)
206 && metadata.len() == 0
207 {
208 tidy_error!(bad, "Empty file with UI testing output: {:?}", file_path);
209 }
210}
211
212fn deny_new_nondescriptive_test_names(
213 bad: &mut bool,
214 path: &Path,
215 remaining_issue_names: &mut BTreeSet<&str>,
216 file_path: &Path,
217 testname: &str,
218 ext: &str,
219) {
220 if ext == "rs"
221 && let Some(test_name) = static_regex!(r"^issues?[-_]?(\d{3,})").captures(testname)
222 {
223 let stripped_path = file_path
225 .strip_prefix(path)
226 .unwrap()
227 .to_str()
228 .unwrap()
229 .replace(std::path::MAIN_SEPARATOR_STR, "/");
230
231 if !remaining_issue_names.remove(stripped_path.as_str())
232 && !stripped_path.starts_with("ui/issues/")
233 {
234 tidy_error!(
235 bad,
236 "file `tests/{stripped_path}` must begin with a descriptive name, consider `{{reason}}-issue-{issue_n}.rs`",
237 issue_n = &test_name[1],
238 );
239 }
240 }
241}