Skip to main content

compiletest/directives/
cfg.rs

1use std::collections::{HashMap, HashSet};
2use std::sync::{Arc, LazyLock};
3
4use crate::common::{CompareMode, Config, Debugger};
5use crate::directives::{DirectiveLine, IgnoreDecision};
6
7const EXTRA_ARCHS: &[&str] = &["spirv"];
8
9const EXTERNAL_IGNORES_LIST: &[&str] = &[
10    // tidy-alphabetical-start
11    "ignore-backends",
12    "ignore-gdb-version",
13    "ignore-llvm-version",
14    "ignore-parallel-frontend",
15    "ignore-pass",
16    // tidy-alphabetical-end
17];
18
19/// Directive names that begin with `ignore-`, but are disregarded by this
20/// module because they are handled elsewhere.
21pub(crate) static EXTERNAL_IGNORES_SET: LazyLock<HashSet<&str>> =
22    LazyLock::new(|| EXTERNAL_IGNORES_LIST.iter().copied().collect());
23
24pub(super) fn handle_ignore(
25    conditions: &PreparedConditions,
26    line: &DirectiveLine<'_>,
27) -> IgnoreDecision {
28    let parsed = parse_cfg_name_directive(conditions, line, "ignore-");
29    let line = line.display();
30
31    match parsed.outcome {
32        MatchOutcome::NoMatch => IgnoreDecision::Continue,
33        MatchOutcome::Match => IgnoreDecision::Ignore {
34            reason: match parsed.comment {
35                Some(comment) => format!("ignored {} ({comment})", parsed.pretty_reason.unwrap()),
36                None => format!("ignored {}", parsed.pretty_reason.unwrap()),
37            },
38        },
39        MatchOutcome::Invalid => IgnoreDecision::Error { message: format!("invalid line: {line}") },
40        MatchOutcome::NotHandledHere => IgnoreDecision::Continue,
41    }
42}
43
44pub(super) fn handle_only(
45    conditions: &PreparedConditions,
46    line: &DirectiveLine<'_>,
47) -> IgnoreDecision {
48    let parsed = parse_cfg_name_directive(conditions, line, "only-");
49    let line = line.display();
50
51    match parsed.outcome {
52        MatchOutcome::Match => IgnoreDecision::Continue,
53        MatchOutcome::NoMatch => IgnoreDecision::Ignore {
54            reason: match parsed.comment {
55                Some(comment) => {
56                    format!("only executed {} ({comment})", parsed.pretty_reason.unwrap())
57                }
58                None => format!("only executed {}", parsed.pretty_reason.unwrap()),
59            },
60        },
61        MatchOutcome::Invalid => IgnoreDecision::Error { message: format!("invalid line: {line}") },
62        MatchOutcome::NotHandledHere => IgnoreDecision::Continue,
63    }
64}
65
66/// Parses a name-value directive which contains config-specific information, e.g., `ignore-x86`
67/// or `only-windows`.
68fn parse_cfg_name_directive<'a>(
69    conditions: &PreparedConditions,
70    line: &'a DirectiveLine<'a>,
71    prefix: &str,
72) -> ParsedNameDirective<'a> {
73    let Some(name) = line.name.strip_prefix(prefix) else {
74        return ParsedNameDirective::not_handled_here();
75    };
76
77    if prefix == "ignore-" && EXTERNAL_IGNORES_SET.contains(line.name) {
78        return ParsedNameDirective::not_handled_here();
79    }
80
81    // FIXME(Zalathar): This currently allows either a space or a colon, and
82    // treats any "value" after a colon as though it were a remark.
83    // We should instead forbid the colon syntax for these directives.
84    let comment = line
85        .remark_after_space()
86        .or_else(|| line.value_after_colon())
87        .map(|c| c.trim().trim_start_matches('-').trim());
88
89    if let Some(cond) = conditions.conds.get(name) {
90        ParsedNameDirective {
91            pretty_reason: Some(Arc::clone(&cond.message_when_ignored)),
92            comment,
93            outcome: if cond.value { MatchOutcome::Match } else { MatchOutcome::NoMatch },
94        }
95    } else {
96        ParsedNameDirective { pretty_reason: None, comment, outcome: MatchOutcome::Invalid }
97    }
98}
99
100/// Uses information about the current target (and all targets) to pre-compute
101/// a value (true or false) for a number of "conditions". Those conditions can
102/// then be used by `ignore-*` and `only-*` directives.
103pub(crate) fn prepare_conditions(config: &Config) -> PreparedConditions {
104    let cfgs = config.target_cfgs();
105    let current = &cfgs.current;
106
107    let mut builder = ConditionsBuilder::new();
108
109    // Some condition names overlap (e.g. "macabi" is both an env and an ABI),
110    // so the order in which conditions are added is significant.
111    // Whichever condition registers that name _first_ will take precedence.
112    // (See `ConditionsBuilder::build`.)
113
114    builder.cond("test", true, "always");
115    builder.cond("auxiliary", true, "used by another main test file");
116
117    for target in &cfgs.all_targets {
118        builder.cond(target, *target == config.target, &format!("when the target is {target}"));
119    }
120    for os in &cfgs.all_oses {
121        builder.cond(os, *os == current.os, &format!("when the operating system is {os}"));
122    }
123    for env in &cfgs.all_envs {
124        builder.cond(env, *env == current.env, &format!("when the target environment is {env}"));
125    }
126    for os_and_env in &cfgs.all_oses_and_envs {
127        builder.cond(
128            os_and_env,
129            *os_and_env == current.os_and_env(),
130            &format!("when the operating system and target environment are {os_and_env}"),
131        );
132    }
133    for abi in &cfgs.all_abis {
134        builder.cond(abi, *abi == current.abi, &format!("when the ABI is {abi}"));
135    }
136    for arch in cfgs.all_archs.iter().map(String::as_str).chain(EXTRA_ARCHS.iter().copied()) {
137        builder.cond(arch, *arch == current.arch, &format!("when the architecture is {arch}"));
138    }
139    for n_bit in &cfgs.all_pointer_widths {
140        builder.cond(
141            n_bit,
142            *n_bit == format!("{}bit", current.pointer_width),
143            &format!("when the pointer width is {n_bit}"),
144        );
145    }
146    for family in &cfgs.all_families {
147        builder.cond(
148            family,
149            current.families.contains(family),
150            &format!("when the target family is {family}"),
151        )
152    }
153
154    builder.cond(
155        "thumb",
156        config.target.starts_with("thumb"),
157        "when the architecture is part of the Thumb family",
158    );
159
160    // The "arch" of `i586-` targets is "x86", so for more specific matching
161    // we have to resort to a string-prefix check.
162    builder.cond("i586", config.matches_arch("i586"), "when the subarchitecture is i586");
163    // FIXME(Zalathar): Use proper target vendor information instead?
164    builder.cond("apple", config.target.contains("apple"), "when the target vendor is Apple");
165    // FIXME(Zalathar): Support all known binary formats, not just ELF?
166    builder.cond("elf", current.binary_format == "elf", "when the target binary format is ELF");
167    builder.cond("enzyme", config.has_enzyme, "when rustc is built with LLVM Enzyme");
168    builder.cond("offload", config.has_offload, "when rustc is built with LLVM Offload");
169
170    // Technically the locally built compiler uses the "dev" channel rather than the "nightly"
171    // channel, even though most people don't know or won't care about it. To avoid confusion, we
172    // treat the "dev" channel as the "nightly" channel when processing the directive.
173    for channel in ["stable", "beta", "nightly"] {
174        let curr_channel = match config.channel.as_str() {
175            "dev" => "nightly",
176            ch => ch,
177        };
178        builder.cond(
179            channel,
180            channel == curr_channel,
181            &format!("when the release channel is {channel}"),
182        );
183    }
184
185    builder.cond("cross-compile", config.target != config.host, "when cross-compiling");
186    builder.cond("endian-big", config.is_big_endian(), "on big-endian targets");
187
188    for stage in ["stage0", "stage1", "stage2"] {
189        builder.cond(
190            stage,
191            stage == format!("stage{}", config.stage),
192            &format!("when the bootstrapping stage is {stage}"),
193        );
194    }
195
196    builder.cond("remote", config.remote_test_client.is_some(), "when running tests remotely");
197    builder.cond(
198        "rustc-debug-assertions",
199        config.with_rustc_debug_assertions,
200        "when rustc is built with debug assertions",
201    );
202    builder.cond(
203        "std-debug-assertions",
204        config.with_std_debug_assertions,
205        "when std is built with debug assertions",
206    );
207    builder.cond(
208        "std-remap-debuginfo",
209        config.with_std_remap_debuginfo,
210        "when std is built with remapping of debuginfo",
211    );
212
213    for &debugger in Debugger::STR_VARIANTS {
214        builder.cond(
215            debugger,
216            Some(debugger) == config.debugger.as_ref().map(Debugger::to_str),
217            &format!("when the debugger is {debugger}"),
218        );
219    }
220
221    for &compare_mode in CompareMode::STR_VARIANTS {
222        builder.cond(
223            &format!("compare-mode-{compare_mode}"),
224            Some(compare_mode) == config.compare_mode.as_ref().map(CompareMode::to_str),
225            &format!("when comparing with compare-mode-{compare_mode}"),
226        );
227    }
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    for test_mode in ["coverage-map", "coverage-run"] {
233        builder.cond(
234            test_mode,
235            test_mode == config.mode.to_str(),
236            &format!("when the test mode is {test_mode}"),
237        );
238    }
239
240    for rustc_abi in &cfgs.all_rustc_abis {
241        builder.cond(
242            &format!("rustc_abi-{rustc_abi}"),
243            Some(rustc_abi) == current.rustc_abi.as_ref(),
244            &format!("when the target `rustc_abi` is rustc_abi-{rustc_abi}"),
245        );
246    }
247
248    // FIXME(Zalathar): Ideally this should be configured by a command-line
249    // flag, not an environment variable.
250    builder.cond(
251        "dist",
252        std::env::var("COMPILETEST_ENABLE_DIST_TESTS").as_deref() == Ok("1"),
253        "when performing tests on dist toolchain",
254    );
255
256    builder.build()
257}
258
259/// The result of parse_cfg_name_directive.
260#[derive(Clone, PartialEq, Debug)]
261pub(super) struct ParsedNameDirective<'a> {
262    pub(super) pretty_reason: Option<Arc<str>>,
263    pub(super) comment: Option<&'a str>,
264    pub(super) outcome: MatchOutcome,
265}
266
267impl ParsedNameDirective<'_> {
268    fn not_handled_here() -> Self {
269        Self { pretty_reason: None, comment: None, outcome: MatchOutcome::NotHandledHere }
270    }
271}
272
273#[derive(Clone, Copy, PartialEq, Debug)]
274pub(super) enum MatchOutcome {
275    /// No match.
276    NoMatch,
277    /// Match.
278    Match,
279    /// The directive was invalid.
280    Invalid,
281    /// The directive should be ignored by this module, because it is handled elsewhere.
282    NotHandledHere,
283}
284
285#[derive(Debug)]
286pub(crate) struct PreparedConditions {
287    /// Maps the "bare" name of each condition to a structure indicating
288    /// whether the condition is true or false for the target being tested.
289    conds: HashMap<Arc<str>, Cond>,
290}
291
292#[derive(Debug)]
293struct Cond {
294    /// Bare condition name without an ignore/only prefix, e.g. `aarch64` or `windows`.
295    bare_name: Arc<str>,
296
297    /// Is this condition true or false for the target being tested, based on
298    /// the config that was used to prepare these conditions?
299    ///
300    /// For example, the condition `windows` is true on Windows targets.
301    value: bool,
302
303    /// Message fragment to show when a test is ignored based on this condition
304    /// being true or false, e.g. "when the architecture is aarch64".
305    message_when_ignored: Arc<str>,
306}
307
308struct ConditionsBuilder {
309    conds: Vec<Cond>,
310}
311
312impl ConditionsBuilder {
313    fn new() -> Self {
314        Self { conds: vec![] }
315    }
316
317    fn cond(&mut self, bare_name: &str, value: bool, message_when_ignored: &str) {
318        self.conds.push(Cond {
319            bare_name: Arc::<str>::from(bare_name),
320            value,
321            message_when_ignored: Arc::<str>::from(message_when_ignored),
322        });
323    }
324
325    fn build(self) -> PreparedConditions {
326        let conds = self
327            .conds
328            .into_iter()
329            // Build the map in reverse order, so that conditions declared
330            // earlier have priority over ones declared later.
331            .rev()
332            .map(|cond| (Arc::clone(&cond.bare_name), cond))
333            .collect::<HashMap<_, _>>();
334        PreparedConditions { conds }
335    }
336}