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 "ignore-backends",
12 "ignore-gdb-version",
13 "ignore-llvm-version",
14 "ignore-pass",
15 ];
17
18pub(crate) static EXTERNAL_IGNORES_SET: LazyLock<HashSet<&str>> =
21 LazyLock::new(|| EXTERNAL_IGNORES_LIST.iter().copied().collect());
22
23pub(super) fn handle_ignore(
24 conditions: &PreparedConditions,
25 line: &DirectiveLine<'_>,
26) -> IgnoreDecision {
27 let parsed = parse_cfg_name_directive(conditions, line, "ignore-");
28 let line = line.display();
29
30 match parsed.outcome {
31 MatchOutcome::NoMatch => IgnoreDecision::Continue,
32 MatchOutcome::Match => IgnoreDecision::Ignore {
33 reason: match parsed.comment {
34 Some(comment) => format!("ignored {} ({comment})", parsed.pretty_reason.unwrap()),
35 None => format!("ignored {}", parsed.pretty_reason.unwrap()),
36 },
37 },
38 MatchOutcome::Invalid => IgnoreDecision::Error { message: format!("invalid line: {line}") },
39 MatchOutcome::NotHandledHere => IgnoreDecision::Continue,
40 }
41}
42
43pub(super) fn handle_only(
44 conditions: &PreparedConditions,
45 line: &DirectiveLine<'_>,
46) -> IgnoreDecision {
47 let parsed = parse_cfg_name_directive(conditions, line, "only-");
48 let line = line.display();
49
50 match parsed.outcome {
51 MatchOutcome::Match => IgnoreDecision::Continue,
52 MatchOutcome::NoMatch => IgnoreDecision::Ignore {
53 reason: match parsed.comment {
54 Some(comment) => {
55 format!("only executed {} ({comment})", parsed.pretty_reason.unwrap())
56 }
57 None => format!("only executed {}", parsed.pretty_reason.unwrap()),
58 },
59 },
60 MatchOutcome::Invalid => IgnoreDecision::Error { message: format!("invalid line: {line}") },
61 MatchOutcome::NotHandledHere => IgnoreDecision::Continue,
62 }
63}
64
65fn parse_cfg_name_directive<'a>(
68 conditions: &PreparedConditions,
69 line: &'a DirectiveLine<'a>,
70 prefix: &str,
71) -> ParsedNameDirective<'a> {
72 let Some(name) = line.name.strip_prefix(prefix) else {
73 return ParsedNameDirective::not_handled_here();
74 };
75
76 if prefix == "ignore-" && EXTERNAL_IGNORES_SET.contains(line.name) {
77 return ParsedNameDirective::not_handled_here();
78 }
79
80 let comment = line
84 .remark_after_space()
85 .or_else(|| line.value_after_colon())
86 .map(|c| c.trim().trim_start_matches('-').trim());
87
88 if let Some(cond) = conditions.conds.get(name) {
89 ParsedNameDirective {
90 pretty_reason: Some(Arc::clone(&cond.message_when_ignored)),
91 comment,
92 outcome: if cond.value { MatchOutcome::Match } else { MatchOutcome::NoMatch },
93 }
94 } else {
95 ParsedNameDirective { pretty_reason: None, comment, outcome: MatchOutcome::Invalid }
96 }
97}
98
99pub(crate) fn prepare_conditions(config: &Config) -> PreparedConditions {
103 let cfgs = config.target_cfgs();
104 let current = &cfgs.current;
105
106 let mut builder = ConditionsBuilder::new();
107
108 builder.cond("test", true, "always");
114 builder.cond("auxiliary", true, "used by another main test file");
115
116 for target in &cfgs.all_targets {
117 builder.cond(target, *target == config.target, &format!("when the target is {target}"));
118 }
119 for os in &cfgs.all_oses {
120 builder.cond(os, *os == current.os, &format!("when the operating system is {os}"));
121 }
122 for env in &cfgs.all_envs {
123 builder.cond(env, *env == current.env, &format!("when the target environment is {env}"));
124 }
125 for os_and_env in &cfgs.all_oses_and_envs {
126 builder.cond(
127 os_and_env,
128 *os_and_env == current.os_and_env(),
129 &format!("when the operating system and target environment are {os_and_env}"),
130 );
131 }
132 for abi in &cfgs.all_abis {
133 builder.cond(abi, *abi == current.abi, &format!("when the ABI is {abi}"));
134 }
135 for arch in cfgs.all_archs.iter().map(String::as_str).chain(EXTRA_ARCHS.iter().copied()) {
136 builder.cond(arch, *arch == current.arch, &format!("when the architecture is {arch}"));
137 }
138 for n_bit in &cfgs.all_pointer_widths {
139 builder.cond(
140 n_bit,
141 *n_bit == format!("{}bit", current.pointer_width),
142 &format!("when the pointer width is {n_bit}"),
143 );
144 }
145 for family in &cfgs.all_families {
146 builder.cond(
147 family,
148 current.families.contains(family),
149 &format!("when the target family is {family}"),
150 )
151 }
152
153 builder.cond(
154 "thumb",
155 config.target.starts_with("thumb"),
156 "when the architecture is part of the Thumb family",
157 );
158
159 builder.cond("i586", config.matches_arch("i586"), "when the subarchitecture is i586");
162 builder.cond("apple", config.target.contains("apple"), "when the target vendor is Apple");
164 builder.cond("elf", current.binary_format == "elf", "when the target binary format is ELF");
166 builder.cond("enzyme", config.has_enzyme, "when rustc is built with LLVM Enzyme");
167
168 for channel in ["stable", "beta", "nightly"] {
172 let curr_channel = match config.channel.as_str() {
173 "dev" => "nightly",
174 ch => ch,
175 };
176 builder.cond(
177 channel,
178 channel == curr_channel,
179 &format!("when the release channel is {channel}"),
180 );
181 }
182
183 builder.cond("cross-compile", config.target != config.host, "when cross-compiling");
184 builder.cond("endian-big", config.is_big_endian(), "on big-endian targets");
185
186 for stage in ["stage0", "stage1", "stage2"] {
187 builder.cond(
188 stage,
189 stage == format!("stage{}", config.stage),
190 &format!("when the bootstrapping stage is {stage}"),
191 );
192 }
193
194 builder.cond("remote", config.remote_test_client.is_some(), "when running tests remotely");
195 builder.cond(
196 "rustc-debug-assertions",
197 config.with_rustc_debug_assertions,
198 "when rustc is built with debug assertions",
199 );
200 builder.cond(
201 "std-debug-assertions",
202 config.with_std_debug_assertions,
203 "when std is built with debug assertions",
204 );
205
206 for &debugger in Debugger::STR_VARIANTS {
207 builder.cond(
208 debugger,
209 Some(debugger) == config.debugger.as_ref().map(Debugger::to_str),
210 &format!("when the debugger is {debugger}"),
211 );
212 }
213
214 for &compare_mode in CompareMode::STR_VARIANTS {
215 builder.cond(
216 &format!("compare-mode-{compare_mode}"),
217 Some(compare_mode) == config.compare_mode.as_ref().map(CompareMode::to_str),
218 &format!("when comparing with compare-mode-{compare_mode}"),
219 );
220 }
221
222 for test_mode in ["coverage-map", "coverage-run"] {
226 builder.cond(
227 test_mode,
228 test_mode == config.mode.to_str(),
229 &format!("when the test mode is {test_mode}"),
230 );
231 }
232
233 for rustc_abi in &cfgs.all_rustc_abis {
234 builder.cond(
235 &format!("rustc_abi-{rustc_abi}"),
236 Some(rustc_abi) == current.rustc_abi.as_ref(),
237 &format!("when the target `rustc_abi` is rustc_abi-{rustc_abi}"),
238 );
239 }
240
241 builder.cond(
244 "dist",
245 std::env::var("COMPILETEST_ENABLE_DIST_TESTS").as_deref() == Ok("1"),
246 "when performing tests on dist toolchain",
247 );
248
249 builder.build()
250}
251
252#[derive(Clone, PartialEq, Debug)]
254pub(super) struct ParsedNameDirective<'a> {
255 pub(super) pretty_reason: Option<Arc<str>>,
256 pub(super) comment: Option<&'a str>,
257 pub(super) outcome: MatchOutcome,
258}
259
260impl ParsedNameDirective<'_> {
261 fn not_handled_here() -> Self {
262 Self { pretty_reason: None, comment: None, outcome: MatchOutcome::NotHandledHere }
263 }
264}
265
266#[derive(Clone, Copy, PartialEq, Debug)]
267pub(super) enum MatchOutcome {
268 NoMatch,
270 Match,
272 Invalid,
274 NotHandledHere,
276}
277
278#[derive(Debug)]
279pub(crate) struct PreparedConditions {
280 conds: HashMap<Arc<str>, Cond>,
283}
284
285#[derive(Debug)]
286struct Cond {
287 bare_name: Arc<str>,
289
290 value: bool,
295
296 message_when_ignored: Arc<str>,
299}
300
301struct ConditionsBuilder {
302 conds: Vec<Cond>,
303}
304
305impl ConditionsBuilder {
306 fn new() -> Self {
307 Self { conds: vec![] }
308 }
309
310 fn cond(&mut self, bare_name: &str, value: bool, message_when_ignored: &str) {
311 self.conds.push(Cond {
312 bare_name: Arc::<str>::from(bare_name),
313 value,
314 message_when_ignored: Arc::<str>::from(message_when_ignored),
315 });
316 }
317
318 fn build(self) -> PreparedConditions {
319 let conds = self
320 .conds
321 .into_iter()
322 .rev()
325 .map(|cond| (Arc::clone(&cond.bare_name), cond))
326 .collect::<HashMap<_, _>>();
327 PreparedConditions { conds }
328 }
329}