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