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