compiletest/header/
cfg.rs

1use std::collections::HashSet;
2
3use crate::common::{CompareMode, Config, Debugger};
4use crate::header::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
42/// Parses a name-value directive which contains config-specific information, e.g., `ignore-x86`
43/// or `only-windows`.
44fn 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    // Some of the matchers might be "" depending on what the target information is. To avoid
61    // problems we outright reject empty directives.
62    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            // This is not inlined to avoid problems with macro repetitions.
77            let format_message = || format!($($message)*);
78
79            if outcome != MatchOutcome::Invalid {
80                // Ignore all other matches if we already found one
81            } 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: &config.target,
105        allowed_names: &target_cfgs.all_targets,
106        message: "when the target is {name}"
107    }
108    condition! {
109        name: &[
110            Some(&*target_cfg.os),
111            // If something is ignored for emscripten, it likely also needs to be
112            // ignored for wasm32-unknown-unknown.
113            (config.target == "wasm32-unknown-unknown").then_some("emscripten"),
114        ],
115        allowed_names: &target_cfgs.all_oses,
116        message: "when the operating system is {name}"
117    }
118    condition! {
119        name: &target_cfg.env,
120        allowed_names: &target_cfgs.all_envs,
121        message: "when the target environment is {name}"
122    }
123    condition! {
124        name: &target_cfg.os_and_env(),
125        allowed_names: &target_cfgs.all_oses_and_envs,
126        message: "when the operating system and target environment are {name}"
127    }
128    condition! {
129        name: &target_cfg.abi,
130        allowed_names: &target_cfgs.all_abis,
131        message: "when the ABI is {name}"
132    }
133    condition! {
134        name: &target_cfg.arch,
135        allowed_names: ContainsEither { a: &target_cfgs.all_archs, b: &EXTRA_ARCHS },
136        message: "when the architecture is {name}"
137    }
138    condition! {
139        name: format!("{}bit", target_cfg.pointer_width),
140        allowed_names: &target_cfgs.all_pointer_widths,
141        message: "when the pointer width is {name}"
142    }
143    condition! {
144        name: &*target_cfg.families,
145        allowed_names: &target_cfgs.all_families,
146        message: "when the target family is {name}"
147    }
148
149    // `wasm32-bare` is an alias to refer to just wasm32-unknown-unknown
150    // (in contrast to `wasm32` which also matches non-bare targets)
151    condition! {
152        name: "wasm32-bare",
153        condition: config.target == "wasm32-unknown-unknown",
154        message: "when the target is WASM"
155    }
156
157    condition! {
158        name: "thumb",
159        condition: config.target.starts_with("thumb"),
160        message: "when the architecture is part of the Thumb family"
161    }
162
163    condition! {
164        name: "apple",
165        condition: config.target.contains("apple"),
166        message: "when the target vendor is Apple"
167    }
168
169    condition! {
170        name: "enzyme",
171        condition: config.has_enzyme,
172        message: "when rustc is built with LLVM Enzyme"
173    }
174
175    // Technically the locally built compiler uses the "dev" channel rather than the "nightly"
176    // channel, even though most people don't know or won't care about it. To avoid confusion, we
177    // treat the "dev" channel as the "nightly" channel when processing the directive.
178    condition! {
179        name: if config.channel == "dev" { "nightly" } else { &config.channel },
180        allowed_names: &["stable", "beta", "nightly"],
181        message: "when the release channel is {name}",
182    }
183
184    condition! {
185        name: "cross-compile",
186        condition: config.target != config.host,
187        message: "when cross-compiling"
188    }
189    condition! {
190        name: "endian-big",
191        condition: config.is_big_endian(),
192        message: "on big-endian targets",
193    }
194    condition! {
195        name: format!("stage{}", config.stage).as_str(),
196        allowed_names: &["stage0", "stage1", "stage2"],
197        message: "when the bootstrapping stage is {name}",
198    }
199    condition! {
200        name: "remote",
201        condition: config.remote_test_client.is_some(),
202        message: "when running tests remotely",
203    }
204    condition! {
205        name: "rustc-debug-assertions",
206        condition: config.with_rustc_debug_assertions,
207        message: "when rustc is built with debug assertions",
208    }
209    condition! {
210        name: "std-debug-assertions",
211        condition: config.with_std_debug_assertions,
212        message: "when std is built with debug assertions",
213    }
214    condition! {
215        name: config.debugger.as_ref().map(|d| d.to_str()),
216        allowed_names: &Debugger::STR_VARIANTS,
217        message: "when the debugger is {name}",
218    }
219    condition! {
220        name: config.compare_mode
221            .as_ref()
222            .map(|d| format!("compare-mode-{}", d.to_str())),
223        allowed_names: ContainsPrefixed {
224            prefix: "compare-mode-",
225            inner: CompareMode::STR_VARIANTS,
226        },
227        message: "when comparing with {name}",
228    }
229    // Coverage tests run the same test file in multiple modes.
230    // If a particular test should not be run in one of the modes, ignore it
231    // with "ignore-coverage-map" or "ignore-coverage-run".
232    condition! {
233        name: config.mode.to_str(),
234        allowed_names: ["coverage-map", "coverage-run"],
235        message: "when the test mode is {name}",
236    }
237
238    condition! {
239        name: "dist",
240        condition: std::env::var("COMPILETEST_ENABLE_DIST_TESTS") == Ok("1".to_string()),
241        message: "when performing tests on dist toolchain"
242    }
243
244    if prefix == "ignore" && outcome == MatchOutcome::Invalid {
245        // Don't error out for ignore-tidy-* diretives, as those are not handled by compiletest.
246        if name.starts_with("tidy-") {
247            outcome = MatchOutcome::External;
248        }
249
250        // Don't error out for ignore-pass, as that is handled elsewhere.
251        if name == "pass" {
252            outcome = MatchOutcome::External;
253        }
254
255        // Don't error out for ignore-llvm-version, that has a custom syntax and is handled
256        // elsewhere.
257        if name == "llvm-version" {
258            outcome = MatchOutcome::External;
259        }
260
261        // Don't error out for ignore-llvm-version, that has a custom syntax and is handled
262        // elsewhere.
263        if name == "gdb-version" {
264            outcome = MatchOutcome::External;
265        }
266    }
267
268    ParsedNameDirective {
269        name: Some(name),
270        comment: comment.map(|c| c.trim().trim_start_matches('-').trim()),
271        outcome,
272        pretty_reason: message,
273    }
274}
275
276/// The result of parse_cfg_name_directive.
277#[derive(Clone, PartialEq, Debug)]
278pub(super) struct ParsedNameDirective<'a> {
279    pub(super) name: Option<&'a str>,
280    pub(super) pretty_reason: Option<String>,
281    pub(super) comment: Option<&'a str>,
282    pub(super) outcome: MatchOutcome,
283}
284
285impl ParsedNameDirective<'_> {
286    fn not_a_directive() -> Self {
287        Self {
288            name: None,
289            pretty_reason: None,
290            comment: None,
291            outcome: MatchOutcome::NotADirective,
292        }
293    }
294}
295
296#[derive(Clone, Copy, PartialEq, Debug)]
297pub(super) enum MatchOutcome {
298    /// No match.
299    NoMatch,
300    /// Match.
301    Match,
302    /// The directive was invalid.
303    Invalid,
304    /// The directive is handled by other parts of our tooling.
305    External,
306    /// The line is not actually a directive.
307    NotADirective,
308}
309
310trait CustomContains {
311    fn custom_contains(&self, item: &str) -> bool;
312}
313
314impl CustomContains for HashSet<String> {
315    fn custom_contains(&self, item: &str) -> bool {
316        self.contains(item)
317    }
318}
319
320impl CustomContains for &[&str] {
321    fn custom_contains(&self, item: &str) -> bool {
322        self.contains(&item)
323    }
324}
325
326impl<const N: usize> CustomContains for [&str; N] {
327    fn custom_contains(&self, item: &str) -> bool {
328        self.contains(&item)
329    }
330}
331
332struct ContainsPrefixed<T: CustomContains> {
333    prefix: &'static str,
334    inner: T,
335}
336
337impl<T: CustomContains> CustomContains for ContainsPrefixed<T> {
338    fn custom_contains(&self, item: &str) -> bool {
339        match item.strip_prefix(self.prefix) {
340            Some(stripped) => self.inner.custom_contains(stripped),
341            None => false,
342        }
343    }
344}
345
346struct ContainsEither<'a, A: CustomContains, B: CustomContains> {
347    a: &'a A,
348    b: &'a B,
349}
350
351impl<A: CustomContains, B: CustomContains> CustomContains for ContainsEither<'_, A, B> {
352    fn custom_contains(&self, item: &str) -> bool {
353        self.a.custom_contains(item) || self.b.custom_contains(item)
354    }
355}
356
357trait CustomMatches {
358    fn custom_matches(&self, name: &str) -> bool;
359}
360
361impl CustomMatches for &str {
362    fn custom_matches(&self, name: &str) -> bool {
363        name == *self
364    }
365}
366
367impl CustomMatches for String {
368    fn custom_matches(&self, name: &str) -> bool {
369        name == self
370    }
371}
372
373impl<T: CustomMatches> CustomMatches for &[T] {
374    fn custom_matches(&self, name: &str) -> bool {
375        self.iter().any(|m| m.custom_matches(name))
376    }
377}
378
379impl<const N: usize, T: CustomMatches> CustomMatches for [T; N] {
380    fn custom_matches(&self, name: &str) -> bool {
381        self.iter().any(|m| m.custom_matches(name))
382    }
383}
384
385impl<T: CustomMatches> CustomMatches for Option<T> {
386    fn custom_matches(&self, name: &str) -> bool {
387        match self {
388            Some(inner) => inner.custom_matches(name),
389            None => false,
390        }
391    }
392}