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