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