1use std::collections::HashSet;
2use std::env;
3use std::fs::File;
4use std::io::BufReader;
5use std::io::prelude::*;
6use std::process::Command;
7
8use camino::{Utf8Path, Utf8PathBuf};
9use semver::Version;
10use tracing::*;
11
12use crate::common::{Config, Debugger, FailMode, PassMode, TestMode};
13use crate::debuggers::{extract_cdb_version, extract_gdb_version};
14use crate::directives::auxiliary::{AuxProps, parse_and_update_aux};
15use crate::directives::needs::CachedNeedsConditions;
16use crate::errors::ErrorKind;
17use crate::executor::{CollectedTestDesc, ShouldPanic};
18use crate::help;
19use crate::util::static_regex;
20
21pub(crate) mod auxiliary;
22mod cfg;
23mod needs;
24#[cfg(test)]
25mod tests;
26
27pub struct DirectivesCache {
28 needs: CachedNeedsConditions,
29}
30
31impl DirectivesCache {
32 pub fn load(config: &Config) -> Self {
33 Self { needs: CachedNeedsConditions::load(config) }
34 }
35}
36
37#[derive(Default)]
40pub struct EarlyProps {
41 pub(crate) aux: AuxProps,
45 pub revisions: Vec<String>,
46}
47
48impl EarlyProps {
49 pub fn from_file(config: &Config, testfile: &Utf8Path) -> Self {
50 let file = File::open(testfile.as_std_path()).expect("open test file to parse earlyprops");
51 Self::from_reader(config, testfile, file)
52 }
53
54 pub fn from_reader<R: Read>(config: &Config, testfile: &Utf8Path, rdr: R) -> Self {
55 let mut props = EarlyProps::default();
56 let mut poisoned = false;
57 iter_directives(
58 config.mode,
59 &mut poisoned,
60 testfile,
61 rdr,
62 &mut |DirectiveLine { raw_directive: ln, .. }| {
63 parse_and_update_aux(config, ln, &mut props.aux);
64 config.parse_and_update_revisions(testfile, ln, &mut props.revisions);
65 },
66 );
67
68 if poisoned {
69 eprintln!("errors encountered during EarlyProps parsing: {}", testfile);
70 panic!("errors encountered during EarlyProps parsing");
71 }
72
73 props
74 }
75}
76
77#[derive(Clone, Debug)]
78pub struct TestProps {
79 pub error_patterns: Vec<String>,
81 pub regex_error_patterns: Vec<String>,
83 pub compile_flags: Vec<String>,
85 pub run_flags: Vec<String>,
87 pub doc_flags: Vec<String>,
89 pub pp_exact: Option<Utf8PathBuf>,
92 pub(crate) aux: AuxProps,
94 pub rustc_env: Vec<(String, String)>,
96 pub unset_rustc_env: Vec<String>,
99 pub exec_env: Vec<(String, String)>,
101 pub unset_exec_env: Vec<String>,
104 pub build_aux_docs: bool,
106 pub unique_doc_out_dir: bool,
109 pub force_host: bool,
111 pub check_stdout: bool,
113 pub check_run_results: bool,
115 pub dont_check_compiler_stdout: bool,
117 pub dont_check_compiler_stderr: bool,
119 pub no_prefer_dynamic: bool,
125 pub pretty_mode: String,
127 pub pretty_compare_only: bool,
129 pub forbid_output: Vec<String>,
131 pub revisions: Vec<String>,
133 pub incremental_dir: Option<Utf8PathBuf>,
138 pub incremental: bool,
153 pub known_bug: bool,
159 pass_mode: Option<PassMode>,
161 ignore_pass: bool,
163 pub fail_mode: Option<FailMode>,
165 pub check_test_line_numbers_match: bool,
167 pub normalize_stdout: Vec<(String, String)>,
169 pub normalize_stderr: Vec<(String, String)>,
170 pub failure_status: Option<i32>,
171 pub dont_check_failure_status: bool,
173 pub run_rustfix: bool,
176 pub rustfix_only_machine_applicable: bool,
178 pub assembly_output: Option<String>,
179 pub should_ice: bool,
181 pub stderr_per_bitwidth: bool,
183 pub mir_unit_test: Option<String>,
185 pub remap_src_base: bool,
188 pub llvm_cov_flags: Vec<String>,
191 pub filecheck_flags: Vec<String>,
193 pub no_auto_check_cfg: bool,
195 pub has_enzyme: bool,
197 pub add_core_stubs: bool,
200 pub dont_require_annotations: HashSet<ErrorKind>,
202}
203
204mod directives {
205 pub const ERROR_PATTERN: &'static str = "error-pattern";
206 pub const REGEX_ERROR_PATTERN: &'static str = "regex-error-pattern";
207 pub const COMPILE_FLAGS: &'static str = "compile-flags";
208 pub const RUN_FLAGS: &'static str = "run-flags";
209 pub const DOC_FLAGS: &'static str = "doc-flags";
210 pub const SHOULD_ICE: &'static str = "should-ice";
211 pub const BUILD_AUX_DOCS: &'static str = "build-aux-docs";
212 pub const UNIQUE_DOC_OUT_DIR: &'static str = "unique-doc-out-dir";
213 pub const FORCE_HOST: &'static str = "force-host";
214 pub const CHECK_STDOUT: &'static str = "check-stdout";
215 pub const CHECK_RUN_RESULTS: &'static str = "check-run-results";
216 pub const DONT_CHECK_COMPILER_STDOUT: &'static str = "dont-check-compiler-stdout";
217 pub const DONT_CHECK_COMPILER_STDERR: &'static str = "dont-check-compiler-stderr";
218 pub const DONT_REQUIRE_ANNOTATIONS: &'static str = "dont-require-annotations";
219 pub const NO_PREFER_DYNAMIC: &'static str = "no-prefer-dynamic";
220 pub const PRETTY_MODE: &'static str = "pretty-mode";
221 pub const PRETTY_COMPARE_ONLY: &'static str = "pretty-compare-only";
222 pub const AUX_BIN: &'static str = "aux-bin";
223 pub const AUX_BUILD: &'static str = "aux-build";
224 pub const AUX_CRATE: &'static str = "aux-crate";
225 pub const PROC_MACRO: &'static str = "proc-macro";
226 pub const AUX_CODEGEN_BACKEND: &'static str = "aux-codegen-backend";
227 pub const EXEC_ENV: &'static str = "exec-env";
228 pub const RUSTC_ENV: &'static str = "rustc-env";
229 pub const UNSET_EXEC_ENV: &'static str = "unset-exec-env";
230 pub const UNSET_RUSTC_ENV: &'static str = "unset-rustc-env";
231 pub const FORBID_OUTPUT: &'static str = "forbid-output";
232 pub const CHECK_TEST_LINE_NUMBERS_MATCH: &'static str = "check-test-line-numbers-match";
233 pub const IGNORE_PASS: &'static str = "ignore-pass";
234 pub const FAILURE_STATUS: &'static str = "failure-status";
235 pub const DONT_CHECK_FAILURE_STATUS: &'static str = "dont-check-failure-status";
236 pub const RUN_RUSTFIX: &'static str = "run-rustfix";
237 pub const RUSTFIX_ONLY_MACHINE_APPLICABLE: &'static str = "rustfix-only-machine-applicable";
238 pub const ASSEMBLY_OUTPUT: &'static str = "assembly-output";
239 pub const STDERR_PER_BITWIDTH: &'static str = "stderr-per-bitwidth";
240 pub const INCREMENTAL: &'static str = "incremental";
241 pub const KNOWN_BUG: &'static str = "known-bug";
242 pub const TEST_MIR_PASS: &'static str = "test-mir-pass";
243 pub const REMAP_SRC_BASE: &'static str = "remap-src-base";
244 pub const LLVM_COV_FLAGS: &'static str = "llvm-cov-flags";
245 pub const FILECHECK_FLAGS: &'static str = "filecheck-flags";
246 pub const NO_AUTO_CHECK_CFG: &'static str = "no-auto-check-cfg";
247 pub const ADD_CORE_STUBS: &'static str = "add-core-stubs";
248 pub const INCORRECT_COMPILER_FLAGS: &'static str = "compiler-flags";
250}
251
252impl TestProps {
253 pub fn new() -> Self {
254 TestProps {
255 error_patterns: vec![],
256 regex_error_patterns: vec![],
257 compile_flags: vec![],
258 run_flags: vec![],
259 doc_flags: vec![],
260 pp_exact: None,
261 aux: Default::default(),
262 revisions: vec![],
263 rustc_env: vec![
264 ("RUSTC_ICE".to_string(), "0".to_string()),
265 ("RUST_BACKTRACE".to_string(), "short".to_string()),
266 ],
267 unset_rustc_env: vec![("RUSTC_LOG_COLOR".to_string())],
268 exec_env: vec![],
269 unset_exec_env: vec![],
270 build_aux_docs: false,
271 unique_doc_out_dir: false,
272 force_host: false,
273 check_stdout: false,
274 check_run_results: false,
275 dont_check_compiler_stdout: false,
276 dont_check_compiler_stderr: false,
277 no_prefer_dynamic: false,
278 pretty_mode: "normal".to_string(),
279 pretty_compare_only: false,
280 forbid_output: vec![],
281 incremental_dir: None,
282 incremental: false,
283 known_bug: false,
284 pass_mode: None,
285 fail_mode: None,
286 ignore_pass: false,
287 check_test_line_numbers_match: false,
288 normalize_stdout: vec![],
289 normalize_stderr: vec![],
290 failure_status: None,
291 dont_check_failure_status: false,
292 run_rustfix: false,
293 rustfix_only_machine_applicable: false,
294 assembly_output: None,
295 should_ice: false,
296 stderr_per_bitwidth: false,
297 mir_unit_test: None,
298 remap_src_base: false,
299 llvm_cov_flags: vec![],
300 filecheck_flags: vec![],
301 no_auto_check_cfg: false,
302 has_enzyme: false,
303 add_core_stubs: false,
304 dont_require_annotations: Default::default(),
305 }
306 }
307
308 pub fn from_aux_file(
309 &self,
310 testfile: &Utf8Path,
311 revision: Option<&str>,
312 config: &Config,
313 ) -> Self {
314 let mut props = TestProps::new();
315
316 props.incremental_dir = self.incremental_dir.clone();
318 props.ignore_pass = true;
319 props.load_from(testfile, revision, config);
320
321 props
322 }
323
324 pub fn from_file(testfile: &Utf8Path, revision: Option<&str>, config: &Config) -> Self {
325 let mut props = TestProps::new();
326 props.load_from(testfile, revision, config);
327 props.exec_env.push(("RUSTC".to_string(), config.rustc_path.to_string()));
328
329 match (props.pass_mode, props.fail_mode) {
330 (None, None) if config.mode == TestMode::Ui => props.fail_mode = Some(FailMode::Check),
331 (Some(_), Some(_)) => panic!("cannot use a *-fail and *-pass mode together"),
332 _ => {}
333 }
334
335 props
336 }
337
338 fn load_from(&mut self, testfile: &Utf8Path, test_revision: Option<&str>, config: &Config) {
343 let mut has_edition = false;
344 if !testfile.is_dir() {
345 let file = File::open(testfile.as_std_path()).unwrap();
346
347 let mut poisoned = false;
348
349 iter_directives(
350 config.mode,
351 &mut poisoned,
352 testfile,
353 file,
354 &mut |directive @ DirectiveLine { raw_directive: ln, .. }| {
355 if !directive.applies_to_test_revision(test_revision) {
356 return;
357 }
358
359 use directives::*;
360
361 config.push_name_value_directive(
362 ln,
363 ERROR_PATTERN,
364 &mut self.error_patterns,
365 |r| r,
366 );
367 config.push_name_value_directive(
368 ln,
369 REGEX_ERROR_PATTERN,
370 &mut self.regex_error_patterns,
371 |r| r,
372 );
373
374 config.push_name_value_directive(ln, DOC_FLAGS, &mut self.doc_flags, |r| r);
375
376 fn split_flags(flags: &str) -> Vec<String> {
377 flags
380 .split('\'')
381 .enumerate()
382 .flat_map(|(i, f)| {
383 if i % 2 == 1 { vec![f] } else { f.split_whitespace().collect() }
384 })
385 .map(move |s| s.to_owned())
386 .collect::<Vec<_>>()
387 }
388
389 if let Some(flags) = config.parse_name_value_directive(ln, COMPILE_FLAGS) {
390 let flags = split_flags(&flags);
391 for flag in &flags {
392 if flag == "--edition" || flag.starts_with("--edition=") {
393 panic!("you must use `//@ edition` to configure the edition");
394 }
395 }
396 self.compile_flags.extend(flags);
397 }
398 if config.parse_name_value_directive(ln, INCORRECT_COMPILER_FLAGS).is_some() {
399 panic!("`compiler-flags` directive should be spelled `compile-flags`");
400 }
401
402 if let Some(edition) = config.parse_edition(ln) {
403 self.compile_flags.insert(0, format!("--edition={}", edition.trim()));
406 has_edition = true;
407 }
408
409 config.parse_and_update_revisions(testfile, ln, &mut self.revisions);
410
411 if let Some(flags) = config.parse_name_value_directive(ln, RUN_FLAGS) {
412 self.run_flags.extend(split_flags(&flags));
413 }
414
415 if self.pp_exact.is_none() {
416 self.pp_exact = config.parse_pp_exact(ln, testfile);
417 }
418
419 config.set_name_directive(ln, SHOULD_ICE, &mut self.should_ice);
420 config.set_name_directive(ln, BUILD_AUX_DOCS, &mut self.build_aux_docs);
421 config.set_name_directive(ln, UNIQUE_DOC_OUT_DIR, &mut self.unique_doc_out_dir);
422
423 config.set_name_directive(ln, FORCE_HOST, &mut self.force_host);
424 config.set_name_directive(ln, CHECK_STDOUT, &mut self.check_stdout);
425 config.set_name_directive(ln, CHECK_RUN_RESULTS, &mut self.check_run_results);
426 config.set_name_directive(
427 ln,
428 DONT_CHECK_COMPILER_STDOUT,
429 &mut self.dont_check_compiler_stdout,
430 );
431 config.set_name_directive(
432 ln,
433 DONT_CHECK_COMPILER_STDERR,
434 &mut self.dont_check_compiler_stderr,
435 );
436 config.set_name_directive(ln, NO_PREFER_DYNAMIC, &mut self.no_prefer_dynamic);
437
438 if let Some(m) = config.parse_name_value_directive(ln, PRETTY_MODE) {
439 self.pretty_mode = m;
440 }
441
442 config.set_name_directive(
443 ln,
444 PRETTY_COMPARE_ONLY,
445 &mut self.pretty_compare_only,
446 );
447
448 parse_and_update_aux(config, ln, &mut self.aux);
450
451 config.push_name_value_directive(
452 ln,
453 EXEC_ENV,
454 &mut self.exec_env,
455 Config::parse_env,
456 );
457 config.push_name_value_directive(
458 ln,
459 UNSET_EXEC_ENV,
460 &mut self.unset_exec_env,
461 |r| r.trim().to_owned(),
462 );
463 config.push_name_value_directive(
464 ln,
465 RUSTC_ENV,
466 &mut self.rustc_env,
467 Config::parse_env,
468 );
469 config.push_name_value_directive(
470 ln,
471 UNSET_RUSTC_ENV,
472 &mut self.unset_rustc_env,
473 |r| r.trim().to_owned(),
474 );
475 config.push_name_value_directive(
476 ln,
477 FORBID_OUTPUT,
478 &mut self.forbid_output,
479 |r| r,
480 );
481 config.set_name_directive(
482 ln,
483 CHECK_TEST_LINE_NUMBERS_MATCH,
484 &mut self.check_test_line_numbers_match,
485 );
486
487 self.update_pass_mode(ln, test_revision, config);
488 self.update_fail_mode(ln, config);
489
490 config.set_name_directive(ln, IGNORE_PASS, &mut self.ignore_pass);
491
492 if let Some(NormalizeRule { kind, regex, replacement }) =
493 config.parse_custom_normalization(ln)
494 {
495 let rule_tuple = (regex, replacement);
496 match kind {
497 NormalizeKind::Stdout => self.normalize_stdout.push(rule_tuple),
498 NormalizeKind::Stderr => self.normalize_stderr.push(rule_tuple),
499 NormalizeKind::Stderr32bit => {
500 if config.target_cfg().pointer_width == 32 {
501 self.normalize_stderr.push(rule_tuple);
502 }
503 }
504 NormalizeKind::Stderr64bit => {
505 if config.target_cfg().pointer_width == 64 {
506 self.normalize_stderr.push(rule_tuple);
507 }
508 }
509 }
510 }
511
512 if let Some(code) = config
513 .parse_name_value_directive(ln, FAILURE_STATUS)
514 .and_then(|code| code.trim().parse::<i32>().ok())
515 {
516 self.failure_status = Some(code);
517 }
518
519 config.set_name_directive(
520 ln,
521 DONT_CHECK_FAILURE_STATUS,
522 &mut self.dont_check_failure_status,
523 );
524
525 config.set_name_directive(ln, RUN_RUSTFIX, &mut self.run_rustfix);
526 config.set_name_directive(
527 ln,
528 RUSTFIX_ONLY_MACHINE_APPLICABLE,
529 &mut self.rustfix_only_machine_applicable,
530 );
531 config.set_name_value_directive(
532 ln,
533 ASSEMBLY_OUTPUT,
534 &mut self.assembly_output,
535 |r| r.trim().to_string(),
536 );
537 config.set_name_directive(
538 ln,
539 STDERR_PER_BITWIDTH,
540 &mut self.stderr_per_bitwidth,
541 );
542 config.set_name_directive(ln, INCREMENTAL, &mut self.incremental);
543
544 if let Some(known_bug) = config.parse_name_value_directive(ln, KNOWN_BUG) {
547 let known_bug = known_bug.trim();
548 if known_bug == "unknown"
549 || known_bug.split(',').all(|issue_ref| {
550 issue_ref
551 .trim()
552 .split_once('#')
553 .filter(|(_, number)| {
554 number.chars().all(|digit| digit.is_numeric())
555 })
556 .is_some()
557 })
558 {
559 self.known_bug = true;
560 } else {
561 panic!(
562 "Invalid known-bug value: {known_bug}\nIt requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`."
563 );
564 }
565 } else if config.parse_name_directive(ln, KNOWN_BUG) {
566 panic!(
567 "Invalid known-bug attribute, requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`."
568 );
569 }
570
571 config.set_name_value_directive(
572 ln,
573 TEST_MIR_PASS,
574 &mut self.mir_unit_test,
575 |s| s.trim().to_string(),
576 );
577 config.set_name_directive(ln, REMAP_SRC_BASE, &mut self.remap_src_base);
578
579 if let Some(flags) = config.parse_name_value_directive(ln, LLVM_COV_FLAGS) {
580 self.llvm_cov_flags.extend(split_flags(&flags));
581 }
582
583 if let Some(flags) = config.parse_name_value_directive(ln, FILECHECK_FLAGS) {
584 self.filecheck_flags.extend(split_flags(&flags));
585 }
586
587 config.set_name_directive(ln, NO_AUTO_CHECK_CFG, &mut self.no_auto_check_cfg);
588
589 self.update_add_core_stubs(ln, config);
590
591 if let Some(err_kind) =
592 config.parse_name_value_directive(ln, DONT_REQUIRE_ANNOTATIONS)
593 {
594 self.dont_require_annotations
595 .insert(ErrorKind::expect_from_user_str(err_kind.trim()));
596 }
597 },
598 );
599
600 if poisoned {
601 eprintln!("errors encountered during TestProps parsing: {}", testfile);
602 panic!("errors encountered during TestProps parsing");
603 }
604 }
605
606 if self.should_ice {
607 self.failure_status = Some(101);
608 }
609
610 if config.mode == TestMode::Incremental {
611 self.incremental = true;
612 }
613
614 if config.mode == TestMode::Crashes {
615 self.rustc_env = vec![
619 ("RUST_BACKTRACE".to_string(), "0".to_string()),
620 ("RUSTC_ICE".to_string(), "0".to_string()),
621 ];
622 }
623
624 for key in &["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] {
625 if let Ok(val) = env::var(key) {
626 if !self.exec_env.iter().any(|&(ref x, _)| x == key) {
627 self.exec_env.push(((*key).to_owned(), val))
628 }
629 }
630 }
631
632 if let (Some(edition), false) = (&config.edition, has_edition) {
633 self.compile_flags.insert(0, format!("--edition={}", edition));
636 }
637 }
638
639 fn update_fail_mode(&mut self, ln: &str, config: &Config) {
640 let check_ui = |mode: &str| {
641 if config.mode != TestMode::Ui && config.mode != TestMode::Crashes {
643 panic!("`{}-fail` directive is only supported in UI tests", mode);
644 }
645 };
646 if config.mode == TestMode::Ui && config.parse_name_directive(ln, "compile-fail") {
647 panic!("`compile-fail` directive is useless in UI tests");
648 }
649 let fail_mode = if config.parse_name_directive(ln, "check-fail") {
650 check_ui("check");
651 Some(FailMode::Check)
652 } else if config.parse_name_directive(ln, "build-fail") {
653 check_ui("build");
654 Some(FailMode::Build)
655 } else if config.parse_name_directive(ln, "run-fail") {
656 check_ui("run");
657 Some(FailMode::Run)
658 } else {
659 None
660 };
661 match (self.fail_mode, fail_mode) {
662 (None, Some(_)) => self.fail_mode = fail_mode,
663 (Some(_), Some(_)) => panic!("multiple `*-fail` directives in a single test"),
664 (_, None) => {}
665 }
666 }
667
668 fn update_pass_mode(&mut self, ln: &str, revision: Option<&str>, config: &Config) {
669 let check_no_run = |s| match (config.mode, s) {
670 (TestMode::Ui, _) => (),
671 (TestMode::Crashes, _) => (),
672 (TestMode::Codegen, "build-pass") => (),
673 (TestMode::Incremental, _) => {
674 if revision.is_some() && !self.revisions.iter().all(|r| r.starts_with("cfail")) {
675 panic!("`{s}` directive is only supported in `cfail` incremental tests")
676 }
677 }
678 (mode, _) => panic!("`{s}` directive is not supported in `{mode}` tests"),
679 };
680 let pass_mode = if config.parse_name_directive(ln, "check-pass") {
681 check_no_run("check-pass");
682 Some(PassMode::Check)
683 } else if config.parse_name_directive(ln, "build-pass") {
684 check_no_run("build-pass");
685 Some(PassMode::Build)
686 } else if config.parse_name_directive(ln, "run-pass") {
687 check_no_run("run-pass");
688 Some(PassMode::Run)
689 } else {
690 None
691 };
692 match (self.pass_mode, pass_mode) {
693 (None, Some(_)) => self.pass_mode = pass_mode,
694 (Some(_), Some(_)) => panic!("multiple `*-pass` directives in a single test"),
695 (_, None) => {}
696 }
697 }
698
699 pub fn pass_mode(&self, config: &Config) -> Option<PassMode> {
700 if !self.ignore_pass && self.fail_mode.is_none() {
701 if let mode @ Some(_) = config.force_pass_mode {
702 return mode;
703 }
704 }
705 self.pass_mode
706 }
707
708 pub fn local_pass_mode(&self) -> Option<PassMode> {
710 self.pass_mode
711 }
712
713 pub fn update_add_core_stubs(&mut self, ln: &str, config: &Config) {
714 let add_core_stubs = config.parse_name_directive(ln, directives::ADD_CORE_STUBS);
715 if add_core_stubs {
716 if !matches!(config.mode, TestMode::Ui | TestMode::Codegen | TestMode::Assembly) {
717 panic!(
718 "`add-core-stubs` is currently only supported for ui, codegen and assembly test modes"
719 );
720 }
721
722 if self.local_pass_mode().is_some_and(|pm| pm == PassMode::Run) {
725 panic!("`add-core-stubs` cannot be used to run the test binary");
728 }
729
730 self.add_core_stubs = add_core_stubs;
731 }
732 }
733}
734
735fn line_directive<'line>(
738 line_number: usize,
739 original_line: &'line str,
740) -> Option<DirectiveLine<'line>> {
741 let after_comment =
743 original_line.trim_start().strip_prefix(COMPILETEST_DIRECTIVE_PREFIX)?.trim_start();
744
745 let revision;
746 let raw_directive;
747
748 if let Some(after_open_bracket) = after_comment.strip_prefix('[') {
749 let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else {
751 panic!(
752 "malformed condition directive: expected `{COMPILETEST_DIRECTIVE_PREFIX}[foo]`, found `{original_line}`"
753 )
754 };
755
756 revision = Some(line_revision);
757 raw_directive = after_close_bracket.trim_start();
758 } else {
759 revision = None;
760 raw_directive = after_comment;
761 };
762
763 Some(DirectiveLine { line_number, revision, raw_directive })
764}
765
766const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
770 "add-core-stubs",
772 "assembly-output",
773 "aux-bin",
774 "aux-build",
775 "aux-codegen-backend",
776 "aux-crate",
777 "build-aux-docs",
778 "build-fail",
779 "build-pass",
780 "check-fail",
781 "check-pass",
782 "check-run-results",
783 "check-stdout",
784 "check-test-line-numbers-match",
785 "compile-flags",
786 "doc-flags",
787 "dont-check-compiler-stderr",
788 "dont-check-compiler-stdout",
789 "dont-check-failure-status",
790 "dont-require-annotations",
791 "edition",
792 "error-pattern",
793 "exact-llvm-major-version",
794 "exec-env",
795 "failure-status",
796 "filecheck-flags",
797 "forbid-output",
798 "force-host",
799 "ignore-16bit",
800 "ignore-32bit",
801 "ignore-64bit",
802 "ignore-aarch64",
803 "ignore-aarch64-pc-windows-msvc",
804 "ignore-aarch64-unknown-linux-gnu",
805 "ignore-aix",
806 "ignore-android",
807 "ignore-apple",
808 "ignore-arm",
809 "ignore-arm-unknown-linux-gnueabi",
810 "ignore-arm-unknown-linux-gnueabihf",
811 "ignore-arm-unknown-linux-musleabi",
812 "ignore-arm-unknown-linux-musleabihf",
813 "ignore-auxiliary",
814 "ignore-avr",
815 "ignore-beta",
816 "ignore-cdb",
817 "ignore-compare-mode-next-solver",
818 "ignore-compare-mode-polonius",
819 "ignore-coverage-map",
820 "ignore-coverage-run",
821 "ignore-cross-compile",
822 "ignore-eabi",
823 "ignore-elf",
824 "ignore-emscripten",
825 "ignore-endian-big",
826 "ignore-enzyme",
827 "ignore-freebsd",
828 "ignore-fuchsia",
829 "ignore-gdb",
830 "ignore-gdb-version",
831 "ignore-gnu",
832 "ignore-haiku",
833 "ignore-horizon",
834 "ignore-i686-pc-windows-gnu",
835 "ignore-i686-pc-windows-msvc",
836 "ignore-illumos",
837 "ignore-ios",
838 "ignore-linux",
839 "ignore-lldb",
840 "ignore-llvm-version",
841 "ignore-loongarch32",
842 "ignore-loongarch64",
843 "ignore-macabi",
844 "ignore-macos",
845 "ignore-msp430",
846 "ignore-msvc",
847 "ignore-musl",
848 "ignore-netbsd",
849 "ignore-nightly",
850 "ignore-none",
851 "ignore-nto",
852 "ignore-nvptx64",
853 "ignore-nvptx64-nvidia-cuda",
854 "ignore-openbsd",
855 "ignore-pass",
856 "ignore-powerpc",
857 "ignore-remote",
858 "ignore-riscv64",
859 "ignore-rustc-debug-assertions",
860 "ignore-rustc_abi-x86-sse2",
861 "ignore-s390x",
862 "ignore-sgx",
863 "ignore-sparc64",
864 "ignore-spirv",
865 "ignore-stable",
866 "ignore-stage1",
867 "ignore-stage2",
868 "ignore-std-debug-assertions",
869 "ignore-test",
870 "ignore-thumb",
871 "ignore-thumbv8m.base-none-eabi",
872 "ignore-thumbv8m.main-none-eabi",
873 "ignore-tvos",
874 "ignore-unix",
875 "ignore-unknown",
876 "ignore-uwp",
877 "ignore-visionos",
878 "ignore-vxworks",
879 "ignore-wasi",
880 "ignore-wasm",
881 "ignore-wasm32",
882 "ignore-wasm32-bare",
883 "ignore-wasm64",
884 "ignore-watchos",
885 "ignore-windows",
886 "ignore-windows-gnu",
887 "ignore-windows-msvc",
888 "ignore-x32",
889 "ignore-x86",
890 "ignore-x86_64",
891 "ignore-x86_64-apple-darwin",
892 "ignore-x86_64-pc-windows-gnu",
893 "ignore-x86_64-unknown-linux-gnu",
894 "incremental",
895 "known-bug",
896 "llvm-cov-flags",
897 "max-llvm-major-version",
898 "min-cdb-version",
899 "min-gdb-version",
900 "min-lldb-version",
901 "min-llvm-version",
902 "min-system-llvm-version",
903 "needs-asm-support",
904 "needs-crate-type",
905 "needs-deterministic-layouts",
906 "needs-dlltool",
907 "needs-dynamic-linking",
908 "needs-enzyme",
909 "needs-force-clang-based-tests",
910 "needs-git-hash",
911 "needs-llvm-components",
912 "needs-llvm-zstd",
913 "needs-profiler-runtime",
914 "needs-relocation-model-pic",
915 "needs-run-enabled",
916 "needs-rust-lld",
917 "needs-rustc-debug-assertions",
918 "needs-sanitizer-address",
919 "needs-sanitizer-cfi",
920 "needs-sanitizer-dataflow",
921 "needs-sanitizer-hwaddress",
922 "needs-sanitizer-kcfi",
923 "needs-sanitizer-leak",
924 "needs-sanitizer-memory",
925 "needs-sanitizer-memtag",
926 "needs-sanitizer-safestack",
927 "needs-sanitizer-shadow-call-stack",
928 "needs-sanitizer-support",
929 "needs-sanitizer-thread",
930 "needs-std-debug-assertions",
931 "needs-subprocess",
932 "needs-symlink",
933 "needs-target-has-atomic",
934 "needs-target-std",
935 "needs-threads",
936 "needs-unwind",
937 "needs-wasmtime",
938 "needs-xray",
939 "no-auto-check-cfg",
940 "no-prefer-dynamic",
941 "normalize-stderr",
942 "normalize-stderr-32bit",
943 "normalize-stderr-64bit",
944 "normalize-stdout",
945 "only-16bit",
946 "only-32bit",
947 "only-64bit",
948 "only-aarch64",
949 "only-aarch64-apple-darwin",
950 "only-aarch64-unknown-linux-gnu",
951 "only-apple",
952 "only-arm",
953 "only-avr",
954 "only-beta",
955 "only-bpf",
956 "only-cdb",
957 "only-dist",
958 "only-elf",
959 "only-emscripten",
960 "only-gnu",
961 "only-i686-pc-windows-gnu",
962 "only-i686-pc-windows-msvc",
963 "only-i686-unknown-linux-gnu",
964 "only-ios",
965 "only-linux",
966 "only-loongarch32",
967 "only-loongarch64",
968 "only-loongarch64-unknown-linux-gnu",
969 "only-macos",
970 "only-mips",
971 "only-mips64",
972 "only-msp430",
973 "only-msvc",
974 "only-musl",
975 "only-nightly",
976 "only-nvptx64",
977 "only-powerpc",
978 "only-riscv64",
979 "only-rustc_abi-x86-sse2",
980 "only-s390x",
981 "only-sparc",
982 "only-sparc64",
983 "only-stable",
984 "only-thumb",
985 "only-tvos",
986 "only-unix",
987 "only-visionos",
988 "only-wasm32",
989 "only-wasm32-bare",
990 "only-wasm32-wasip1",
991 "only-watchos",
992 "only-windows",
993 "only-windows-gnu",
994 "only-windows-msvc",
995 "only-x86",
996 "only-x86_64",
997 "only-x86_64-apple-darwin",
998 "only-x86_64-fortanix-unknown-sgx",
999 "only-x86_64-pc-windows-gnu",
1000 "only-x86_64-pc-windows-msvc",
1001 "only-x86_64-unknown-linux-gnu",
1002 "pp-exact",
1003 "pretty-compare-only",
1004 "pretty-mode",
1005 "proc-macro",
1006 "reference",
1007 "regex-error-pattern",
1008 "remap-src-base",
1009 "revisions",
1010 "run-fail",
1011 "run-flags",
1012 "run-pass",
1013 "run-rustfix",
1014 "rustc-env",
1015 "rustfix-only-machine-applicable",
1016 "should-fail",
1017 "should-ice",
1018 "stderr-per-bitwidth",
1019 "test-mir-pass",
1020 "unique-doc-out-dir",
1021 "unset-exec-env",
1022 "unset-rustc-env",
1023 "unused-revision-names",
1025 ];
1027
1028const KNOWN_HTMLDOCCK_DIRECTIVE_NAMES: &[&str] = &[
1029 "count",
1030 "!count",
1031 "files",
1032 "!files",
1033 "has",
1034 "!has",
1035 "has-dir",
1036 "!has-dir",
1037 "hasraw",
1038 "!hasraw",
1039 "matches",
1040 "!matches",
1041 "matchesraw",
1042 "!matchesraw",
1043 "snapshot",
1044 "!snapshot",
1045];
1046
1047const KNOWN_JSONDOCCK_DIRECTIVE_NAMES: &[&str] =
1048 &["count", "!count", "has", "!has", "is", "!is", "ismany", "!ismany", "set", "!set"];
1049
1050struct DirectiveLine<'ln> {
1064 line_number: usize,
1065 revision: Option<&'ln str>,
1069 raw_directive: &'ln str,
1075}
1076
1077impl<'ln> DirectiveLine<'ln> {
1078 fn applies_to_test_revision(&self, test_revision: Option<&str>) -> bool {
1079 self.revision.is_none() || self.revision == test_revision
1080 }
1081}
1082
1083pub(crate) struct CheckDirectiveResult<'ln> {
1084 is_known_directive: bool,
1085 trailing_directive: Option<&'ln str>,
1086}
1087
1088pub(crate) fn check_directive<'a>(
1089 directive_ln: &'a str,
1090 mode: TestMode,
1091) -> CheckDirectiveResult<'a> {
1092 let (directive_name, post) = directive_ln.split_once([':', ' ']).unwrap_or((directive_ln, ""));
1093
1094 let is_known_directive = KNOWN_DIRECTIVE_NAMES.contains(&directive_name)
1095 || match mode {
1096 TestMode::Rustdoc => KNOWN_HTMLDOCCK_DIRECTIVE_NAMES.contains(&directive_name),
1097 TestMode::RustdocJson => KNOWN_JSONDOCCK_DIRECTIVE_NAMES.contains(&directive_name),
1098 _ => false,
1099 };
1100
1101 let trailing = post.trim().split_once(' ').map(|(pre, _)| pre).unwrap_or(post);
1102 let trailing_directive = {
1103 directive_ln.get(directive_name.len()..).is_some_and(|s| s.starts_with(' '))
1105 && KNOWN_DIRECTIVE_NAMES.contains(&trailing)
1107 }
1108 .then_some(trailing);
1109
1110 CheckDirectiveResult { is_known_directive, trailing_directive }
1111}
1112
1113const COMPILETEST_DIRECTIVE_PREFIX: &str = "//@";
1114
1115fn iter_directives(
1116 mode: TestMode,
1117 poisoned: &mut bool,
1118 testfile: &Utf8Path,
1119 rdr: impl Read,
1120 it: &mut dyn FnMut(DirectiveLine<'_>),
1121) {
1122 if testfile.is_dir() {
1123 return;
1124 }
1125
1126 if mode == TestMode::CoverageRun {
1131 let extra_directives: &[&str] = &[
1132 "needs-profiler-runtime",
1133 "ignore-cross-compile",
1137 ];
1138 for raw_directive in extra_directives {
1140 it(DirectiveLine { line_number: 0, revision: None, raw_directive });
1141 }
1142 }
1143
1144 let mut rdr = BufReader::with_capacity(1024, rdr);
1145 let mut ln = String::new();
1146 let mut line_number = 0;
1147
1148 loop {
1149 line_number += 1;
1150 ln.clear();
1151 if rdr.read_line(&mut ln).unwrap() == 0 {
1152 break;
1153 }
1154 let ln = ln.trim();
1155
1156 let Some(directive_line) = line_directive(line_number, ln) else {
1157 continue;
1158 };
1159
1160 if testfile.extension() == Some("rs") {
1162 let CheckDirectiveResult { is_known_directive, trailing_directive } =
1163 check_directive(directive_line.raw_directive, mode);
1164
1165 if !is_known_directive {
1166 *poisoned = true;
1167
1168 error!(
1169 "{testfile}:{line_number}: detected unknown compiletest test directive `{}`",
1170 directive_line.raw_directive,
1171 );
1172
1173 return;
1174 }
1175
1176 if let Some(trailing_directive) = &trailing_directive {
1177 *poisoned = true;
1178
1179 error!(
1180 "{testfile}:{line_number}: detected trailing compiletest test directive `{}`",
1181 trailing_directive,
1182 );
1183 help!("put the trailing directive in its own line: `//@ {}`", trailing_directive);
1184
1185 return;
1186 }
1187 }
1188
1189 it(directive_line);
1190 }
1191}
1192
1193impl Config {
1194 fn parse_and_update_revisions(
1195 &self,
1196 testfile: &Utf8Path,
1197 line: &str,
1198 existing: &mut Vec<String>,
1199 ) {
1200 const FORBIDDEN_REVISION_NAMES: [&str; 2] = [
1201 "true", "false",
1205 ];
1206
1207 const FILECHECK_FORBIDDEN_REVISION_NAMES: [&str; 9] =
1208 ["CHECK", "COM", "NEXT", "SAME", "EMPTY", "NOT", "COUNT", "DAG", "LABEL"];
1209
1210 if let Some(raw) = self.parse_name_value_directive(line, "revisions") {
1211 if self.mode == TestMode::RunMake {
1212 panic!("`run-make` tests do not support revisions: {}", testfile);
1213 }
1214
1215 let mut duplicates: HashSet<_> = existing.iter().cloned().collect();
1216 for revision in raw.split_whitespace() {
1217 if !duplicates.insert(revision.to_string()) {
1218 panic!("duplicate revision: `{}` in line `{}`: {}", revision, raw, testfile);
1219 }
1220
1221 if FORBIDDEN_REVISION_NAMES.contains(&revision) {
1222 panic!(
1223 "revision name `{revision}` is not permitted: `{}` in line `{}`: {}",
1224 revision, raw, testfile
1225 );
1226 }
1227
1228 if matches!(self.mode, TestMode::Assembly | TestMode::Codegen | TestMode::MirOpt)
1229 && FILECHECK_FORBIDDEN_REVISION_NAMES.contains(&revision)
1230 {
1231 panic!(
1232 "revision name `{revision}` is not permitted in a test suite that uses \
1233 `FileCheck` annotations as it is confusing when used as custom `FileCheck` \
1234 prefix: `{revision}` in line `{}`: {}",
1235 raw, testfile
1236 );
1237 }
1238
1239 existing.push(revision.to_string());
1240 }
1241 }
1242 }
1243
1244 fn parse_env(nv: String) -> (String, String) {
1245 let (name, value) = nv.split_once('=').unwrap_or((&nv, ""));
1249 let name = name.trim();
1252 (name.to_owned(), value.to_owned())
1253 }
1254
1255 fn parse_pp_exact(&self, line: &str, testfile: &Utf8Path) -> Option<Utf8PathBuf> {
1256 if let Some(s) = self.parse_name_value_directive(line, "pp-exact") {
1257 Some(Utf8PathBuf::from(&s))
1258 } else if self.parse_name_directive(line, "pp-exact") {
1259 testfile.file_name().map(Utf8PathBuf::from)
1260 } else {
1261 None
1262 }
1263 }
1264
1265 fn parse_custom_normalization(&self, raw_directive: &str) -> Option<NormalizeRule> {
1266 let (directive_name, raw_value) = raw_directive.split_once(':')?;
1269
1270 let kind = match directive_name {
1271 "normalize-stdout" => NormalizeKind::Stdout,
1272 "normalize-stderr" => NormalizeKind::Stderr,
1273 "normalize-stderr-32bit" => NormalizeKind::Stderr32bit,
1274 "normalize-stderr-64bit" => NormalizeKind::Stderr64bit,
1275 _ => return None,
1276 };
1277
1278 let Some((regex, replacement)) = parse_normalize_rule(raw_value) else {
1279 error!("couldn't parse custom normalization rule: `{raw_directive}`");
1280 help!("expected syntax is: `{directive_name}: \"REGEX\" -> \"REPLACEMENT\"`");
1281 panic!("invalid normalization rule detected");
1282 };
1283 Some(NormalizeRule { kind, regex, replacement })
1284 }
1285
1286 fn parse_name_directive(&self, line: &str, directive: &str) -> bool {
1287 line.starts_with(directive)
1290 && matches!(line.as_bytes().get(directive.len()), None | Some(&b' ') | Some(&b':'))
1291 }
1292
1293 fn parse_negative_name_directive(&self, line: &str, directive: &str) -> bool {
1294 line.starts_with("no-") && self.parse_name_directive(&line[3..], directive)
1295 }
1296
1297 pub fn parse_name_value_directive(&self, line: &str, directive: &str) -> Option<String> {
1298 let colon = directive.len();
1299 if line.starts_with(directive) && line.as_bytes().get(colon) == Some(&b':') {
1300 let value = line[(colon + 1)..].to_owned();
1301 debug!("{}: {}", directive, value);
1302 Some(expand_variables(value, self))
1303 } else {
1304 None
1305 }
1306 }
1307
1308 fn parse_edition(&self, line: &str) -> Option<String> {
1309 self.parse_name_value_directive(line, "edition")
1310 }
1311
1312 fn set_name_directive(&self, line: &str, directive: &str, value: &mut bool) {
1313 match value {
1314 true => {
1315 if self.parse_negative_name_directive(line, directive) {
1316 *value = false;
1317 }
1318 }
1319 false => {
1320 if self.parse_name_directive(line, directive) {
1321 *value = true;
1322 }
1323 }
1324 }
1325 }
1326
1327 fn set_name_value_directive<T>(
1328 &self,
1329 line: &str,
1330 directive: &str,
1331 value: &mut Option<T>,
1332 parse: impl FnOnce(String) -> T,
1333 ) {
1334 if value.is_none() {
1335 *value = self.parse_name_value_directive(line, directive).map(parse);
1336 }
1337 }
1338
1339 fn push_name_value_directive<T>(
1340 &self,
1341 line: &str,
1342 directive: &str,
1343 values: &mut Vec<T>,
1344 parse: impl FnOnce(String) -> T,
1345 ) {
1346 if let Some(value) = self.parse_name_value_directive(line, directive).map(parse) {
1347 values.push(value);
1348 }
1349 }
1350}
1351
1352fn expand_variables(mut value: String, config: &Config) -> String {
1354 const CWD: &str = "{{cwd}}";
1355 const SRC_BASE: &str = "{{src-base}}";
1356 const TEST_SUITE_BUILD_BASE: &str = "{{build-base}}";
1357 const RUST_SRC_BASE: &str = "{{rust-src-base}}";
1358 const SYSROOT_BASE: &str = "{{sysroot-base}}";
1359 const TARGET_LINKER: &str = "{{target-linker}}";
1360 const TARGET: &str = "{{target}}";
1361
1362 if value.contains(CWD) {
1363 let cwd = env::current_dir().unwrap();
1364 value = value.replace(CWD, &cwd.to_str().unwrap());
1365 }
1366
1367 if value.contains(SRC_BASE) {
1368 value = value.replace(SRC_BASE, &config.src_test_suite_root.as_str());
1369 }
1370
1371 if value.contains(TEST_SUITE_BUILD_BASE) {
1372 value = value.replace(TEST_SUITE_BUILD_BASE, &config.build_test_suite_root.as_str());
1373 }
1374
1375 if value.contains(SYSROOT_BASE) {
1376 value = value.replace(SYSROOT_BASE, &config.sysroot_base.as_str());
1377 }
1378
1379 if value.contains(TARGET_LINKER) {
1380 value = value.replace(TARGET_LINKER, config.target_linker.as_deref().unwrap_or(""));
1381 }
1382
1383 if value.contains(TARGET) {
1384 value = value.replace(TARGET, &config.target);
1385 }
1386
1387 if value.contains(RUST_SRC_BASE) {
1388 let src_base = config.sysroot_base.join("lib/rustlib/src/rust");
1389 src_base.try_exists().expect(&*format!("{} should exists", src_base));
1390 let src_base = src_base.read_link_utf8().unwrap_or(src_base);
1391 value = value.replace(RUST_SRC_BASE, &src_base.as_str());
1392 }
1393
1394 value
1395}
1396
1397struct NormalizeRule {
1398 kind: NormalizeKind,
1399 regex: String,
1400 replacement: String,
1401}
1402
1403enum NormalizeKind {
1404 Stdout,
1405 Stderr,
1406 Stderr32bit,
1407 Stderr64bit,
1408}
1409
1410fn parse_normalize_rule(raw_value: &str) -> Option<(String, String)> {
1415 let captures = static_regex!(
1417 r#"(?x) # (verbose mode regex)
1418 ^
1419 \s* # (leading whitespace)
1420 "(?<regex>[^"]*)" # "REGEX"
1421 \s+->\s+ # ->
1422 "(?<replacement>[^"]*)" # "REPLACEMENT"
1423 $
1424 "#
1425 )
1426 .captures(raw_value)?;
1427 let regex = captures["regex"].to_owned();
1428 let replacement = captures["replacement"].to_owned();
1429 let replacement = replacement.replace("\\n", "\n");
1433 Some((regex, replacement))
1434}
1435
1436pub fn extract_llvm_version(version: &str) -> Version {
1446 let version = version.trim();
1449 let uninterested = |c: char| !c.is_ascii_digit() && c != '.';
1450 let version_without_suffix = match version.split_once(uninterested) {
1451 Some((prefix, _suffix)) => prefix,
1452 None => version,
1453 };
1454
1455 let components: Vec<u64> = version_without_suffix
1456 .split('.')
1457 .map(|s| s.parse().expect("llvm version component should consist of only digits"))
1458 .collect();
1459
1460 match &components[..] {
1461 [major] => Version::new(*major, 0, 0),
1462 [major, minor] => Version::new(*major, *minor, 0),
1463 [major, minor, patch] => Version::new(*major, *minor, *patch),
1464 _ => panic!("malformed llvm version string, expected only 1-3 components: {version}"),
1465 }
1466}
1467
1468pub fn extract_llvm_version_from_binary(binary_path: &str) -> Option<Version> {
1469 let output = Command::new(binary_path).arg("--version").output().ok()?;
1470 if !output.status.success() {
1471 return None;
1472 }
1473 let version = String::from_utf8(output.stdout).ok()?;
1474 for line in version.lines() {
1475 if let Some(version) = line.split("LLVM version ").nth(1) {
1476 return Some(extract_llvm_version(version));
1477 }
1478 }
1479 None
1480}
1481
1482pub fn llvm_has_libzstd(config: &Config) -> bool {
1486 fn is_zstd_in_config(llvm_bin_dir: &Utf8Path) -> Option<()> {
1494 let llvm_config_path = llvm_bin_dir.join("llvm-config");
1495 let output = Command::new(llvm_config_path).arg("--system-libs").output().ok()?;
1496 assert!(output.status.success(), "running llvm-config --system-libs failed");
1497
1498 let libs = String::from_utf8(output.stdout).ok()?;
1499 for lib in libs.split_whitespace() {
1500 if lib.ends_with("libzstd.a") && Utf8Path::new(lib).exists() {
1501 return Some(());
1502 }
1503 }
1504
1505 None
1506 }
1507
1508 #[cfg(unix)]
1518 fn is_lld_built_with_zstd(llvm_bin_dir: &Utf8Path) -> Option<()> {
1519 let lld_path = llvm_bin_dir.join("lld");
1520 if lld_path.exists() {
1521 let lld_symlink_path = llvm_bin_dir.join("ld.lld");
1524 if !lld_symlink_path.exists() {
1525 std::os::unix::fs::symlink(lld_path, &lld_symlink_path).ok()?;
1526 }
1527
1528 let output = Command::new(&lld_symlink_path)
1531 .arg("--compress-debug-sections=zstd")
1532 .output()
1533 .ok()?;
1534 assert!(!output.status.success());
1535
1536 let stderr = String::from_utf8(output.stderr).ok()?;
1539 let zstd_available = !stderr.contains("LLVM was not built with LLVM_ENABLE_ZSTD");
1540
1541 std::fs::remove_file(lld_symlink_path).ok()?;
1544
1545 if zstd_available {
1546 return Some(());
1547 }
1548 }
1549
1550 None
1551 }
1552
1553 #[cfg(not(unix))]
1554 fn is_lld_built_with_zstd(_llvm_bin_dir: &Utf8Path) -> Option<()> {
1555 None
1556 }
1557
1558 if let Some(llvm_bin_dir) = &config.llvm_bin_dir {
1559 if is_zstd_in_config(llvm_bin_dir).is_some() {
1561 return true;
1562 }
1563
1564 if config.target == "x86_64-unknown-linux-gnu"
1572 && config.host == config.target
1573 && is_lld_built_with_zstd(llvm_bin_dir).is_some()
1574 {
1575 return true;
1576 }
1577 }
1578
1579 false
1581}
1582
1583fn extract_version_range<'a, F, VersionTy: Clone>(
1589 line: &'a str,
1590 parse: F,
1591) -> Option<(VersionTy, VersionTy)>
1592where
1593 F: Fn(&'a str) -> Option<VersionTy>,
1594{
1595 let mut splits = line.splitn(2, "- ").map(str::trim);
1596 let min = splits.next().unwrap();
1597 if min.ends_with('-') {
1598 return None;
1599 }
1600
1601 let max = splits.next();
1602
1603 if min.is_empty() {
1604 return None;
1605 }
1606
1607 let min = parse(min)?;
1608 let max = match max {
1609 Some("") => return None,
1610 Some(max) => parse(max)?,
1611 _ => min.clone(),
1612 };
1613
1614 Some((min, max))
1615}
1616
1617pub(crate) fn make_test_description<R: Read>(
1618 config: &Config,
1619 cache: &DirectivesCache,
1620 name: String,
1621 path: &Utf8Path,
1622 src: R,
1623 test_revision: Option<&str>,
1624 poisoned: &mut bool,
1625) -> CollectedTestDesc {
1626 let mut ignore = false;
1627 let mut ignore_message = None;
1628 let mut should_fail = false;
1629
1630 let mut local_poisoned = false;
1631
1632 iter_directives(
1634 config.mode,
1635 &mut local_poisoned,
1636 path,
1637 src,
1638 &mut |directive @ DirectiveLine { line_number, raw_directive: ln, .. }| {
1639 if !directive.applies_to_test_revision(test_revision) {
1640 return;
1641 }
1642
1643 macro_rules! decision {
1644 ($e:expr) => {
1645 match $e {
1646 IgnoreDecision::Ignore { reason } => {
1647 ignore = true;
1648 ignore_message = Some(reason.into());
1649 }
1650 IgnoreDecision::Error { message } => {
1651 error!("{path}:{line_number}: {message}");
1652 *poisoned = true;
1653 return;
1654 }
1655 IgnoreDecision::Continue => {}
1656 }
1657 };
1658 }
1659
1660 decision!(cfg::handle_ignore(config, ln));
1661 decision!(cfg::handle_only(config, ln));
1662 decision!(needs::handle_needs(&cache.needs, config, ln));
1663 decision!(ignore_llvm(config, path, ln));
1664 decision!(ignore_cdb(config, ln));
1665 decision!(ignore_gdb(config, ln));
1666 decision!(ignore_lldb(config, ln));
1667
1668 if config.target == "wasm32-unknown-unknown"
1669 && config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS)
1670 {
1671 decision!(IgnoreDecision::Ignore {
1672 reason: "ignored on WASM as the run results cannot be checked there".into(),
1673 });
1674 }
1675
1676 should_fail |= config.parse_name_directive(ln, "should-fail");
1677 },
1678 );
1679
1680 if local_poisoned {
1681 eprintln!("errors encountered when trying to make test description: {}", path);
1682 panic!("errors encountered when trying to make test description");
1683 }
1684
1685 let should_panic = match config.mode {
1689 TestMode::Pretty => ShouldPanic::No,
1690 _ if should_fail => ShouldPanic::Yes,
1691 _ => ShouldPanic::No,
1692 };
1693
1694 CollectedTestDesc { name, ignore, ignore_message, should_panic }
1695}
1696
1697fn ignore_cdb(config: &Config, line: &str) -> IgnoreDecision {
1698 if config.debugger != Some(Debugger::Cdb) {
1699 return IgnoreDecision::Continue;
1700 }
1701
1702 if let Some(actual_version) = config.cdb_version {
1703 if let Some(rest) = line.strip_prefix("min-cdb-version:").map(str::trim) {
1704 let min_version = extract_cdb_version(rest).unwrap_or_else(|| {
1705 panic!("couldn't parse version range: {:?}", rest);
1706 });
1707
1708 if actual_version < min_version {
1711 return IgnoreDecision::Ignore {
1712 reason: format!("ignored when the CDB version is lower than {rest}"),
1713 };
1714 }
1715 }
1716 }
1717 IgnoreDecision::Continue
1718}
1719
1720fn ignore_gdb(config: &Config, line: &str) -> IgnoreDecision {
1721 if config.debugger != Some(Debugger::Gdb) {
1722 return IgnoreDecision::Continue;
1723 }
1724
1725 if let Some(actual_version) = config.gdb_version {
1726 if let Some(rest) = line.strip_prefix("min-gdb-version:").map(str::trim) {
1727 let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version)
1728 .unwrap_or_else(|| {
1729 panic!("couldn't parse version range: {:?}", rest);
1730 });
1731
1732 if start_ver != end_ver {
1733 panic!("Expected single GDB version")
1734 }
1735 if actual_version < start_ver {
1738 return IgnoreDecision::Ignore {
1739 reason: format!("ignored when the GDB version is lower than {rest}"),
1740 };
1741 }
1742 } else if let Some(rest) = line.strip_prefix("ignore-gdb-version:").map(str::trim) {
1743 let (min_version, max_version) = extract_version_range(rest, extract_gdb_version)
1744 .unwrap_or_else(|| {
1745 panic!("couldn't parse version range: {:?}", rest);
1746 });
1747
1748 if max_version < min_version {
1749 panic!("Malformed GDB version range: max < min")
1750 }
1751
1752 if actual_version >= min_version && actual_version <= max_version {
1753 if min_version == max_version {
1754 return IgnoreDecision::Ignore {
1755 reason: format!("ignored when the GDB version is {rest}"),
1756 };
1757 } else {
1758 return IgnoreDecision::Ignore {
1759 reason: format!("ignored when the GDB version is between {rest}"),
1760 };
1761 }
1762 }
1763 }
1764 }
1765 IgnoreDecision::Continue
1766}
1767
1768fn ignore_lldb(config: &Config, line: &str) -> IgnoreDecision {
1769 if config.debugger != Some(Debugger::Lldb) {
1770 return IgnoreDecision::Continue;
1771 }
1772
1773 if let Some(actual_version) = config.lldb_version {
1774 if let Some(rest) = line.strip_prefix("min-lldb-version:").map(str::trim) {
1775 let min_version = rest.parse().unwrap_or_else(|e| {
1776 panic!("Unexpected format of LLDB version string: {}\n{:?}", rest, e);
1777 });
1778 if actual_version < min_version {
1781 return IgnoreDecision::Ignore {
1782 reason: format!("ignored when the LLDB version is {rest}"),
1783 };
1784 }
1785 }
1786 }
1787 IgnoreDecision::Continue
1788}
1789
1790fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
1791 if let Some(needed_components) =
1792 config.parse_name_value_directive(line, "needs-llvm-components")
1793 {
1794 let components: HashSet<_> = config.llvm_components.split_whitespace().collect();
1795 if let Some(missing_component) = needed_components
1796 .split_whitespace()
1797 .find(|needed_component| !components.contains(needed_component))
1798 {
1799 if env::var_os("COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS").is_some() {
1800 panic!(
1801 "missing LLVM component {}, and COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS is set: {}",
1802 missing_component, path
1803 );
1804 }
1805 return IgnoreDecision::Ignore {
1806 reason: format!("ignored when the {missing_component} LLVM component is missing"),
1807 };
1808 }
1809 }
1810 if let Some(actual_version) = &config.llvm_version {
1811 if let Some(version_string) = config.parse_name_value_directive(line, "min-llvm-version") {
1814 let min_version = extract_llvm_version(&version_string);
1815 if *actual_version < min_version {
1817 return IgnoreDecision::Ignore {
1818 reason: format!(
1819 "ignored when the LLVM version {actual_version} is older than {min_version}"
1820 ),
1821 };
1822 }
1823 } else if let Some(version_string) =
1824 config.parse_name_value_directive(line, "max-llvm-major-version")
1825 {
1826 let max_version = extract_llvm_version(&version_string);
1827 if actual_version.major > max_version.major {
1829 return IgnoreDecision::Ignore {
1830 reason: format!(
1831 "ignored when the LLVM version ({actual_version}) is newer than major\
1832 version {}",
1833 max_version.major
1834 ),
1835 };
1836 }
1837 } else if let Some(version_string) =
1838 config.parse_name_value_directive(line, "min-system-llvm-version")
1839 {
1840 let min_version = extract_llvm_version(&version_string);
1841 if config.system_llvm && *actual_version < min_version {
1844 return IgnoreDecision::Ignore {
1845 reason: format!(
1846 "ignored when the system LLVM version {actual_version} is older than {min_version}"
1847 ),
1848 };
1849 }
1850 } else if let Some(version_range) =
1851 config.parse_name_value_directive(line, "ignore-llvm-version")
1852 {
1853 let (v_min, v_max) =
1855 extract_version_range(&version_range, |s| Some(extract_llvm_version(s)))
1856 .unwrap_or_else(|| {
1857 panic!("couldn't parse version range: \"{version_range}\"");
1858 });
1859 if v_max < v_min {
1860 panic!("malformed LLVM version range where {v_max} < {v_min}")
1861 }
1862 if *actual_version >= v_min && *actual_version <= v_max {
1864 if v_min == v_max {
1865 return IgnoreDecision::Ignore {
1866 reason: format!("ignored when the LLVM version is {actual_version}"),
1867 };
1868 } else {
1869 return IgnoreDecision::Ignore {
1870 reason: format!(
1871 "ignored when the LLVM version is between {v_min} and {v_max}"
1872 ),
1873 };
1874 }
1875 }
1876 } else if let Some(version_string) =
1877 config.parse_name_value_directive(line, "exact-llvm-major-version")
1878 {
1879 let version = extract_llvm_version(&version_string);
1881 if actual_version.major != version.major {
1882 return IgnoreDecision::Ignore {
1883 reason: format!(
1884 "ignored when the actual LLVM major version is {}, but the test only targets major version {}",
1885 actual_version.major, version.major
1886 ),
1887 };
1888 }
1889 }
1890 }
1891 IgnoreDecision::Continue
1892}
1893
1894enum IgnoreDecision {
1895 Ignore { reason: String },
1896 Continue,
1897 Error { message: String },
1898}