compiletest/directives/
cfg.rs

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