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-parallel-frontend",
15 "ignore-pass",
16 ];
18
19pub(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
66fn 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 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
100pub(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 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 builder.cond("i586", config.matches_arch("i586"), "when the subarchitecture is i586");
163 builder.cond("apple", config.target.contains("apple"), "when the target vendor is Apple");
165 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 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 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 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#[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 NoMatch,
277 Match,
279 Invalid,
281 NotHandledHere,
283}
284
285#[derive(Debug)]
286pub(crate) struct PreparedConditions {
287 conds: HashMap<Arc<str>, Cond>,
290}
291
292#[derive(Debug)]
293struct Cond {
294 bare_name: Arc<str>,
296
297 value: bool,
302
303 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 .rev()
332 .map(|cond| (Arc::clone(&cond.bare_name), cond))
333 .collect::<HashMap<_, _>>();
334 PreparedConditions { conds }
335 }
336}