tidy/
tests_revision_unpaired_stdout_stderr.rs
1use std::collections::{BTreeMap, BTreeSet};
4use std::ffi::OsStr;
5use std::path::Path;
6
7use crate::iter_header::*;
8use crate::walk::*;
9
10const IGNORES: &[&str] = &[
13 "polonius",
14 "chalk",
15 "split-dwarf",
16 "split-dwarf-single",
17 "next-solver-coherence",
18 "next-solver",
19 "run",
20];
21const EXTENSIONS: &[&str] = &["stdout", "stderr"];
22const SPECIAL_TEST: &str = "tests/ui/command/need-crate-arg-ignore-tidy.x.rs";
23
24pub fn check(tests_path: impl AsRef<Path>, bad: &mut bool) {
25 walk_dir(tests_path.as_ref(), filter, &mut |entry| {
27 let mut files_under_inspection = BTreeSet::new();
30 for sibling in std::fs::read_dir(entry.path()).unwrap() {
31 let Ok(sibling) = sibling else {
32 continue;
33 };
34
35 if sibling.path().is_dir() {
36 continue;
37 }
38
39 let sibling_path = sibling.path();
40
41 let Some(ext) = sibling_path.extension().map(OsStr::to_str).flatten() else {
42 continue;
43 };
44
45 if ext == "rs" || EXTENSIONS.contains(&ext) {
46 files_under_inspection.insert(sibling_path);
47 }
48 }
49
50 let mut test_info = BTreeMap::new();
51
52 for test in
53 files_under_inspection.iter().filter(|f| f.extension().is_some_and(|ext| ext == "rs"))
54 {
55 if test.ends_with(SPECIAL_TEST) {
56 continue;
57 }
58
59 let mut expected_revisions = BTreeSet::new();
60
61 let Ok(contents) = std::fs::read_to_string(test) else { continue };
62
63 iter_header(&contents, &mut |HeaderLine { revision, directive, .. }| {
65 if revision.is_some() {
68 return;
69 }
70
71 let directive = directive.trim();
72
73 if directive.starts_with("revisions") {
74 let Some((name, value)) = directive.split_once([':', ' ']) else {
75 return;
76 };
77
78 if name == "revisions" {
79 let revs = value.split(' ');
80 for rev in revs {
81 expected_revisions.insert(rev.to_owned());
82 }
83 }
84 }
85 });
86
87 let Some(test_name) = test.file_stem().map(OsStr::to_str).flatten() else {
88 continue;
89 };
90
91 assert!(
92 !test_name.contains('.'),
93 "test name cannot contain dots '.': `{}`",
94 test.display()
95 );
96
97 test_info.insert(test_name.to_string(), (test, expected_revisions));
98 }
99
100 for sibling in files_under_inspection.iter().filter(|f| {
105 f.extension().map(OsStr::to_str).flatten().is_some_and(|ext| EXTENSIONS.contains(&ext))
106 }) {
107 let Some(filename) = sibling.file_name().map(OsStr::to_str).flatten() else {
108 continue;
109 };
110
111 let filename_components = filename.split('.').collect::<Vec<_>>();
112 let [file_prefix, ..] = &filename_components[..] else {
113 continue;
114 };
115
116 let Some((test_path, expected_revisions)) = test_info.get(*file_prefix) else {
117 continue;
118 };
119
120 match &filename_components[..] {
121 [] | [_] => return,
123 [_, _] if !expected_revisions.is_empty() => {
124 tidy_error!(
126 bad,
127 "found unrevisioned output file `{}` for a revisioned test `{}`",
128 sibling.display(),
129 test_path.display(),
130 );
131 }
132 [_, _] => return,
133 [_, found_revision, .., extension] => {
134 if !IGNORES.contains(&found_revision)
135 && !expected_revisions.contains(*found_revision)
136 && !(*extension == "stderr" && ["32bit", "64bit"].contains(&found_revision))
138 {
139 tidy_error!(
142 bad,
143 "found output file `{}` for unexpected revision `{}` of test `{}`",
144 sibling.display(),
145 found_revision,
146 test_path.display()
147 );
148 }
149 }
150 }
151 }
152 });
153}
154
155fn filter(path: &Path) -> bool {
156 filter_dirs(path) || (path.file_name().is_some_and(|name| name == "auxiliary")) }