Skip to content

Commit 50b816f

Browse files
committed
Auto merge of #110319 - ferrocene:pa-more-ignore-reasons, r=ehuss
[compiletest] Add more test ignore reasons, `needs-` validation, and improved error messages This PR makes more improvements to the way compiletest ignoring headers are handled, following up on #108905: * Human-readable ignore reasons have been added for the remaining ignore causes (`needs-*` directives, `*llvm*` directives, and debugger version directives). All ignored tests should now have a human-readable reason. * The code handling `needs-*` directives has been refactored, and now invalid `needs-*` directive emit errors like `ignore-*` and `only-*`. * All errors are now displayed at startup (with line numbers) rather than just the first error of the first file. This PR is best reviewed commit-by-commit. r? `@ehuss`
2 parents 5cdb788 + 34a52df commit 50b816f

14 files changed

+467
-196
lines changed

src/tools/compiletest/src/header.rs

+133-178
Large diffs are not rendered by default.

src/tools/compiletest/src/header/cfg.rs

+47-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,43 @@
11
use crate::common::{CompareMode, Config, Debugger};
2+
use crate::header::IgnoreDecision;
23
use std::collections::HashSet;
34

45
const EXTRA_ARCHS: &[&str] = &["spirv"];
56

7+
pub(super) fn handle_ignore(config: &Config, line: &str) -> IgnoreDecision {
8+
let parsed = parse_cfg_name_directive(config, line, "ignore");
9+
match parsed.outcome {
10+
MatchOutcome::NoMatch => IgnoreDecision::Continue,
11+
MatchOutcome::Match => IgnoreDecision::Ignore {
12+
reason: match parsed.comment {
13+
Some(comment) => format!("ignored {} ({comment})", parsed.pretty_reason.unwrap()),
14+
None => format!("ignored {}", parsed.pretty_reason.unwrap()),
15+
},
16+
},
17+
MatchOutcome::Invalid => IgnoreDecision::Error { message: format!("invalid line: {line}") },
18+
MatchOutcome::External => IgnoreDecision::Continue,
19+
MatchOutcome::NotADirective => IgnoreDecision::Continue,
20+
}
21+
}
22+
23+
pub(super) fn handle_only(config: &Config, line: &str) -> IgnoreDecision {
24+
let parsed = parse_cfg_name_directive(config, line, "only");
25+
match parsed.outcome {
26+
MatchOutcome::Match => IgnoreDecision::Continue,
27+
MatchOutcome::NoMatch => IgnoreDecision::Ignore {
28+
reason: match parsed.comment {
29+
Some(comment) => {
30+
format!("only executed {} ({comment})", parsed.pretty_reason.unwrap())
31+
}
32+
None => format!("only executed {}", parsed.pretty_reason.unwrap()),
33+
},
34+
},
35+
MatchOutcome::Invalid => IgnoreDecision::Error { message: format!("invalid line: {line}") },
36+
MatchOutcome::External => IgnoreDecision::Continue,
37+
MatchOutcome::NotADirective => IgnoreDecision::Continue,
38+
}
39+
}
40+
641
/// Parses a name-value directive which contains config-specific information, e.g., `ignore-x86`
742
/// or `normalize-stderr-32bit`.
843
pub(super) fn parse_cfg_name_directive<'a>(
@@ -11,10 +46,10 @@ pub(super) fn parse_cfg_name_directive<'a>(
1146
prefix: &str,
1247
) -> ParsedNameDirective<'a> {
1348
if !line.as_bytes().starts_with(prefix.as_bytes()) {
14-
return ParsedNameDirective::invalid();
49+
return ParsedNameDirective::not_a_directive();
1550
}
1651
if line.as_bytes().get(prefix.len()) != Some(&b'-') {
17-
return ParsedNameDirective::invalid();
52+
return ParsedNameDirective::not_a_directive();
1853
}
1954
let line = &line[prefix.len() + 1..];
2055

@@ -24,7 +59,7 @@ pub(super) fn parse_cfg_name_directive<'a>(
2459
// Some of the matchers might be "" depending on what the target information is. To avoid
2560
// problems we outright reject empty directives.
2661
if name == "" {
27-
return ParsedNameDirective::invalid();
62+
return ParsedNameDirective::not_a_directive();
2863
}
2964

3065
let mut outcome = MatchOutcome::Invalid;
@@ -218,8 +253,13 @@ pub(super) struct ParsedNameDirective<'a> {
218253
}
219254

220255
impl ParsedNameDirective<'_> {
221-
fn invalid() -> Self {
222-
Self { name: None, pretty_reason: None, comment: None, outcome: MatchOutcome::NoMatch }
256+
fn not_a_directive() -> Self {
257+
Self {
258+
name: None,
259+
pretty_reason: None,
260+
comment: None,
261+
outcome: MatchOutcome::NotADirective,
262+
}
223263
}
224264
}
225265

@@ -233,6 +273,8 @@ pub(super) enum MatchOutcome {
233273
Invalid,
234274
/// The directive is handled by other parts of our tooling.
235275
External,
276+
/// The line is not actually a directive.
277+
NotADirective,
236278
}
237279

238280
trait CustomContains {
+236
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
use crate::common::{Config, Debugger};
2+
use crate::header::IgnoreDecision;
3+
use crate::util;
4+
5+
pub(super) fn handle_needs(
6+
cache: &CachedNeedsConditions,
7+
config: &Config,
8+
ln: &str,
9+
) -> IgnoreDecision {
10+
// Note thet we intentionally still put the needs- prefix here to make the file show up when
11+
// grepping for a directive name, even though we could technically strip that.
12+
let needs = &[
13+
Need {
14+
name: "needs-asm-support",
15+
condition: config.has_asm_support(),
16+
ignore_reason: "ignored on targets without inline assembly support",
17+
},
18+
Need {
19+
name: "needs-sanitizer-support",
20+
condition: cache.sanitizer_support,
21+
ignore_reason: "ignored on targets without sanitizers support",
22+
},
23+
Need {
24+
name: "needs-sanitizer-address",
25+
condition: cache.sanitizer_address,
26+
ignore_reason: "ignored on targets without address sanitizer",
27+
},
28+
Need {
29+
name: "needs-sanitizer-cfi",
30+
condition: cache.sanitizer_cfi,
31+
ignore_reason: "ignored on targets without CFI sanitizer",
32+
},
33+
Need {
34+
name: "needs-sanitizer-kcfi",
35+
condition: cache.sanitizer_kcfi,
36+
ignore_reason: "ignored on targets without kernel CFI sanitizer",
37+
},
38+
Need {
39+
name: "needs-sanitizer-kasan",
40+
condition: cache.sanitizer_kasan,
41+
ignore_reason: "ignored on targets without kernel address sanitizer",
42+
},
43+
Need {
44+
name: "needs-sanitizer-leak",
45+
condition: cache.sanitizer_leak,
46+
ignore_reason: "ignored on targets without leak sanitizer",
47+
},
48+
Need {
49+
name: "needs-sanitizer-memory",
50+
condition: cache.sanitizer_memory,
51+
ignore_reason: "ignored on targets without memory sanitizer",
52+
},
53+
Need {
54+
name: "needs-sanitizer-thread",
55+
condition: cache.sanitizer_thread,
56+
ignore_reason: "ignored on targets without thread sanitizer",
57+
},
58+
Need {
59+
name: "needs-sanitizer-hwaddress",
60+
condition: cache.sanitizer_hwaddress,
61+
ignore_reason: "ignored on targets without hardware-assisted address sanitizer",
62+
},
63+
Need {
64+
name: "needs-sanitizer-memtag",
65+
condition: cache.sanitizer_memtag,
66+
ignore_reason: "ignored on targets without memory tagging sanitizer",
67+
},
68+
Need {
69+
name: "needs-sanitizer-shadow-call-stack",
70+
condition: cache.sanitizer_shadow_call_stack,
71+
ignore_reason: "ignored on targets without shadow call stacks",
72+
},
73+
Need {
74+
name: "needs-run-enabled",
75+
condition: config.run_enabled(),
76+
ignore_reason: "ignored when running the resulting test binaries is disabled",
77+
},
78+
Need {
79+
name: "needs-unwind",
80+
condition: config.can_unwind(),
81+
ignore_reason: "ignored on targets without unwinding support",
82+
},
83+
Need {
84+
name: "needs-profiler-support",
85+
condition: std::env::var_os("RUSTC_PROFILER_SUPPORT").is_some(),
86+
ignore_reason: "ignored when profiler support is disabled",
87+
},
88+
Need {
89+
name: "needs-matching-clang",
90+
condition: config.run_clang_based_tests_with.is_some(),
91+
ignore_reason: "ignored when the used clang does not match the built LLVM",
92+
},
93+
Need {
94+
name: "needs-xray",
95+
condition: cache.xray,
96+
ignore_reason: "ignored on targets without xray tracing",
97+
},
98+
Need {
99+
name: "needs-rust-lld",
100+
condition: cache.rust_lld,
101+
ignore_reason: "ignored on targets without Rust's LLD",
102+
},
103+
Need {
104+
name: "needs-rust-lldb",
105+
condition: config.debugger != Some(Debugger::Lldb) || config.lldb_native_rust,
106+
ignore_reason: "ignored on targets without Rust's LLDB",
107+
},
108+
Need {
109+
name: "needs-i686-dlltool",
110+
condition: cache.i686_dlltool,
111+
ignore_reason: "ignored when dlltool for i686 is not present",
112+
},
113+
Need {
114+
name: "needs-x86_64-dlltool",
115+
condition: cache.x86_64_dlltool,
116+
ignore_reason: "ignored when dlltool for x86_64 is not present",
117+
},
118+
];
119+
120+
let (name, comment) = match ln.split_once([':', ' ']) {
121+
Some((name, comment)) => (name, Some(comment)),
122+
None => (ln, None),
123+
};
124+
125+
if !name.starts_with("needs-") {
126+
return IgnoreDecision::Continue;
127+
}
128+
129+
// Handled elsewhere.
130+
if name == "needs-llvm-components" {
131+
return IgnoreDecision::Continue;
132+
}
133+
134+
let mut found_valid = false;
135+
for need in needs {
136+
if need.name == name {
137+
if need.condition {
138+
found_valid = true;
139+
break;
140+
} else {
141+
return IgnoreDecision::Ignore {
142+
reason: if let Some(comment) = comment {
143+
format!("{} ({comment})", need.ignore_reason)
144+
} else {
145+
need.ignore_reason.into()
146+
},
147+
};
148+
}
149+
}
150+
}
151+
152+
if found_valid {
153+
IgnoreDecision::Continue
154+
} else {
155+
IgnoreDecision::Error { message: format!("invalid needs directive: {name}") }
156+
}
157+
}
158+
159+
struct Need {
160+
name: &'static str,
161+
condition: bool,
162+
ignore_reason: &'static str,
163+
}
164+
165+
pub(super) struct CachedNeedsConditions {
166+
sanitizer_support: bool,
167+
sanitizer_address: bool,
168+
sanitizer_cfi: bool,
169+
sanitizer_kcfi: bool,
170+
sanitizer_kasan: bool,
171+
sanitizer_leak: bool,
172+
sanitizer_memory: bool,
173+
sanitizer_thread: bool,
174+
sanitizer_hwaddress: bool,
175+
sanitizer_memtag: bool,
176+
sanitizer_shadow_call_stack: bool,
177+
xray: bool,
178+
rust_lld: bool,
179+
i686_dlltool: bool,
180+
x86_64_dlltool: bool,
181+
}
182+
183+
impl CachedNeedsConditions {
184+
pub(super) fn load(config: &Config) -> Self {
185+
let path = std::env::var_os("PATH").expect("missing PATH environment variable");
186+
let path = std::env::split_paths(&path).collect::<Vec<_>>();
187+
188+
let target = &&*config.target;
189+
Self {
190+
sanitizer_support: std::env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(),
191+
sanitizer_address: util::ASAN_SUPPORTED_TARGETS.contains(target),
192+
sanitizer_cfi: util::CFI_SUPPORTED_TARGETS.contains(target),
193+
sanitizer_kcfi: util::KCFI_SUPPORTED_TARGETS.contains(target),
194+
sanitizer_kasan: util::KASAN_SUPPORTED_TARGETS.contains(target),
195+
sanitizer_leak: util::LSAN_SUPPORTED_TARGETS.contains(target),
196+
sanitizer_memory: util::MSAN_SUPPORTED_TARGETS.contains(target),
197+
sanitizer_thread: util::TSAN_SUPPORTED_TARGETS.contains(target),
198+
sanitizer_hwaddress: util::HWASAN_SUPPORTED_TARGETS.contains(target),
199+
sanitizer_memtag: util::MEMTAG_SUPPORTED_TARGETS.contains(target),
200+
sanitizer_shadow_call_stack: util::SHADOWCALLSTACK_SUPPORTED_TARGETS.contains(target),
201+
xray: util::XRAY_SUPPORTED_TARGETS.contains(target),
202+
203+
// For tests using the `needs-rust-lld` directive (e.g. for `-Zgcc-ld=lld`), we need to find
204+
// whether `rust-lld` is present in the compiler under test.
205+
//
206+
// The --compile-lib-path is the path to host shared libraries, but depends on the OS. For
207+
// example:
208+
// - on linux, it can be <sysroot>/lib
209+
// - on windows, it can be <sysroot>/bin
210+
//
211+
// However, `rust-lld` is only located under the lib path, so we look for it there.
212+
rust_lld: config
213+
.compile_lib_path
214+
.parent()
215+
.expect("couldn't traverse to the parent of the specified --compile-lib-path")
216+
.join("lib")
217+
.join("rustlib")
218+
.join(target)
219+
.join("bin")
220+
.join(if config.host.contains("windows") { "rust-lld.exe" } else { "rust-lld" })
221+
.exists(),
222+
223+
// On Windows, dlltool.exe is used for all architectures.
224+
#[cfg(windows)]
225+
i686_dlltool: path.iter().any(|dir| dir.join("dlltool.exe").is_file()),
226+
#[cfg(windows)]
227+
x86_64_dlltool: path.iter().any(|dir| dir.join("dlltool.exe").is_file()),
228+
229+
// For non-Windows, there are architecture specific dlltool binaries.
230+
#[cfg(not(windows))]
231+
i686_dlltool: path.iter().any(|dir| dir.join("i686-w64-mingw32-dlltool").is_file()),
232+
#[cfg(not(windows))]
233+
x86_64_dlltool: path.iter().any(|dir| dir.join("x86_64-w64-mingw32-dlltool").is_file()),
234+
}
235+
}
236+
}

src/tools/compiletest/src/header/tests.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,25 @@
1+
use std::io::Read;
12
use std::path::Path;
23

34
use crate::common::{Config, Debugger};
4-
use crate::header::{make_test_description, parse_normalization_string, EarlyProps};
5+
use crate::header::{parse_normalization_string, EarlyProps, HeadersCache};
6+
7+
fn make_test_description<R: Read>(
8+
config: &Config,
9+
name: test::TestName,
10+
path: &Path,
11+
src: R,
12+
cfg: Option<&str>,
13+
) -> test::TestDesc {
14+
let cache = HeadersCache::load(config);
15+
let mut poisoned = false;
16+
let test =
17+
crate::header::make_test_description(config, &cache, name, path, src, cfg, &mut poisoned);
18+
if poisoned {
19+
panic!("poisoned!");
20+
}
21+
test
22+
}
523

624
#[test]
725
fn test_parse_normalization_string() {

0 commit comments

Comments
 (0)