1use std::collections::HashSet;
2
3use crate::common::{CompareMode, Config, Debugger};
4use crate::directives::IgnoreDecision;
5
6const EXTRA_ARCHS: &[&str] = &["spirv"];
7
8pub(super) fn handle_ignore(config: &Config, line: &str) -> IgnoreDecision {
9 let parsed = parse_cfg_name_directive(config, line, "ignore");
10 match parsed.outcome {
11 MatchOutcome::NoMatch => IgnoreDecision::Continue,
12 MatchOutcome::Match => IgnoreDecision::Ignore {
13 reason: match parsed.comment {
14 Some(comment) => format!("ignored {} ({comment})", parsed.pretty_reason.unwrap()),
15 None => format!("ignored {}", parsed.pretty_reason.unwrap()),
16 },
17 },
18 MatchOutcome::Invalid => IgnoreDecision::Error { message: format!("invalid line: {line}") },
19 MatchOutcome::External => IgnoreDecision::Continue,
20 MatchOutcome::NotADirective => IgnoreDecision::Continue,
21 }
22}
23
24pub(super) fn handle_only(config: &Config, line: &str) -> IgnoreDecision {
25 let parsed = parse_cfg_name_directive(config, line, "only");
26 match parsed.outcome {
27 MatchOutcome::Match => IgnoreDecision::Continue,
28 MatchOutcome::NoMatch => IgnoreDecision::Ignore {
29 reason: match parsed.comment {
30 Some(comment) => {
31 format!("only executed {} ({comment})", parsed.pretty_reason.unwrap())
32 }
33 None => format!("only executed {}", parsed.pretty_reason.unwrap()),
34 },
35 },
36 MatchOutcome::Invalid => IgnoreDecision::Error { message: format!("invalid line: {line}") },
37 MatchOutcome::External => IgnoreDecision::Continue,
38 MatchOutcome::NotADirective => IgnoreDecision::Continue,
39 }
40}
41
42fn parse_cfg_name_directive<'a>(
45 config: &Config,
46 line: &'a str,
47 prefix: &str,
48) -> ParsedNameDirective<'a> {
49 if !line.as_bytes().starts_with(prefix.as_bytes()) {
50 return ParsedNameDirective::not_a_directive();
51 }
52 if line.as_bytes().get(prefix.len()) != Some(&b'-') {
53 return ParsedNameDirective::not_a_directive();
54 }
55 let line = &line[prefix.len() + 1..];
56
57 let (name, comment) =
58 line.split_once(&[':', ' ']).map(|(l, c)| (l, Some(c))).unwrap_or((line, None));
59
60 if name.is_empty() {
63 return ParsedNameDirective::not_a_directive();
64 }
65
66 let mut outcome = MatchOutcome::Invalid;
67 let mut message = None;
68
69 macro_rules! condition {
70 (
71 name: $name:expr,
72 $(allowed_names: $allowed_names:expr,)?
73 $(condition: $condition:expr,)?
74 message: $($message:tt)*
75 ) => {{
76 let format_message = || format!($($message)*);
78
79 if outcome != MatchOutcome::Invalid {
80 } else if $name.custom_matches(name) {
82 message = Some(format_message());
83 if true $(&& $condition)? {
84 outcome = MatchOutcome::Match;
85 } else {
86 outcome = MatchOutcome::NoMatch;
87 }
88 }
89 $(else if $allowed_names.custom_contains(name) {
90 message = Some(format_message());
91 outcome = MatchOutcome::NoMatch;
92 })?
93 }};
94 }
95
96 let target_cfgs = config.target_cfgs();
97 let target_cfg = config.target_cfg();
98
99 condition! {
100 name: "test",
101 message: "always"
102 }
103 condition! {
104 name: "auxiliary",
105 message: "used by another main test file"
106 }
107 condition! {
108 name: &config.target,
109 allowed_names: &target_cfgs.all_targets,
110 message: "when the target is {name}"
111 }
112 condition! {
113 name: &[
114 Some(&*target_cfg.os),
115 (config.target == "wasm32-unknown-unknown").then_some("emscripten"),
118 ],
119 allowed_names: &target_cfgs.all_oses,
120 message: "when the operating system is {name}"
121 }
122 condition! {
123 name: &target_cfg.env,
124 allowed_names: &target_cfgs.all_envs,
125 message: "when the target environment is {name}"
126 }
127 condition! {
128 name: &target_cfg.os_and_env(),
129 allowed_names: &target_cfgs.all_oses_and_envs,
130 message: "when the operating system and target environment are {name}"
131 }
132 condition! {
133 name: &target_cfg.abi,
134 allowed_names: &target_cfgs.all_abis,
135 message: "when the ABI is {name}"
136 }
137 condition! {
138 name: &target_cfg.arch,
139 allowed_names: ContainsEither { a: &target_cfgs.all_archs, b: &EXTRA_ARCHS },
140 message: "when the architecture is {name}"
141 }
142 condition! {
143 name: format!("{}bit", target_cfg.pointer_width),
144 allowed_names: &target_cfgs.all_pointer_widths,
145 message: "when the pointer width is {name}"
146 }
147 condition! {
148 name: &*target_cfg.families,
149 allowed_names: &target_cfgs.all_families,
150 message: "when the target family is {name}"
151 }
152
153 condition! {
156 name: "wasm32-bare",
157 condition: config.target == "wasm32-unknown-unknown",
158 message: "when the target is WASM"
159 }
160
161 condition! {
162 name: "thumb",
163 condition: config.target.starts_with("thumb"),
164 message: "when the architecture is part of the Thumb family"
165 }
166
167 condition! {
168 name: "apple",
169 condition: config.target.contains("apple"),
170 message: "when the target vendor is Apple"
171 }
172
173 condition! {
174 name: "elf",
175 condition: !config.target.contains("windows")
176 && !config.target.contains("wasm")
177 && !config.target.contains("apple")
178 && !config.target.contains("aix")
179 && !config.target.contains("uefi"),
180 message: "when the target binary format is ELF"
181 }
182
183 condition! {
184 name: "enzyme",
185 condition: config.has_enzyme,
186 message: "when rustc is built with LLVM Enzyme"
187 }
188
189 condition! {
193 name: if config.channel == "dev" { "nightly" } else { &config.channel },
194 allowed_names: &["stable", "beta", "nightly"],
195 message: "when the release channel is {name}",
196 }
197
198 condition! {
199 name: "cross-compile",
200 condition: config.target != config.host,
201 message: "when cross-compiling"
202 }
203 condition! {
204 name: "endian-big",
205 condition: config.is_big_endian(),
206 message: "on big-endian targets",
207 }
208 condition! {
209 name: format!("stage{}", config.stage).as_str(),
210 allowed_names: &["stage0", "stage1", "stage2"],
211 message: "when the bootstrapping stage is {name}",
212 }
213 condition! {
214 name: "remote",
215 condition: config.remote_test_client.is_some(),
216 message: "when running tests remotely",
217 }
218 condition! {
219 name: "rustc-debug-assertions",
220 condition: config.with_rustc_debug_assertions,
221 message: "when rustc is built with debug assertions",
222 }
223 condition! {
224 name: "std-debug-assertions",
225 condition: config.with_std_debug_assertions,
226 message: "when std is built with debug assertions",
227 }
228 condition! {
229 name: config.debugger.as_ref().map(|d| d.to_str()),
230 allowed_names: &Debugger::STR_VARIANTS,
231 message: "when the debugger is {name}",
232 }
233 condition! {
234 name: config.compare_mode
235 .as_ref()
236 .map(|d| format!("compare-mode-{}", d.to_str())),
237 allowed_names: ContainsPrefixed {
238 prefix: "compare-mode-",
239 inner: CompareMode::STR_VARIANTS,
240 },
241 message: "when comparing with {name}",
242 }
243 condition! {
247 name: config.mode.to_str(),
248 allowed_names: ["coverage-map", "coverage-run"],
249 message: "when the test mode is {name}",
250 }
251 condition! {
252 name: target_cfg.rustc_abi.as_ref().map(|abi| format!("rustc_abi-{abi}")).unwrap_or_default(),
253 allowed_names: ContainsPrefixed {
254 prefix: "rustc_abi-",
255 inner: target_cfgs.all_rustc_abis.clone(),
256 },
257 message: "when the target `rustc_abi` is {name}",
258 }
259
260 condition! {
261 name: "dist",
262 condition: std::env::var("COMPILETEST_ENABLE_DIST_TESTS") == Ok("1".to_string()),
263 message: "when performing tests on dist toolchain"
264 }
265
266 if prefix == "ignore" && outcome == MatchOutcome::Invalid {
267 if name.starts_with("tidy-") {
269 outcome = MatchOutcome::External;
270 }
271
272 if name == "pass" {
274 outcome = MatchOutcome::External;
275 }
276
277 if name == "llvm-version" {
280 outcome = MatchOutcome::External;
281 }
282
283 if name == "gdb-version" {
286 outcome = MatchOutcome::External;
287 }
288
289 if name == "backends" {
291 outcome = MatchOutcome::External;
292 }
293 }
294
295 ParsedNameDirective {
296 name: Some(name),
297 comment: comment.map(|c| c.trim().trim_start_matches('-').trim()),
298 outcome,
299 pretty_reason: message,
300 }
301}
302
303#[derive(Clone, PartialEq, Debug)]
305pub(super) struct ParsedNameDirective<'a> {
306 pub(super) name: Option<&'a str>,
307 pub(super) pretty_reason: Option<String>,
308 pub(super) comment: Option<&'a str>,
309 pub(super) outcome: MatchOutcome,
310}
311
312impl ParsedNameDirective<'_> {
313 fn not_a_directive() -> Self {
314 Self {
315 name: None,
316 pretty_reason: None,
317 comment: None,
318 outcome: MatchOutcome::NotADirective,
319 }
320 }
321}
322
323#[derive(Clone, Copy, PartialEq, Debug)]
324pub(super) enum MatchOutcome {
325 NoMatch,
327 Match,
329 Invalid,
331 External,
333 NotADirective,
335}
336
337trait CustomContains {
338 fn custom_contains(&self, item: &str) -> bool;
339}
340
341impl CustomContains for HashSet<String> {
342 fn custom_contains(&self, item: &str) -> bool {
343 self.contains(item)
344 }
345}
346
347impl CustomContains for &[&str] {
348 fn custom_contains(&self, item: &str) -> bool {
349 self.contains(&item)
350 }
351}
352
353impl<const N: usize> CustomContains for [&str; N] {
354 fn custom_contains(&self, item: &str) -> bool {
355 self.contains(&item)
356 }
357}
358
359struct ContainsPrefixed<T: CustomContains> {
360 prefix: &'static str,
361 inner: T,
362}
363
364impl<T: CustomContains> CustomContains for ContainsPrefixed<T> {
365 fn custom_contains(&self, item: &str) -> bool {
366 match item.strip_prefix(self.prefix) {
367 Some(stripped) => self.inner.custom_contains(stripped),
368 None => false,
369 }
370 }
371}
372
373struct ContainsEither<'a, A: CustomContains, B: CustomContains> {
374 a: &'a A,
375 b: &'a B,
376}
377
378impl<A: CustomContains, B: CustomContains> CustomContains for ContainsEither<'_, A, B> {
379 fn custom_contains(&self, item: &str) -> bool {
380 self.a.custom_contains(item) || self.b.custom_contains(item)
381 }
382}
383
384trait CustomMatches {
385 fn custom_matches(&self, name: &str) -> bool;
386}
387
388impl CustomMatches for &str {
389 fn custom_matches(&self, name: &str) -> bool {
390 name == *self
391 }
392}
393
394impl CustomMatches for String {
395 fn custom_matches(&self, name: &str) -> bool {
396 name == self
397 }
398}
399
400impl<T: CustomMatches> CustomMatches for &[T] {
401 fn custom_matches(&self, name: &str) -> bool {
402 self.iter().any(|m| m.custom_matches(name))
403 }
404}
405
406impl<const N: usize, T: CustomMatches> CustomMatches for [T; N] {
407 fn custom_matches(&self, name: &str) -> bool {
408 self.iter().any(|m| m.custom_matches(name))
409 }
410}
411
412impl<T: CustomMatches> CustomMatches for Option<T> {
413 fn custom_matches(&self, name: &str) -> bool {
414 match self {
415 Some(inner) => inner.custom_matches(name),
416 None => false,
417 }
418 }
419}