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