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::{CodegenBackend, Config, Debugger, FailMode, PassMode, RunFailMode, TestMode};
13use crate::debuggers::{extract_cdb_version, extract_gdb_version};
14use crate::directives::auxiliary::{AuxProps, parse_and_update_aux};
15use crate::directives::directive_names::{
16 KNOWN_DIRECTIVE_NAMES, KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, KNOWN_JSONDOCCK_DIRECTIVE_NAMES,
17};
18use crate::directives::needs::CachedNeedsConditions;
19use crate::errors::ErrorKind;
20use crate::executor::{CollectedTestDesc, ShouldPanic};
21use crate::help;
22use crate::util::static_regex;
23
24pub(crate) mod auxiliary;
25mod cfg;
26mod directive_names;
27mod needs;
28#[cfg(test)]
29mod tests;
30
31pub struct DirectivesCache {
32 needs: CachedNeedsConditions,
33}
34
35impl DirectivesCache {
36 pub fn load(config: &Config) -> Self {
37 Self { needs: CachedNeedsConditions::load(config) }
38 }
39}
40
41#[derive(Default)]
44pub struct EarlyProps {
45 pub(crate) aux: AuxProps,
49 pub revisions: Vec<String>,
50}
51
52impl EarlyProps {
53 pub fn from_file(config: &Config, testfile: &Utf8Path) -> Self {
54 let file = File::open(testfile.as_std_path()).expect("open test file to parse earlyprops");
55 Self::from_reader(config, testfile, file)
56 }
57
58 pub fn from_reader<R: Read>(config: &Config, testfile: &Utf8Path, rdr: R) -> Self {
59 let mut props = EarlyProps::default();
60 let mut poisoned = false;
61 iter_directives(
62 config.mode,
63 &mut poisoned,
64 testfile,
65 rdr,
66 &mut |DirectiveLine { line_number, raw_directive: ln, .. }| {
67 parse_and_update_aux(config, ln, testfile, line_number, &mut props.aux);
68 config.parse_and_update_revisions(testfile, line_number, ln, &mut props.revisions);
69 },
70 );
71
72 if poisoned {
73 eprintln!("errors encountered during EarlyProps parsing: {}", testfile);
74 panic!("errors encountered during EarlyProps parsing");
75 }
76
77 props
78 }
79}
80
81#[derive(Clone, Debug)]
82pub struct TestProps {
83 pub error_patterns: Vec<String>,
85 pub regex_error_patterns: Vec<String>,
87 pub compile_flags: Vec<String>,
89 pub run_flags: Vec<String>,
91 pub doc_flags: Vec<String>,
93 pub pp_exact: Option<Utf8PathBuf>,
96 pub(crate) aux: AuxProps,
98 pub rustc_env: Vec<(String, String)>,
100 pub unset_rustc_env: Vec<String>,
103 pub exec_env: Vec<(String, String)>,
105 pub unset_exec_env: Vec<String>,
108 pub build_aux_docs: bool,
110 pub unique_doc_out_dir: bool,
113 pub force_host: bool,
115 pub check_stdout: bool,
117 pub check_run_results: bool,
119 pub dont_check_compiler_stdout: bool,
121 pub dont_check_compiler_stderr: bool,
123 pub no_prefer_dynamic: bool,
129 pub pretty_mode: String,
131 pub pretty_compare_only: bool,
133 pub forbid_output: Vec<String>,
135 pub revisions: Vec<String>,
137 pub incremental_dir: Option<Utf8PathBuf>,
142 pub incremental: bool,
157 pub known_bug: bool,
163 pass_mode: Option<PassMode>,
165 ignore_pass: bool,
167 pub fail_mode: Option<FailMode>,
169 pub check_test_line_numbers_match: bool,
171 pub normalize_stdout: Vec<(String, String)>,
173 pub normalize_stderr: Vec<(String, String)>,
174 pub failure_status: Option<i32>,
175 pub dont_check_failure_status: bool,
177 pub run_rustfix: bool,
180 pub rustfix_only_machine_applicable: bool,
182 pub assembly_output: Option<String>,
183 pub should_ice: bool,
185 pub stderr_per_bitwidth: bool,
187 pub mir_unit_test: Option<String>,
189 pub remap_src_base: bool,
192 pub llvm_cov_flags: Vec<String>,
195 pub filecheck_flags: Vec<String>,
197 pub no_auto_check_cfg: bool,
199 pub has_enzyme: bool,
201 pub add_core_stubs: bool,
204 pub dont_require_annotations: HashSet<ErrorKind>,
206 pub disable_gdb_pretty_printers: bool,
208}
209
210mod directives {
211 pub const ERROR_PATTERN: &'static str = "error-pattern";
212 pub const REGEX_ERROR_PATTERN: &'static str = "regex-error-pattern";
213 pub const COMPILE_FLAGS: &'static str = "compile-flags";
214 pub const RUN_FLAGS: &'static str = "run-flags";
215 pub const DOC_FLAGS: &'static str = "doc-flags";
216 pub const SHOULD_ICE: &'static str = "should-ice";
217 pub const BUILD_AUX_DOCS: &'static str = "build-aux-docs";
218 pub const UNIQUE_DOC_OUT_DIR: &'static str = "unique-doc-out-dir";
219 pub const FORCE_HOST: &'static str = "force-host";
220 pub const CHECK_STDOUT: &'static str = "check-stdout";
221 pub const CHECK_RUN_RESULTS: &'static str = "check-run-results";
222 pub const DONT_CHECK_COMPILER_STDOUT: &'static str = "dont-check-compiler-stdout";
223 pub const DONT_CHECK_COMPILER_STDERR: &'static str = "dont-check-compiler-stderr";
224 pub const DONT_REQUIRE_ANNOTATIONS: &'static str = "dont-require-annotations";
225 pub const NO_PREFER_DYNAMIC: &'static str = "no-prefer-dynamic";
226 pub const PRETTY_MODE: &'static str = "pretty-mode";
227 pub const PRETTY_COMPARE_ONLY: &'static str = "pretty-compare-only";
228 pub const AUX_BIN: &'static str = "aux-bin";
229 pub const AUX_BUILD: &'static str = "aux-build";
230 pub const AUX_CRATE: &'static str = "aux-crate";
231 pub const PROC_MACRO: &'static str = "proc-macro";
232 pub const AUX_CODEGEN_BACKEND: &'static str = "aux-codegen-backend";
233 pub const EXEC_ENV: &'static str = "exec-env";
234 pub const RUSTC_ENV: &'static str = "rustc-env";
235 pub const UNSET_EXEC_ENV: &'static str = "unset-exec-env";
236 pub const UNSET_RUSTC_ENV: &'static str = "unset-rustc-env";
237 pub const FORBID_OUTPUT: &'static str = "forbid-output";
238 pub const CHECK_TEST_LINE_NUMBERS_MATCH: &'static str = "check-test-line-numbers-match";
239 pub const IGNORE_PASS: &'static str = "ignore-pass";
240 pub const FAILURE_STATUS: &'static str = "failure-status";
241 pub const DONT_CHECK_FAILURE_STATUS: &'static str = "dont-check-failure-status";
242 pub const RUN_RUSTFIX: &'static str = "run-rustfix";
243 pub const RUSTFIX_ONLY_MACHINE_APPLICABLE: &'static str = "rustfix-only-machine-applicable";
244 pub const ASSEMBLY_OUTPUT: &'static str = "assembly-output";
245 pub const STDERR_PER_BITWIDTH: &'static str = "stderr-per-bitwidth";
246 pub const INCREMENTAL: &'static str = "incremental";
247 pub const KNOWN_BUG: &'static str = "known-bug";
248 pub const TEST_MIR_PASS: &'static str = "test-mir-pass";
249 pub const REMAP_SRC_BASE: &'static str = "remap-src-base";
250 pub const LLVM_COV_FLAGS: &'static str = "llvm-cov-flags";
251 pub const FILECHECK_FLAGS: &'static str = "filecheck-flags";
252 pub const NO_AUTO_CHECK_CFG: &'static str = "no-auto-check-cfg";
253 pub const ADD_CORE_STUBS: &'static str = "add-core-stubs";
254 pub const INCORRECT_COMPILER_FLAGS: &'static str = "compiler-flags";
256 pub const DISABLE_GDB_PRETTY_PRINTERS: &'static str = "disable-gdb-pretty-printers";
257}
258
259impl TestProps {
260 pub fn new() -> Self {
261 TestProps {
262 error_patterns: vec![],
263 regex_error_patterns: vec![],
264 compile_flags: vec![],
265 run_flags: vec![],
266 doc_flags: vec![],
267 pp_exact: None,
268 aux: Default::default(),
269 revisions: vec![],
270 rustc_env: vec![
271 ("RUSTC_ICE".to_string(), "0".to_string()),
272 ("RUST_BACKTRACE".to_string(), "short".to_string()),
273 ],
274 unset_rustc_env: vec![("RUSTC_LOG_COLOR".to_string())],
275 exec_env: vec![],
276 unset_exec_env: vec![],
277 build_aux_docs: false,
278 unique_doc_out_dir: false,
279 force_host: false,
280 check_stdout: false,
281 check_run_results: false,
282 dont_check_compiler_stdout: false,
283 dont_check_compiler_stderr: false,
284 no_prefer_dynamic: false,
285 pretty_mode: "normal".to_string(),
286 pretty_compare_only: false,
287 forbid_output: vec![],
288 incremental_dir: None,
289 incremental: false,
290 known_bug: false,
291 pass_mode: None,
292 fail_mode: None,
293 ignore_pass: false,
294 check_test_line_numbers_match: false,
295 normalize_stdout: vec![],
296 normalize_stderr: vec![],
297 failure_status: None,
298 dont_check_failure_status: false,
299 run_rustfix: false,
300 rustfix_only_machine_applicable: false,
301 assembly_output: None,
302 should_ice: false,
303 stderr_per_bitwidth: false,
304 mir_unit_test: None,
305 remap_src_base: false,
306 llvm_cov_flags: vec![],
307 filecheck_flags: vec![],
308 no_auto_check_cfg: false,
309 has_enzyme: false,
310 add_core_stubs: false,
311 dont_require_annotations: Default::default(),
312 disable_gdb_pretty_printers: false,
313 }
314 }
315
316 pub fn from_aux_file(
317 &self,
318 testfile: &Utf8Path,
319 revision: Option<&str>,
320 config: &Config,
321 ) -> Self {
322 let mut props = TestProps::new();
323
324 props.incremental_dir = self.incremental_dir.clone();
326 props.ignore_pass = true;
327 props.load_from(testfile, revision, config);
328
329 props
330 }
331
332 pub fn from_file(testfile: &Utf8Path, revision: Option<&str>, config: &Config) -> Self {
333 let mut props = TestProps::new();
334 props.load_from(testfile, revision, config);
335 props.exec_env.push(("RUSTC".to_string(), config.rustc_path.to_string()));
336
337 match (props.pass_mode, props.fail_mode) {
338 (None, None) if config.mode == TestMode::Ui => props.fail_mode = Some(FailMode::Check),
339 (Some(_), Some(_)) => panic!("cannot use a *-fail and *-pass mode together"),
340 _ => {}
341 }
342
343 props
344 }
345
346 fn load_from(&mut self, testfile: &Utf8Path, test_revision: Option<&str>, config: &Config) {
351 let mut has_edition = false;
352 if !testfile.is_dir() {
353 let file = File::open(testfile.as_std_path()).unwrap();
354
355 let mut poisoned = false;
356
357 iter_directives(
358 config.mode,
359 &mut poisoned,
360 testfile,
361 file,
362 &mut |directive @ DirectiveLine { line_number, raw_directive: ln, .. }| {
363 if !directive.applies_to_test_revision(test_revision) {
364 return;
365 }
366
367 use directives::*;
368
369 config.push_name_value_directive(
370 ln,
371 ERROR_PATTERN,
372 testfile,
373 line_number,
374 &mut self.error_patterns,
375 |r| r,
376 );
377 config.push_name_value_directive(
378 ln,
379 REGEX_ERROR_PATTERN,
380 testfile,
381 line_number,
382 &mut self.regex_error_patterns,
383 |r| r,
384 );
385
386 config.push_name_value_directive(
387 ln,
388 DOC_FLAGS,
389 testfile,
390 line_number,
391 &mut self.doc_flags,
392 |r| r,
393 );
394
395 fn split_flags(flags: &str) -> Vec<String> {
396 flags
399 .split('\'')
400 .enumerate()
401 .flat_map(|(i, f)| {
402 if i % 2 == 1 { vec![f] } else { f.split_whitespace().collect() }
403 })
404 .map(move |s| s.to_owned())
405 .collect::<Vec<_>>()
406 }
407
408 if let Some(flags) =
409 config.parse_name_value_directive(ln, COMPILE_FLAGS, testfile, line_number)
410 {
411 let flags = split_flags(&flags);
412 for flag in &flags {
413 if flag == "--edition" || flag.starts_with("--edition=") {
414 panic!("you must use `//@ edition` to configure the edition");
415 }
416 }
417 self.compile_flags.extend(flags);
418 }
419 if config
420 .parse_name_value_directive(
421 ln,
422 INCORRECT_COMPILER_FLAGS,
423 testfile,
424 line_number,
425 )
426 .is_some()
427 {
428 panic!("`compiler-flags` directive should be spelled `compile-flags`");
429 }
430
431 if let Some(edition) = config.parse_edition(ln, testfile, line_number) {
432 self.compile_flags.insert(0, format!("--edition={}", edition.trim()));
435 has_edition = true;
436 }
437
438 config.parse_and_update_revisions(
439 testfile,
440 line_number,
441 ln,
442 &mut self.revisions,
443 );
444
445 if let Some(flags) =
446 config.parse_name_value_directive(ln, RUN_FLAGS, testfile, line_number)
447 {
448 self.run_flags.extend(split_flags(&flags));
449 }
450
451 if self.pp_exact.is_none() {
452 self.pp_exact = config.parse_pp_exact(ln, testfile, line_number);
453 }
454
455 config.set_name_directive(ln, SHOULD_ICE, &mut self.should_ice);
456 config.set_name_directive(ln, BUILD_AUX_DOCS, &mut self.build_aux_docs);
457 config.set_name_directive(ln, UNIQUE_DOC_OUT_DIR, &mut self.unique_doc_out_dir);
458
459 config.set_name_directive(ln, FORCE_HOST, &mut self.force_host);
460 config.set_name_directive(ln, CHECK_STDOUT, &mut self.check_stdout);
461 config.set_name_directive(ln, CHECK_RUN_RESULTS, &mut self.check_run_results);
462 config.set_name_directive(
463 ln,
464 DONT_CHECK_COMPILER_STDOUT,
465 &mut self.dont_check_compiler_stdout,
466 );
467 config.set_name_directive(
468 ln,
469 DONT_CHECK_COMPILER_STDERR,
470 &mut self.dont_check_compiler_stderr,
471 );
472 config.set_name_directive(ln, NO_PREFER_DYNAMIC, &mut self.no_prefer_dynamic);
473
474 if let Some(m) =
475 config.parse_name_value_directive(ln, PRETTY_MODE, testfile, line_number)
476 {
477 self.pretty_mode = m;
478 }
479
480 config.set_name_directive(
481 ln,
482 PRETTY_COMPARE_ONLY,
483 &mut self.pretty_compare_only,
484 );
485
486 parse_and_update_aux(config, ln, testfile, line_number, &mut self.aux);
488
489 config.push_name_value_directive(
490 ln,
491 EXEC_ENV,
492 testfile,
493 line_number,
494 &mut self.exec_env,
495 Config::parse_env,
496 );
497 config.push_name_value_directive(
498 ln,
499 UNSET_EXEC_ENV,
500 testfile,
501 line_number,
502 &mut self.unset_exec_env,
503 |r| r.trim().to_owned(),
504 );
505 config.push_name_value_directive(
506 ln,
507 RUSTC_ENV,
508 testfile,
509 line_number,
510 &mut self.rustc_env,
511 Config::parse_env,
512 );
513 config.push_name_value_directive(
514 ln,
515 UNSET_RUSTC_ENV,
516 testfile,
517 line_number,
518 &mut self.unset_rustc_env,
519 |r| r.trim().to_owned(),
520 );
521 config.push_name_value_directive(
522 ln,
523 FORBID_OUTPUT,
524 testfile,
525 line_number,
526 &mut self.forbid_output,
527 |r| r,
528 );
529 config.set_name_directive(
530 ln,
531 CHECK_TEST_LINE_NUMBERS_MATCH,
532 &mut self.check_test_line_numbers_match,
533 );
534
535 self.update_pass_mode(ln, test_revision, config);
536 self.update_fail_mode(ln, config);
537
538 config.set_name_directive(ln, IGNORE_PASS, &mut self.ignore_pass);
539
540 if let Some(NormalizeRule { kind, regex, replacement }) =
541 config.parse_custom_normalization(ln)
542 {
543 let rule_tuple = (regex, replacement);
544 match kind {
545 NormalizeKind::Stdout => self.normalize_stdout.push(rule_tuple),
546 NormalizeKind::Stderr => self.normalize_stderr.push(rule_tuple),
547 NormalizeKind::Stderr32bit => {
548 if config.target_cfg().pointer_width == 32 {
549 self.normalize_stderr.push(rule_tuple);
550 }
551 }
552 NormalizeKind::Stderr64bit => {
553 if config.target_cfg().pointer_width == 64 {
554 self.normalize_stderr.push(rule_tuple);
555 }
556 }
557 }
558 }
559
560 if let Some(code) = config
561 .parse_name_value_directive(ln, FAILURE_STATUS, testfile, line_number)
562 .and_then(|code| code.trim().parse::<i32>().ok())
563 {
564 self.failure_status = Some(code);
565 }
566
567 config.set_name_directive(
568 ln,
569 DONT_CHECK_FAILURE_STATUS,
570 &mut self.dont_check_failure_status,
571 );
572
573 config.set_name_directive(ln, RUN_RUSTFIX, &mut self.run_rustfix);
574 config.set_name_directive(
575 ln,
576 RUSTFIX_ONLY_MACHINE_APPLICABLE,
577 &mut self.rustfix_only_machine_applicable,
578 );
579 config.set_name_value_directive(
580 ln,
581 ASSEMBLY_OUTPUT,
582 testfile,
583 line_number,
584 &mut self.assembly_output,
585 |r| r.trim().to_string(),
586 );
587 config.set_name_directive(
588 ln,
589 STDERR_PER_BITWIDTH,
590 &mut self.stderr_per_bitwidth,
591 );
592 config.set_name_directive(ln, INCREMENTAL, &mut self.incremental);
593
594 if let Some(known_bug) =
597 config.parse_name_value_directive(ln, KNOWN_BUG, testfile, line_number)
598 {
599 let known_bug = known_bug.trim();
600 if known_bug == "unknown"
601 || known_bug.split(',').all(|issue_ref| {
602 issue_ref
603 .trim()
604 .split_once('#')
605 .filter(|(_, number)| {
606 number.chars().all(|digit| digit.is_numeric())
607 })
608 .is_some()
609 })
610 {
611 self.known_bug = true;
612 } else {
613 panic!(
614 "Invalid known-bug value: {known_bug}\nIt requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`."
615 );
616 }
617 } else if config.parse_name_directive(ln, KNOWN_BUG) {
618 panic!(
619 "Invalid known-bug attribute, requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`."
620 );
621 }
622
623 config.set_name_value_directive(
624 ln,
625 TEST_MIR_PASS,
626 testfile,
627 line_number,
628 &mut self.mir_unit_test,
629 |s| s.trim().to_string(),
630 );
631 config.set_name_directive(ln, REMAP_SRC_BASE, &mut self.remap_src_base);
632
633 if let Some(flags) =
634 config.parse_name_value_directive(ln, LLVM_COV_FLAGS, testfile, line_number)
635 {
636 self.llvm_cov_flags.extend(split_flags(&flags));
637 }
638
639 if let Some(flags) = config.parse_name_value_directive(
640 ln,
641 FILECHECK_FLAGS,
642 testfile,
643 line_number,
644 ) {
645 self.filecheck_flags.extend(split_flags(&flags));
646 }
647
648 config.set_name_directive(ln, NO_AUTO_CHECK_CFG, &mut self.no_auto_check_cfg);
649
650 self.update_add_core_stubs(ln, config);
651
652 if let Some(err_kind) = config.parse_name_value_directive(
653 ln,
654 DONT_REQUIRE_ANNOTATIONS,
655 testfile,
656 line_number,
657 ) {
658 self.dont_require_annotations
659 .insert(ErrorKind::expect_from_user_str(err_kind.trim()));
660 }
661
662 config.set_name_directive(
663 ln,
664 DISABLE_GDB_PRETTY_PRINTERS,
665 &mut self.disable_gdb_pretty_printers,
666 );
667 },
668 );
669
670 if poisoned {
671 eprintln!("errors encountered during TestProps parsing: {}", testfile);
672 panic!("errors encountered during TestProps parsing");
673 }
674 }
675
676 if self.should_ice {
677 self.failure_status = Some(101);
678 }
679
680 if config.mode == TestMode::Incremental {
681 self.incremental = true;
682 }
683
684 if config.mode == TestMode::Crashes {
685 self.rustc_env = vec![
689 ("RUST_BACKTRACE".to_string(), "0".to_string()),
690 ("RUSTC_ICE".to_string(), "0".to_string()),
691 ];
692 }
693
694 for key in &["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] {
695 if let Ok(val) = env::var(key) {
696 if !self.exec_env.iter().any(|&(ref x, _)| x == key) {
697 self.exec_env.push(((*key).to_owned(), val))
698 }
699 }
700 }
701
702 if let (Some(edition), false) = (&config.edition, has_edition) {
703 self.compile_flags.insert(0, format!("--edition={}", edition));
706 }
707 }
708
709 fn update_fail_mode(&mut self, ln: &str, config: &Config) {
710 let check_ui = |mode: &str| {
711 if config.mode != TestMode::Ui && config.mode != TestMode::Crashes {
713 panic!("`{}-fail` directive is only supported in UI tests", mode);
714 }
715 };
716 if config.mode == TestMode::Ui && config.parse_name_directive(ln, "compile-fail") {
717 panic!("`compile-fail` directive is useless in UI tests");
718 }
719 let fail_mode = if config.parse_name_directive(ln, "check-fail") {
720 check_ui("check");
721 Some(FailMode::Check)
722 } else if config.parse_name_directive(ln, "build-fail") {
723 check_ui("build");
724 Some(FailMode::Build)
725 } else if config.parse_name_directive(ln, "run-fail") {
726 check_ui("run");
727 Some(FailMode::Run(RunFailMode::Fail))
728 } else if config.parse_name_directive(ln, "run-crash") {
729 check_ui("run");
730 Some(FailMode::Run(RunFailMode::Crash))
731 } else if config.parse_name_directive(ln, "run-fail-or-crash") {
732 check_ui("run");
733 Some(FailMode::Run(RunFailMode::FailOrCrash))
734 } else {
735 None
736 };
737 match (self.fail_mode, fail_mode) {
738 (None, Some(_)) => self.fail_mode = fail_mode,
739 (Some(_), Some(_)) => panic!("multiple `*-fail` directives in a single test"),
740 (_, None) => {}
741 }
742 }
743
744 fn update_pass_mode(&mut self, ln: &str, revision: Option<&str>, config: &Config) {
745 let check_no_run = |s| match (config.mode, s) {
746 (TestMode::Ui, _) => (),
747 (TestMode::Crashes, _) => (),
748 (TestMode::Codegen, "build-pass") => (),
749 (TestMode::Incremental, _) => {
750 if revision.is_some() && !self.revisions.iter().all(|r| r.starts_with("cfail")) {
751 panic!("`{s}` directive is only supported in `cfail` incremental tests")
752 }
753 }
754 (mode, _) => panic!("`{s}` directive is not supported in `{mode}` tests"),
755 };
756 let pass_mode = if config.parse_name_directive(ln, "check-pass") {
757 check_no_run("check-pass");
758 Some(PassMode::Check)
759 } else if config.parse_name_directive(ln, "build-pass") {
760 check_no_run("build-pass");
761 Some(PassMode::Build)
762 } else if config.parse_name_directive(ln, "run-pass") {
763 check_no_run("run-pass");
764 Some(PassMode::Run)
765 } else {
766 None
767 };
768 match (self.pass_mode, pass_mode) {
769 (None, Some(_)) => self.pass_mode = pass_mode,
770 (Some(_), Some(_)) => panic!("multiple `*-pass` directives in a single test"),
771 (_, None) => {}
772 }
773 }
774
775 pub fn pass_mode(&self, config: &Config) -> Option<PassMode> {
776 if !self.ignore_pass && self.fail_mode.is_none() {
777 if let mode @ Some(_) = config.force_pass_mode {
778 return mode;
779 }
780 }
781 self.pass_mode
782 }
783
784 pub fn local_pass_mode(&self) -> Option<PassMode> {
786 self.pass_mode
787 }
788
789 pub fn update_add_core_stubs(&mut self, ln: &str, config: &Config) {
790 let add_core_stubs = config.parse_name_directive(ln, directives::ADD_CORE_STUBS);
791 if add_core_stubs {
792 if !matches!(config.mode, TestMode::Ui | TestMode::Codegen | TestMode::Assembly) {
793 panic!(
794 "`add-core-stubs` is currently only supported for ui, codegen and assembly test modes"
795 );
796 }
797
798 if self.local_pass_mode().is_some_and(|pm| pm == PassMode::Run) {
801 panic!("`add-core-stubs` cannot be used to run the test binary");
804 }
805
806 self.add_core_stubs = add_core_stubs;
807 }
808 }
809}
810
811fn line_directive<'line>(
814 line_number: usize,
815 original_line: &'line str,
816) -> Option<DirectiveLine<'line>> {
817 let after_comment =
819 original_line.trim_start().strip_prefix(COMPILETEST_DIRECTIVE_PREFIX)?.trim_start();
820
821 let revision;
822 let raw_directive;
823
824 if let Some(after_open_bracket) = after_comment.strip_prefix('[') {
825 let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else {
827 panic!(
828 "malformed condition directive: expected `{COMPILETEST_DIRECTIVE_PREFIX}[foo]`, found `{original_line}`"
829 )
830 };
831
832 revision = Some(line_revision);
833 raw_directive = after_close_bracket.trim_start();
834 } else {
835 revision = None;
836 raw_directive = after_comment;
837 };
838
839 Some(DirectiveLine { line_number, revision, raw_directive })
840}
841
842struct DirectiveLine<'ln> {
856 line_number: usize,
857 revision: Option<&'ln str>,
861 raw_directive: &'ln str,
867}
868
869impl<'ln> DirectiveLine<'ln> {
870 fn applies_to_test_revision(&self, test_revision: Option<&str>) -> bool {
871 self.revision.is_none() || self.revision == test_revision
872 }
873}
874
875pub(crate) struct CheckDirectiveResult<'ln> {
876 is_known_directive: bool,
877 trailing_directive: Option<&'ln str>,
878}
879
880pub(crate) fn check_directive<'a>(
881 directive_ln: &'a str,
882 mode: TestMode,
883) -> CheckDirectiveResult<'a> {
884 let (directive_name, post) = directive_ln.split_once([':', ' ']).unwrap_or((directive_ln, ""));
885
886 let is_known_directive = KNOWN_DIRECTIVE_NAMES.contains(&directive_name)
887 || match mode {
888 TestMode::Rustdoc => KNOWN_HTMLDOCCK_DIRECTIVE_NAMES.contains(&directive_name),
889 TestMode::RustdocJson => KNOWN_JSONDOCCK_DIRECTIVE_NAMES.contains(&directive_name),
890 _ => false,
891 };
892
893 let trailing = post.trim().split_once(' ').map(|(pre, _)| pre).unwrap_or(post);
894 let trailing_directive = {
895 directive_ln.get(directive_name.len()..).is_some_and(|s| s.starts_with(' '))
897 && KNOWN_DIRECTIVE_NAMES.contains(&trailing)
899 }
900 .then_some(trailing);
901
902 CheckDirectiveResult { is_known_directive, trailing_directive }
903}
904
905const COMPILETEST_DIRECTIVE_PREFIX: &str = "//@";
906
907fn iter_directives(
908 mode: TestMode,
909 poisoned: &mut bool,
910 testfile: &Utf8Path,
911 rdr: impl Read,
912 it: &mut dyn FnMut(DirectiveLine<'_>),
913) {
914 if testfile.is_dir() {
915 return;
916 }
917
918 if mode == TestMode::CoverageRun {
923 let extra_directives: &[&str] = &[
924 "needs-profiler-runtime",
925 "ignore-cross-compile",
929 ];
930 for raw_directive in extra_directives {
932 it(DirectiveLine { line_number: 0, revision: None, raw_directive });
933 }
934 }
935
936 let mut rdr = BufReader::with_capacity(1024, rdr);
937 let mut ln = String::new();
938 let mut line_number = 0;
939
940 loop {
941 line_number += 1;
942 ln.clear();
943 if rdr.read_line(&mut ln).unwrap() == 0 {
944 break;
945 }
946 let ln = ln.trim();
947
948 let Some(directive_line) = line_directive(line_number, ln) else {
949 continue;
950 };
951
952 if testfile.extension() == Some("rs") {
954 let CheckDirectiveResult { is_known_directive, trailing_directive } =
955 check_directive(directive_line.raw_directive, mode);
956
957 if !is_known_directive {
958 *poisoned = true;
959
960 error!(
961 "{testfile}:{line_number}: detected unknown compiletest test directive `{}`",
962 directive_line.raw_directive,
963 );
964
965 return;
966 }
967
968 if let Some(trailing_directive) = &trailing_directive {
969 *poisoned = true;
970
971 error!(
972 "{testfile}:{line_number}: detected trailing compiletest test directive `{}`",
973 trailing_directive,
974 );
975 help!("put the trailing directive in its own line: `//@ {}`", trailing_directive);
976
977 return;
978 }
979 }
980
981 it(directive_line);
982 }
983}
984
985impl Config {
986 fn parse_and_update_revisions(
987 &self,
988 testfile: &Utf8Path,
989 line_number: usize,
990 line: &str,
991 existing: &mut Vec<String>,
992 ) {
993 const FORBIDDEN_REVISION_NAMES: [&str; 2] = [
994 "true", "false",
998 ];
999
1000 const FILECHECK_FORBIDDEN_REVISION_NAMES: [&str; 9] =
1001 ["CHECK", "COM", "NEXT", "SAME", "EMPTY", "NOT", "COUNT", "DAG", "LABEL"];
1002
1003 if let Some(raw) = self.parse_name_value_directive(line, "revisions", testfile, line_number)
1004 {
1005 if self.mode == TestMode::RunMake {
1006 panic!("`run-make` tests do not support revisions: {}", testfile);
1007 }
1008
1009 let mut duplicates: HashSet<_> = existing.iter().cloned().collect();
1010 for revision in raw.split_whitespace() {
1011 if !duplicates.insert(revision.to_string()) {
1012 panic!("duplicate revision: `{}` in line `{}`: {}", revision, raw, testfile);
1013 }
1014
1015 if FORBIDDEN_REVISION_NAMES.contains(&revision) {
1016 panic!(
1017 "revision name `{revision}` is not permitted: `{}` in line `{}`: {}",
1018 revision, raw, testfile
1019 );
1020 }
1021
1022 if matches!(self.mode, TestMode::Assembly | TestMode::Codegen | TestMode::MirOpt)
1023 && FILECHECK_FORBIDDEN_REVISION_NAMES.contains(&revision)
1024 {
1025 panic!(
1026 "revision name `{revision}` is not permitted in a test suite that uses \
1027 `FileCheck` annotations as it is confusing when used as custom `FileCheck` \
1028 prefix: `{revision}` in line `{}`: {}",
1029 raw, testfile
1030 );
1031 }
1032
1033 existing.push(revision.to_string());
1034 }
1035 }
1036 }
1037
1038 fn parse_env(nv: String) -> (String, String) {
1039 let (name, value) = nv.split_once('=').unwrap_or((&nv, ""));
1043 let name = name.trim();
1046 (name.to_owned(), value.to_owned())
1047 }
1048
1049 fn parse_pp_exact(
1050 &self,
1051 line: &str,
1052 testfile: &Utf8Path,
1053 line_number: usize,
1054 ) -> Option<Utf8PathBuf> {
1055 if let Some(s) = self.parse_name_value_directive(line, "pp-exact", testfile, line_number) {
1056 Some(Utf8PathBuf::from(&s))
1057 } else if self.parse_name_directive(line, "pp-exact") {
1058 testfile.file_name().map(Utf8PathBuf::from)
1059 } else {
1060 None
1061 }
1062 }
1063
1064 fn parse_custom_normalization(&self, raw_directive: &str) -> Option<NormalizeRule> {
1065 let (directive_name, raw_value) = raw_directive.split_once(':')?;
1068
1069 let kind = match directive_name {
1070 "normalize-stdout" => NormalizeKind::Stdout,
1071 "normalize-stderr" => NormalizeKind::Stderr,
1072 "normalize-stderr-32bit" => NormalizeKind::Stderr32bit,
1073 "normalize-stderr-64bit" => NormalizeKind::Stderr64bit,
1074 _ => return None,
1075 };
1076
1077 let Some((regex, replacement)) = parse_normalize_rule(raw_value) else {
1078 error!("couldn't parse custom normalization rule: `{raw_directive}`");
1079 help!("expected syntax is: `{directive_name}: \"REGEX\" -> \"REPLACEMENT\"`");
1080 panic!("invalid normalization rule detected");
1081 };
1082 Some(NormalizeRule { kind, regex, replacement })
1083 }
1084
1085 fn parse_name_directive(&self, line: &str, directive: &str) -> bool {
1086 line.starts_with(directive)
1089 && matches!(line.as_bytes().get(directive.len()), None | Some(&b' ') | Some(&b':'))
1090 }
1091
1092 fn parse_negative_name_directive(&self, line: &str, directive: &str) -> bool {
1093 line.starts_with("no-") && self.parse_name_directive(&line[3..], directive)
1094 }
1095
1096 pub fn parse_name_value_directive(
1097 &self,
1098 line: &str,
1099 directive: &str,
1100 testfile: &Utf8Path,
1101 line_number: usize,
1102 ) -> Option<String> {
1103 let colon = directive.len();
1104 if line.starts_with(directive) && line.as_bytes().get(colon) == Some(&b':') {
1105 let value = line[(colon + 1)..].to_owned();
1106 debug!("{}: {}", directive, value);
1107 let value = expand_variables(value, self);
1108 if value.is_empty() {
1109 error!("{testfile}:{line_number}: empty value for directive `{directive}`");
1110 help!("expected syntax is: `{directive}: value`");
1111 panic!("empty directive value detected");
1112 }
1113 Some(value)
1114 } else {
1115 None
1116 }
1117 }
1118
1119 fn parse_edition(&self, line: &str, testfile: &Utf8Path, line_number: usize) -> Option<String> {
1120 self.parse_name_value_directive(line, "edition", testfile, line_number)
1121 }
1122
1123 fn set_name_directive(&self, line: &str, directive: &str, value: &mut bool) {
1124 match value {
1125 true => {
1126 if self.parse_negative_name_directive(line, directive) {
1127 *value = false;
1128 }
1129 }
1130 false => {
1131 if self.parse_name_directive(line, directive) {
1132 *value = true;
1133 }
1134 }
1135 }
1136 }
1137
1138 fn set_name_value_directive<T>(
1139 &self,
1140 line: &str,
1141 directive: &str,
1142 testfile: &Utf8Path,
1143 line_number: usize,
1144 value: &mut Option<T>,
1145 parse: impl FnOnce(String) -> T,
1146 ) {
1147 if value.is_none() {
1148 *value =
1149 self.parse_name_value_directive(line, directive, testfile, line_number).map(parse);
1150 }
1151 }
1152
1153 fn push_name_value_directive<T>(
1154 &self,
1155 line: &str,
1156 directive: &str,
1157 testfile: &Utf8Path,
1158 line_number: usize,
1159 values: &mut Vec<T>,
1160 parse: impl FnOnce(String) -> T,
1161 ) {
1162 if let Some(value) =
1163 self.parse_name_value_directive(line, directive, testfile, line_number).map(parse)
1164 {
1165 values.push(value);
1166 }
1167 }
1168}
1169
1170fn expand_variables(mut value: String, config: &Config) -> String {
1172 const CWD: &str = "{{cwd}}";
1173 const SRC_BASE: &str = "{{src-base}}";
1174 const TEST_SUITE_BUILD_BASE: &str = "{{build-base}}";
1175 const RUST_SRC_BASE: &str = "{{rust-src-base}}";
1176 const SYSROOT_BASE: &str = "{{sysroot-base}}";
1177 const TARGET_LINKER: &str = "{{target-linker}}";
1178 const TARGET: &str = "{{target}}";
1179
1180 if value.contains(CWD) {
1181 let cwd = env::current_dir().unwrap();
1182 value = value.replace(CWD, &cwd.to_str().unwrap());
1183 }
1184
1185 if value.contains(SRC_BASE) {
1186 value = value.replace(SRC_BASE, &config.src_test_suite_root.as_str());
1187 }
1188
1189 if value.contains(TEST_SUITE_BUILD_BASE) {
1190 value = value.replace(TEST_SUITE_BUILD_BASE, &config.build_test_suite_root.as_str());
1191 }
1192
1193 if value.contains(SYSROOT_BASE) {
1194 value = value.replace(SYSROOT_BASE, &config.sysroot_base.as_str());
1195 }
1196
1197 if value.contains(TARGET_LINKER) {
1198 value = value.replace(TARGET_LINKER, config.target_linker.as_deref().unwrap_or(""));
1199 }
1200
1201 if value.contains(TARGET) {
1202 value = value.replace(TARGET, &config.target);
1203 }
1204
1205 if value.contains(RUST_SRC_BASE) {
1206 let src_base = config.sysroot_base.join("lib/rustlib/src/rust");
1207 src_base.try_exists().expect(&*format!("{} should exists", src_base));
1208 let src_base = src_base.read_link_utf8().unwrap_or(src_base);
1209 value = value.replace(RUST_SRC_BASE, &src_base.as_str());
1210 }
1211
1212 value
1213}
1214
1215struct NormalizeRule {
1216 kind: NormalizeKind,
1217 regex: String,
1218 replacement: String,
1219}
1220
1221enum NormalizeKind {
1222 Stdout,
1223 Stderr,
1224 Stderr32bit,
1225 Stderr64bit,
1226}
1227
1228fn parse_normalize_rule(raw_value: &str) -> Option<(String, String)> {
1233 let captures = static_regex!(
1235 r#"(?x) # (verbose mode regex)
1236 ^
1237 \s* # (leading whitespace)
1238 "(?<regex>[^"]*)" # "REGEX"
1239 \s+->\s+ # ->
1240 "(?<replacement>[^"]*)" # "REPLACEMENT"
1241 $
1242 "#
1243 )
1244 .captures(raw_value)?;
1245 let regex = captures["regex"].to_owned();
1246 let replacement = captures["replacement"].to_owned();
1247 let replacement = replacement.replace("\\n", "\n");
1251 Some((regex, replacement))
1252}
1253
1254pub fn extract_llvm_version(version: &str) -> Version {
1264 let version = version.trim();
1267 let uninterested = |c: char| !c.is_ascii_digit() && c != '.';
1268 let version_without_suffix = match version.split_once(uninterested) {
1269 Some((prefix, _suffix)) => prefix,
1270 None => version,
1271 };
1272
1273 let components: Vec<u64> = version_without_suffix
1274 .split('.')
1275 .map(|s| s.parse().expect("llvm version component should consist of only digits"))
1276 .collect();
1277
1278 match &components[..] {
1279 [major] => Version::new(*major, 0, 0),
1280 [major, minor] => Version::new(*major, *minor, 0),
1281 [major, minor, patch] => Version::new(*major, *minor, *patch),
1282 _ => panic!("malformed llvm version string, expected only 1-3 components: {version}"),
1283 }
1284}
1285
1286pub fn extract_llvm_version_from_binary(binary_path: &str) -> Option<Version> {
1287 let output = Command::new(binary_path).arg("--version").output().ok()?;
1288 if !output.status.success() {
1289 return None;
1290 }
1291 let version = String::from_utf8(output.stdout).ok()?;
1292 for line in version.lines() {
1293 if let Some(version) = line.split("LLVM version ").nth(1) {
1294 return Some(extract_llvm_version(version));
1295 }
1296 }
1297 None
1298}
1299
1300pub fn llvm_has_libzstd(config: &Config) -> bool {
1304 fn is_zstd_in_config(llvm_bin_dir: &Utf8Path) -> Option<()> {
1312 let llvm_config_path = llvm_bin_dir.join("llvm-config");
1313 let output = Command::new(llvm_config_path).arg("--system-libs").output().ok()?;
1314 assert!(output.status.success(), "running llvm-config --system-libs failed");
1315
1316 let libs = String::from_utf8(output.stdout).ok()?;
1317 for lib in libs.split_whitespace() {
1318 if lib.ends_with("libzstd.a") && Utf8Path::new(lib).exists() {
1319 return Some(());
1320 }
1321 }
1322
1323 None
1324 }
1325
1326 #[cfg(unix)]
1336 fn is_lld_built_with_zstd(llvm_bin_dir: &Utf8Path) -> Option<()> {
1337 let lld_path = llvm_bin_dir.join("lld");
1338 if lld_path.exists() {
1339 let lld_symlink_path = llvm_bin_dir.join("ld.lld");
1342 if !lld_symlink_path.exists() {
1343 std::os::unix::fs::symlink(lld_path, &lld_symlink_path).ok()?;
1344 }
1345
1346 let output = Command::new(&lld_symlink_path)
1349 .arg("--compress-debug-sections=zstd")
1350 .output()
1351 .ok()?;
1352 assert!(!output.status.success());
1353
1354 let stderr = String::from_utf8(output.stderr).ok()?;
1357 let zstd_available = !stderr.contains("LLVM was not built with LLVM_ENABLE_ZSTD");
1358
1359 std::fs::remove_file(lld_symlink_path).ok()?;
1362
1363 if zstd_available {
1364 return Some(());
1365 }
1366 }
1367
1368 None
1369 }
1370
1371 #[cfg(not(unix))]
1372 fn is_lld_built_with_zstd(_llvm_bin_dir: &Utf8Path) -> Option<()> {
1373 None
1374 }
1375
1376 if let Some(llvm_bin_dir) = &config.llvm_bin_dir {
1377 if is_zstd_in_config(llvm_bin_dir).is_some() {
1379 return true;
1380 }
1381
1382 if config.target == "x86_64-unknown-linux-gnu"
1390 && config.host == config.target
1391 && is_lld_built_with_zstd(llvm_bin_dir).is_some()
1392 {
1393 return true;
1394 }
1395 }
1396
1397 false
1399}
1400
1401fn extract_version_range<'a, F, VersionTy: Clone>(
1407 line: &'a str,
1408 parse: F,
1409) -> Option<(VersionTy, VersionTy)>
1410where
1411 F: Fn(&'a str) -> Option<VersionTy>,
1412{
1413 let mut splits = line.splitn(2, "- ").map(str::trim);
1414 let min = splits.next().unwrap();
1415 if min.ends_with('-') {
1416 return None;
1417 }
1418
1419 let max = splits.next();
1420
1421 if min.is_empty() {
1422 return None;
1423 }
1424
1425 let min = parse(min)?;
1426 let max = match max {
1427 Some("") => return None,
1428 Some(max) => parse(max)?,
1429 _ => min.clone(),
1430 };
1431
1432 Some((min, max))
1433}
1434
1435pub(crate) fn make_test_description<R: Read>(
1436 config: &Config,
1437 cache: &DirectivesCache,
1438 name: String,
1439 path: &Utf8Path,
1440 src: R,
1441 test_revision: Option<&str>,
1442 poisoned: &mut bool,
1443) -> CollectedTestDesc {
1444 let mut ignore = false;
1445 let mut ignore_message = None;
1446 let mut should_fail = false;
1447
1448 let mut local_poisoned = false;
1449
1450 iter_directives(
1452 config.mode,
1453 &mut local_poisoned,
1454 path,
1455 src,
1456 &mut |directive @ DirectiveLine { line_number, raw_directive: ln, .. }| {
1457 if !directive.applies_to_test_revision(test_revision) {
1458 return;
1459 }
1460
1461 macro_rules! decision {
1462 ($e:expr) => {
1463 match $e {
1464 IgnoreDecision::Ignore { reason } => {
1465 ignore = true;
1466 ignore_message = Some(reason.into());
1467 }
1468 IgnoreDecision::Error { message } => {
1469 error!("{path}:{line_number}: {message}");
1470 *poisoned = true;
1471 return;
1472 }
1473 IgnoreDecision::Continue => {}
1474 }
1475 };
1476 }
1477
1478 decision!(cfg::handle_ignore(config, ln));
1479 decision!(cfg::handle_only(config, ln));
1480 decision!(needs::handle_needs(&cache.needs, config, ln));
1481 decision!(ignore_llvm(config, path, ln, line_number));
1482 decision!(ignore_backends(config, path, ln, line_number));
1483 decision!(needs_backends(config, path, ln, line_number));
1484 decision!(ignore_cdb(config, ln));
1485 decision!(ignore_gdb(config, ln));
1486 decision!(ignore_lldb(config, ln));
1487
1488 if config.target == "wasm32-unknown-unknown"
1489 && config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS)
1490 {
1491 decision!(IgnoreDecision::Ignore {
1492 reason: "ignored on WASM as the run results cannot be checked there".into(),
1493 });
1494 }
1495
1496 should_fail |= config.parse_name_directive(ln, "should-fail");
1497 },
1498 );
1499
1500 if local_poisoned {
1501 eprintln!("errors encountered when trying to make test description: {}", path);
1502 panic!("errors encountered when trying to make test description");
1503 }
1504
1505 let should_panic = match config.mode {
1509 TestMode::Pretty => ShouldPanic::No,
1510 _ if should_fail => ShouldPanic::Yes,
1511 _ => ShouldPanic::No,
1512 };
1513
1514 CollectedTestDesc { name, ignore, ignore_message, should_panic }
1515}
1516
1517fn ignore_cdb(config: &Config, line: &str) -> IgnoreDecision {
1518 if config.debugger != Some(Debugger::Cdb) {
1519 return IgnoreDecision::Continue;
1520 }
1521
1522 if let Some(actual_version) = config.cdb_version {
1523 if let Some(rest) = line.strip_prefix("min-cdb-version:").map(str::trim) {
1524 let min_version = extract_cdb_version(rest).unwrap_or_else(|| {
1525 panic!("couldn't parse version range: {:?}", rest);
1526 });
1527
1528 if actual_version < min_version {
1531 return IgnoreDecision::Ignore {
1532 reason: format!("ignored when the CDB version is lower than {rest}"),
1533 };
1534 }
1535 }
1536 }
1537 IgnoreDecision::Continue
1538}
1539
1540fn ignore_gdb(config: &Config, line: &str) -> IgnoreDecision {
1541 if config.debugger != Some(Debugger::Gdb) {
1542 return IgnoreDecision::Continue;
1543 }
1544
1545 if let Some(actual_version) = config.gdb_version {
1546 if let Some(rest) = line.strip_prefix("min-gdb-version:").map(str::trim) {
1547 let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version)
1548 .unwrap_or_else(|| {
1549 panic!("couldn't parse version range: {:?}", rest);
1550 });
1551
1552 if start_ver != end_ver {
1553 panic!("Expected single GDB version")
1554 }
1555 if actual_version < start_ver {
1558 return IgnoreDecision::Ignore {
1559 reason: format!("ignored when the GDB version is lower than {rest}"),
1560 };
1561 }
1562 } else if let Some(rest) = line.strip_prefix("ignore-gdb-version:").map(str::trim) {
1563 let (min_version, max_version) = extract_version_range(rest, extract_gdb_version)
1564 .unwrap_or_else(|| {
1565 panic!("couldn't parse version range: {:?}", rest);
1566 });
1567
1568 if max_version < min_version {
1569 panic!("Malformed GDB version range: max < min")
1570 }
1571
1572 if actual_version >= min_version && actual_version <= max_version {
1573 if min_version == max_version {
1574 return IgnoreDecision::Ignore {
1575 reason: format!("ignored when the GDB version is {rest}"),
1576 };
1577 } else {
1578 return IgnoreDecision::Ignore {
1579 reason: format!("ignored when the GDB version is between {rest}"),
1580 };
1581 }
1582 }
1583 }
1584 }
1585 IgnoreDecision::Continue
1586}
1587
1588fn ignore_lldb(config: &Config, line: &str) -> IgnoreDecision {
1589 if config.debugger != Some(Debugger::Lldb) {
1590 return IgnoreDecision::Continue;
1591 }
1592
1593 if let Some(actual_version) = config.lldb_version {
1594 if let Some(rest) = line.strip_prefix("min-lldb-version:").map(str::trim) {
1595 let min_version = rest.parse().unwrap_or_else(|e| {
1596 panic!("Unexpected format of LLDB version string: {}\n{:?}", rest, e);
1597 });
1598 if actual_version < min_version {
1601 return IgnoreDecision::Ignore {
1602 reason: format!("ignored when the LLDB version is {rest}"),
1603 };
1604 }
1605 }
1606 }
1607 IgnoreDecision::Continue
1608}
1609
1610fn ignore_backends(
1611 config: &Config,
1612 path: &Utf8Path,
1613 line: &str,
1614 line_number: usize,
1615) -> IgnoreDecision {
1616 if let Some(backends_to_ignore) =
1617 config.parse_name_value_directive(line, "ignore-backends", path, line_number)
1618 {
1619 for backend in backends_to_ignore.split_whitespace().map(|backend| {
1620 match CodegenBackend::try_from(backend) {
1621 Ok(backend) => backend,
1622 Err(error) => {
1623 panic!("Invalid ignore-backends value `{backend}` in `{path}`: {error}")
1624 }
1625 }
1626 }) {
1627 if config.codegen_backend == backend {
1628 return IgnoreDecision::Ignore {
1629 reason: format!("{} backend is marked as ignore", backend.as_str()),
1630 };
1631 }
1632 }
1633 }
1634 IgnoreDecision::Continue
1635}
1636
1637fn needs_backends(
1638 config: &Config,
1639 path: &Utf8Path,
1640 line: &str,
1641 line_number: usize,
1642) -> IgnoreDecision {
1643 if let Some(needed_backends) =
1644 config.parse_name_value_directive(line, "needs-backends", path, line_number)
1645 {
1646 if !needed_backends
1647 .split_whitespace()
1648 .map(|backend| match CodegenBackend::try_from(backend) {
1649 Ok(backend) => backend,
1650 Err(error) => {
1651 panic!("Invalid needs-backends value `{backend}` in `{path}`: {error}")
1652 }
1653 })
1654 .any(|backend| config.codegen_backend == backend)
1655 {
1656 return IgnoreDecision::Ignore {
1657 reason: format!(
1658 "{} backend is not part of required backends",
1659 config.codegen_backend.as_str()
1660 ),
1661 };
1662 }
1663 }
1664 IgnoreDecision::Continue
1665}
1666
1667fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str, line_number: usize) -> IgnoreDecision {
1668 if let Some(needed_components) =
1669 config.parse_name_value_directive(line, "needs-llvm-components", path, line_number)
1670 {
1671 let components: HashSet<_> = config.llvm_components.split_whitespace().collect();
1672 if let Some(missing_component) = needed_components
1673 .split_whitespace()
1674 .find(|needed_component| !components.contains(needed_component))
1675 {
1676 if env::var_os("COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS").is_some() {
1677 panic!(
1678 "missing LLVM component {}, and COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS is set: {}",
1679 missing_component, path
1680 );
1681 }
1682 return IgnoreDecision::Ignore {
1683 reason: format!("ignored when the {missing_component} LLVM component is missing"),
1684 };
1685 }
1686 }
1687 if let Some(actual_version) = &config.llvm_version {
1688 if let Some(version_string) =
1691 config.parse_name_value_directive(line, "min-llvm-version", path, line_number)
1692 {
1693 let min_version = extract_llvm_version(&version_string);
1694 if *actual_version < min_version {
1696 return IgnoreDecision::Ignore {
1697 reason: format!(
1698 "ignored when the LLVM version {actual_version} is older than {min_version}"
1699 ),
1700 };
1701 }
1702 } else if let Some(version_string) =
1703 config.parse_name_value_directive(line, "max-llvm-major-version", path, line_number)
1704 {
1705 let max_version = extract_llvm_version(&version_string);
1706 if actual_version.major > max_version.major {
1708 return IgnoreDecision::Ignore {
1709 reason: format!(
1710 "ignored when the LLVM version ({actual_version}) is newer than major\
1711 version {}",
1712 max_version.major
1713 ),
1714 };
1715 }
1716 } else if let Some(version_string) =
1717 config.parse_name_value_directive(line, "min-system-llvm-version", path, line_number)
1718 {
1719 let min_version = extract_llvm_version(&version_string);
1720 if config.system_llvm && *actual_version < min_version {
1723 return IgnoreDecision::Ignore {
1724 reason: format!(
1725 "ignored when the system LLVM version {actual_version} is older than {min_version}"
1726 ),
1727 };
1728 }
1729 } else if let Some(version_range) =
1730 config.parse_name_value_directive(line, "ignore-llvm-version", path, line_number)
1731 {
1732 let (v_min, v_max) =
1734 extract_version_range(&version_range, |s| Some(extract_llvm_version(s)))
1735 .unwrap_or_else(|| {
1736 panic!("couldn't parse version range: \"{version_range}\"");
1737 });
1738 if v_max < v_min {
1739 panic!("malformed LLVM version range where {v_max} < {v_min}")
1740 }
1741 if *actual_version >= v_min && *actual_version <= v_max {
1743 if v_min == v_max {
1744 return IgnoreDecision::Ignore {
1745 reason: format!("ignored when the LLVM version is {actual_version}"),
1746 };
1747 } else {
1748 return IgnoreDecision::Ignore {
1749 reason: format!(
1750 "ignored when the LLVM version is between {v_min} and {v_max}"
1751 ),
1752 };
1753 }
1754 }
1755 } else if let Some(version_string) =
1756 config.parse_name_value_directive(line, "exact-llvm-major-version", path, line_number)
1757 {
1758 let version = extract_llvm_version(&version_string);
1760 if actual_version.major != version.major {
1761 return IgnoreDecision::Ignore {
1762 reason: format!(
1763 "ignored when the actual LLVM major version is {}, but the test only targets major version {}",
1764 actual_version.major, version.major
1765 ),
1766 };
1767 }
1768 }
1769 }
1770 IgnoreDecision::Continue
1771}
1772
1773enum IgnoreDecision {
1774 Ignore { reason: String },
1775 Continue,
1776 Error { message: String },
1777}