1use std::collections::HashSet;
2use std::process::Command;
3use std::{env, fs};
4
5use camino::{Utf8Path, Utf8PathBuf};
6use semver::Version;
7use tracing::*;
8
9use crate::common::{CodegenBackend, Config, Debugger, PassFailMode, TestMode};
10use crate::debuggers::{extract_cdb_version, extract_gdb_version};
11use crate::directives::auxiliary::parse_and_update_aux;
12pub(crate) use crate::directives::auxiliary::{AuxCrate, AuxProps};
13use crate::directives::directive_names::{
14 KNOWN_DIRECTIVE_NAMES_SET, KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, KNOWN_JSONDOCCK_DIRECTIVE_NAMES,
15};
16pub(crate) use crate::directives::file::FileDirectives;
17use crate::directives::handlers::DIRECTIVE_HANDLERS_MAP;
18use crate::directives::line::DirectiveLine;
19use crate::directives::needs::CachedNeedsConditions;
20use crate::edition::{Edition, parse_edition};
21use crate::errors::ErrorKind;
22use crate::executor::{CollectedTestDesc, ShouldFail};
23use crate::util::static_regex;
24use crate::{fatal, help};
25
26mod auxiliary;
27mod cfg;
28mod directive_names;
29mod file;
30mod handlers;
31mod line;
32pub(crate) use line::line_directive;
33mod line_number;
34pub(crate) use line_number::LineNumber;
35mod needs;
36#[cfg(test)]
37mod tests;
38
39pub(crate) struct DirectivesCache {
40 cfg_conditions: cfg::PreparedConditions,
43 needs: CachedNeedsConditions,
44}
45
46impl DirectivesCache {
47 pub(crate) fn load(config: &Config) -> Self {
48 Self {
49 cfg_conditions: cfg::prepare_conditions(config),
50 needs: CachedNeedsConditions::load(config),
51 }
52 }
53}
54
55#[derive(Default)]
58pub(crate) struct EarlyProps {
59 pub(crate) revisions: Vec<String>,
60}
61
62impl EarlyProps {
63 pub(crate) fn from_file_directives(
64 config: &Config,
65 file_directives: &FileDirectives<'_>,
66 ) -> Self {
67 let mut props = EarlyProps::default();
68
69 iter_directives(
70 config,
71 file_directives,
72 &mut |ln: &DirectiveLine<'_>| {
74 config.parse_and_update_revisions(ln, &mut props.revisions);
75 },
76 );
77
78 props
79 }
80}
81
82#[derive(Clone, Debug)]
83pub(crate) struct TestProps {
84 pub(crate) error_patterns: Vec<String>,
86 pub(crate) regex_error_patterns: Vec<String>,
88 pub(crate) edition: Option<Edition>,
92 pub(crate) compile_flags: Vec<String>,
94 pub(crate) run_flags: Vec<String>,
96 pub(crate) doc_flags: Vec<String>,
98 pub(crate) pp_exact: Option<Utf8PathBuf>,
101 pub(crate) aux: AuxProps,
103 pub(crate) rustc_env: Vec<(String, String)>,
105 pub(crate) unset_rustc_env: Vec<String>,
108 pub(crate) exec_env: Vec<(String, String)>,
110 pub(crate) unset_exec_env: Vec<String>,
113 pub(crate) build_aux_docs: bool,
115 pub(crate) unique_doc_out_dir: bool,
118 pub(crate) force_host: bool,
120 pub(crate) check_stdout: bool,
122 pub(crate) check_run_results: bool,
124 pub(crate) dont_check_compiler_stdout: bool,
126 pub(crate) dont_check_compiler_stderr: bool,
128 pub(crate) no_prefer_dynamic: bool,
134 pub(crate) pretty_mode: String,
136 pub(crate) pretty_compare_only: bool,
138 pub(crate) forbid_output: Vec<String>,
140 pub(crate) revisions: Vec<String>,
142 pub(crate) incremental_dir: Option<Utf8PathBuf>,
147 pub(crate) incremental: bool,
162 pub(crate) known_bug: bool,
168 pub(crate) pass_fail_mode: Option<PassFailMode>,
173 pub(crate) no_pass_override: bool,
175 pub(crate) check_test_line_numbers_match: bool,
177 pub(crate) normalize_stdout: Vec<(String, String)>,
179 pub(crate) normalize_stderr: Vec<(String, String)>,
180 pub(crate) failure_status: Option<i32>,
181 pub(crate) dont_check_failure_status: bool,
183 pub(crate) run_rustfix: bool,
186 pub(crate) rustfix_only_machine_applicable: bool,
188 pub(crate) assembly_output: Option<String>,
189 pub(crate) stderr_per_bitwidth: bool,
191 pub(crate) mir_unit_test: Option<String>,
193 pub(crate) remap_src_base: bool,
196 pub(crate) llvm_cov_flags: Vec<String>,
199 pub(crate) skip_filecheck: bool,
202 pub(crate) filecheck_flags: Vec<String>,
204 pub(crate) no_auto_check_cfg: bool,
206 pub(crate) add_minicore: bool,
209 pub(crate) minicore_compile_flags: Vec<String>,
211 pub(crate) dont_require_annotations: HashSet<ErrorKind>,
213 pub(crate) disable_gdb_pretty_printers: bool,
215 pub(crate) compare_output_by_lines: bool,
217}
218
219mod directives {
220 pub(crate) const ERROR_PATTERN: &str = "error-pattern";
221 pub(crate) const REGEX_ERROR_PATTERN: &str = "regex-error-pattern";
222 pub(crate) const COMPILE_FLAGS: &str = "compile-flags";
223 pub(crate) const RUN_FLAGS: &str = "run-flags";
224 pub(crate) const DOC_FLAGS: &str = "doc-flags";
225 pub(crate) const BUILD_AUX_DOCS: &str = "build-aux-docs";
226 pub(crate) const UNIQUE_DOC_OUT_DIR: &str = "unique-doc-out-dir";
227 pub(crate) const FORCE_HOST: &str = "force-host";
228 pub(crate) const CHECK_STDOUT: &str = "check-stdout";
229 pub(crate) const CHECK_RUN_RESULTS: &str = "check-run-results";
230 pub(crate) const DONT_CHECK_COMPILER_STDOUT: &str = "dont-check-compiler-stdout";
231 pub(crate) const DONT_CHECK_COMPILER_STDERR: &str = "dont-check-compiler-stderr";
232 pub(crate) const DONT_REQUIRE_ANNOTATIONS: &str = "dont-require-annotations";
233 pub(crate) const NO_PREFER_DYNAMIC: &str = "no-prefer-dynamic";
234 pub(crate) const PRETTY_MODE: &str = "pretty-mode";
235 pub(crate) const PRETTY_COMPARE_ONLY: &str = "pretty-compare-only";
236 pub(crate) const AUX_BIN: &str = "aux-bin";
237 pub(crate) const AUX_BUILD: &str = "aux-build";
238 pub(crate) const AUX_CRATE: &str = "aux-crate";
239 pub(crate) const PROC_MACRO: &str = "proc-macro";
240 pub(crate) const AUX_CODEGEN_BACKEND: &str = "aux-codegen-backend";
241 pub(crate) const EXEC_ENV: &str = "exec-env";
242 pub(crate) const RUSTC_ENV: &str = "rustc-env";
243 pub(crate) const UNSET_EXEC_ENV: &str = "unset-exec-env";
244 pub(crate) const UNSET_RUSTC_ENV: &str = "unset-rustc-env";
245 pub(crate) const FORBID_OUTPUT: &str = "forbid-output";
246 pub(crate) const CHECK_TEST_LINE_NUMBERS_MATCH: &str = "check-test-line-numbers-match";
247 pub(crate) const FAILURE_STATUS: &str = "failure-status";
248 pub(crate) const DONT_CHECK_FAILURE_STATUS: &str = "dont-check-failure-status";
249 pub(crate) const RUN_RUSTFIX: &str = "run-rustfix";
250 pub(crate) const RUSTFIX_ONLY_MACHINE_APPLICABLE: &str = "rustfix-only-machine-applicable";
251 pub(crate) const ASSEMBLY_OUTPUT: &str = "assembly-output";
252 pub(crate) const STDERR_PER_BITWIDTH: &str = "stderr-per-bitwidth";
253 pub(crate) const INCREMENTAL: &str = "incremental";
254 pub(crate) const KNOWN_BUG: &str = "known-bug";
255 pub(crate) const TEST_MIR_PASS: &str = "test-mir-pass";
256 pub(crate) const REMAP_SRC_BASE: &str = "remap-src-base";
257 pub(crate) const LLVM_COV_FLAGS: &str = "llvm-cov-flags";
258 pub(crate) const FILECHECK_FLAGS: &str = "filecheck-flags";
259 pub(crate) const NO_AUTO_CHECK_CFG: &str = "no-auto-check-cfg";
260 pub(crate) const ADD_MINICORE: &str = "add-minicore";
261 pub(crate) const MINICORE_COMPILE_FLAGS: &str = "minicore-compile-flags";
262 pub(crate) const DISABLE_GDB_PRETTY_PRINTERS: &str = "disable-gdb-pretty-printers";
263 pub(crate) const COMPARE_OUTPUT_BY_LINES: &str = "compare-output-by-lines";
264}
265
266impl TestProps {
267 pub(crate) fn new() -> Self {
268 TestProps {
269 error_patterns: vec![],
270 regex_error_patterns: vec![],
271 edition: None,
272 compile_flags: vec![],
273 run_flags: vec![],
274 doc_flags: vec![],
275 pp_exact: None,
276 aux: Default::default(),
277 revisions: vec![],
278 rustc_env: vec![
279 ("RUSTC_ICE".to_string(), "0".to_string()),
280 ("RUST_BACKTRACE".to_string(), "short".to_string()),
281 ],
282 unset_rustc_env: vec![("RUSTC_LOG_COLOR".to_string())],
283 exec_env: vec![],
284 unset_exec_env: vec![],
285 build_aux_docs: false,
286 unique_doc_out_dir: false,
287 force_host: false,
288 check_stdout: false,
289 check_run_results: false,
290 dont_check_compiler_stdout: false,
291 dont_check_compiler_stderr: false,
292 no_prefer_dynamic: false,
293 pretty_mode: "normal".to_string(),
294 pretty_compare_only: false,
295 forbid_output: vec![],
296 incremental_dir: None,
297 incremental: false,
298 known_bug: false,
299 pass_fail_mode: None,
300 no_pass_override: false,
301 check_test_line_numbers_match: false,
302 normalize_stdout: vec![],
303 normalize_stderr: vec![],
304 failure_status: None,
305 dont_check_failure_status: false,
306 run_rustfix: false,
307 rustfix_only_machine_applicable: false,
308 assembly_output: None,
309 stderr_per_bitwidth: false,
310 mir_unit_test: None,
311 remap_src_base: false,
312 llvm_cov_flags: vec![],
313 skip_filecheck: false,
314 filecheck_flags: vec![],
315 no_auto_check_cfg: false,
316 add_minicore: false,
317 minicore_compile_flags: vec![],
318 dont_require_annotations: Default::default(),
319 disable_gdb_pretty_printers: false,
320 compare_output_by_lines: false,
321 }
322 }
323
324 pub(crate) fn from_aux_file(
325 &self,
326 testfile: &Utf8Path,
327 revision: Option<&str>,
328 config: &Config,
329 ) -> Self {
330 let mut props = TestProps::new();
331
332 props.incremental_dir = self.incremental_dir.clone();
334 props.no_pass_override = true;
335 props.load_from(testfile, revision, config);
336
337 props
338 }
339
340 pub(crate) fn from_file(testfile: &Utf8Path, revision: Option<&str>, config: &Config) -> Self {
341 let mut props = TestProps::new();
342 props.load_from(testfile, revision, config);
343 props.exec_env.push(("RUSTC".to_string(), config.rustc_path.to_string()));
344
345 if config.mode == TestMode::Ui && props.pass_fail_mode.is_none() {
347 props.pass_fail_mode = Some(PassFailMode::CheckFail);
348 }
349
350 props
351 }
352
353 fn load_from(&mut self, testfile: &Utf8Path, test_revision: Option<&str>, config: &Config) {
358 if !testfile.is_dir() {
359 let file_contents = fs::read_to_string(testfile).unwrap();
360 let file_directives = FileDirectives::from_file_contents(testfile, &file_contents);
361
362 iter_directives(
363 config,
364 &file_directives,
365 &mut |ln: &DirectiveLine<'_>| {
367 if !ln.applies_to_test_revision(test_revision) {
368 return;
369 }
370
371 if let Some(handler) = DIRECTIVE_HANDLERS_MAP.get(ln.name) {
372 handler.handle(config, ln, self);
373 }
374 },
375 );
376 }
377
378 if config.mode == TestMode::Incremental {
379 self.incremental = true;
380 }
381
382 if config.mode == TestMode::Crashes {
383 self.rustc_env = vec![
387 ("RUST_BACKTRACE".to_string(), "0".to_string()),
388 ("RUSTC_ICE".to_string(), "0".to_string()),
389 ];
390 }
391
392 for key in &["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] {
393 if let Ok(val) = env::var(key) {
394 if !self.exec_env.iter().any(|&(ref x, _)| x == key) {
395 self.exec_env.push(((*key).to_owned(), val))
396 }
397 }
398 }
399
400 if let Some(edition) = self.edition.or(config.edition) {
401 self.compile_flags.insert(0, format!("--edition={edition}"));
404 }
405 }
406
407 fn update_pass_fail_mode(&mut self, ln: &DirectiveLine<'_>, config: &Config) {
408 let name = ln.name;
409 if config.mode != TestMode::Ui {
410 panic!("`{name}` directive is only supported in UI tests");
411 }
412 if self.pass_fail_mode.is_some() {
413 panic!("multiple `*-fail` or `*-pass` directives in a single test");
414 }
415
416 let mode = ln.name.parse::<PassFailMode>().unwrap();
417 self.pass_fail_mode = Some(mode);
418 }
419
420 fn update_add_minicore(&mut self, ln: &DirectiveLine<'_>, config: &Config) {
421 let add_minicore = config.parse_name_directive(ln, directives::ADD_MINICORE);
422 if add_minicore {
423 if !matches!(
424 config.mode,
425 TestMode::Ui | TestMode::Codegen | TestMode::Assembly | TestMode::MirOpt
426 ) {
427 panic!(
428 "`add-minicore` is currently only supported for ui, codegen, assembly and mir-opt test modes"
429 );
430 }
431
432 if self.pass_fail_mode == Some(PassFailMode::RunPass) {
435 panic!("`add-minicore` cannot be used to run the test binary");
438 }
439
440 self.add_minicore = add_minicore;
441 }
442 }
443}
444
445pub(crate) fn do_early_directives_check(
446 mode: TestMode,
447 file_directives: &FileDirectives<'_>,
448) -> Result<(), String> {
449 let testfile = file_directives.path;
450
451 for directive_line @ DirectiveLine { line_number, .. } in &file_directives.lines {
452 let CheckDirectiveResult { is_known_directive, trailing_directive } =
453 check_directive(directive_line, mode);
454
455 if !is_known_directive {
456 return Err(format!(
457 "ERROR: unknown compiletest directive `{directive}` at {testfile}:{line_number}",
458 directive = directive_line.display(),
459 ));
460 }
461
462 if let Some(trailing_directive) = &trailing_directive {
463 return Err(format!(
464 "ERROR: detected trailing compiletest directive `{trailing_directive}` at {testfile}:{line_number}\n\
465 HELP: put the directive on its own line: `//@ {trailing_directive}`"
466 ));
467 }
468 }
469
470 Ok(())
471}
472
473pub(crate) struct CheckDirectiveResult<'ln> {
474 is_known_directive: bool,
475 trailing_directive: Option<&'ln str>,
476}
477
478fn check_directive<'a>(
479 directive_ln: &DirectiveLine<'a>,
480 mode: TestMode,
481) -> CheckDirectiveResult<'a> {
482 let &DirectiveLine { name: directive_name, .. } = directive_ln;
483
484 let is_known_directive = KNOWN_DIRECTIVE_NAMES_SET.contains(&directive_name)
485 || match mode {
486 TestMode::RustdocHtml => KNOWN_HTMLDOCCK_DIRECTIVE_NAMES.contains(&directive_name),
487 TestMode::RustdocJson => KNOWN_JSONDOCCK_DIRECTIVE_NAMES.contains(&directive_name),
488 _ => false,
489 };
490
491 let trailing_directive = directive_ln
495 .remark_after_space()
496 .map(|remark| remark.trim_start().split(' ').next().unwrap())
497 .filter(|token| KNOWN_DIRECTIVE_NAMES_SET.contains(token));
498
499 CheckDirectiveResult { is_known_directive, trailing_directive }
505}
506
507fn iter_directives(
508 config: &Config,
509 file_directives: &FileDirectives<'_>,
510 it: &mut dyn FnMut(&DirectiveLine<'_>),
511) {
512 let testfile = file_directives.path;
513
514 let extra_directives = match config.mode {
515 TestMode::CoverageRun => {
516 vec![
521 "//@ needs-profiler-runtime",
522 "//@ ignore-cross-compile",
526 ]
527 }
528 TestMode::Codegen if !file_directives.has_explicit_no_std_core_attribute => {
529 vec!["//@ needs-target-std"]
536 }
537 TestMode::Ui if config.parallel_frontend_enabled() => {
538 vec!["//@ compare-output-by-lines"]
541 }
542
543 _ => {
544 vec![]
546 }
547 };
548
549 for directive_str in extra_directives {
550 let directive_line = line_directive(testfile, LineNumber::ZERO, directive_str)
551 .unwrap_or_else(|| panic!("bad extra-directive line: {directive_str:?}"));
552 it(&directive_line);
553 }
554
555 for directive_line in &file_directives.lines {
556 it(directive_line);
557 }
558}
559
560impl Config {
561 fn parse_and_update_revisions(&self, line: &DirectiveLine<'_>, existing: &mut Vec<String>) {
562 const FORBIDDEN_REVISION_NAMES: [&str; 2] = [
563 "true", "false",
567 ];
568
569 const FILECHECK_FORBIDDEN_REVISION_NAMES: [&str; 9] =
570 ["CHECK", "COM", "NEXT", "SAME", "EMPTY", "NOT", "COUNT", "DAG", "LABEL"];
571
572 if let Some(raw) = self.parse_name_value_directive(line, "revisions") {
573 let &DirectiveLine { file_path: testfile, .. } = line;
574
575 if self.mode == TestMode::RunMake {
576 panic!("`run-make` mode tests do not support revisions: {}", testfile);
577 }
578
579 let mut duplicates: HashSet<_> = existing.iter().cloned().collect();
580 for revision in raw.split_whitespace() {
581 if !duplicates.insert(revision.to_string()) {
582 panic!("duplicate revision: `{}` in line `{}`: {}", revision, raw, testfile);
583 }
584
585 if FORBIDDEN_REVISION_NAMES.contains(&revision) {
586 panic!(
587 "revision name `{revision}` is not permitted: `{}` in line `{}`: {}",
588 revision, raw, testfile
589 );
590 }
591
592 if matches!(self.mode, TestMode::Assembly | TestMode::Codegen | TestMode::MirOpt)
593 && FILECHECK_FORBIDDEN_REVISION_NAMES.contains(&revision)
594 {
595 panic!(
596 "revision name `{revision}` is not permitted in a test suite that uses \
597 `FileCheck` annotations as it is confusing when used as custom `FileCheck` \
598 prefix: `{revision}` in line `{}`: {}",
599 raw, testfile
600 );
601 }
602
603 existing.push(revision.to_string());
604 }
605 }
606 }
607
608 fn parse_env(nv: String) -> (String, String) {
609 let (name, value) = nv.split_once('=').unwrap_or((&nv, ""));
613 let name = name.trim();
616 (name.to_owned(), value.to_owned())
617 }
618
619 fn parse_pp_exact(&self, line: &DirectiveLine<'_>) -> Option<Utf8PathBuf> {
620 if line.value_after_colon().is_some()
623 && let Some(s) = self.parse_name_value_directive(line, "pp-exact")
624 {
625 Some(Utf8PathBuf::from(&s))
626 } else if self.parse_name_directive(line, "pp-exact") {
627 line.file_path.file_name().map(Utf8PathBuf::from)
628 } else {
629 None
630 }
631 }
632
633 fn parse_custom_normalization(&self, line: &DirectiveLine<'_>) -> Option<NormalizeRule> {
634 let &DirectiveLine { name, .. } = line;
635
636 let kind = match name {
637 "normalize-stdout" => NormalizeKind::Stdout,
638 "normalize-stderr" => NormalizeKind::Stderr,
639 "normalize-stderr-32bit" => NormalizeKind::Stderr32bit,
640 "normalize-stderr-64bit" => NormalizeKind::Stderr64bit,
641 _ => return None,
642 };
643
644 let Some((regex, replacement)) = line.value_after_colon().and_then(parse_normalize_rule)
645 else {
646 error!("couldn't parse custom normalization rule: `{}`", line.display());
647 help!("expected syntax is: `{name}: \"REGEX\" -> \"REPLACEMENT\"`");
648 panic!("invalid normalization rule detected");
649 };
650 Some(NormalizeRule { kind, regex, replacement })
651 }
652
653 fn parse_name_directive(&self, line: &DirectiveLine<'_>, directive: &str) -> bool {
654 if line.name != directive {
655 return false;
656 }
657
658 if line.value_after_colon().is_some() {
659 let &DirectiveLine { file_path, line_number, .. } = line;
660 panic!(
661 "{file_path}:{line_number}: directive `{directive}` must not be followed by a colon"
662 );
663 }
664 true
665 }
666
667 fn parse_name_value_directive(
668 &self,
669 line: &DirectiveLine<'_>,
670 directive: &str,
671 ) -> Option<String> {
672 let &DirectiveLine { file_path, line_number, .. } = line;
673
674 if line.name != directive {
675 return None;
676 };
677
678 let value = line.value_after_colon().unwrap_or_else(|| {
679 panic!("{file_path}:{line_number}: directive `{directive}` must be followed by a colon and value");
680 });
681 debug!("{}: {}", directive, value);
682 let value = expand_variables(value.to_owned(), self);
683
684 if value.is_empty() {
685 error!("{file_path}:{line_number}: empty value for directive `{directive}`");
686 help!("expected syntax is: `{directive}: value`");
687 panic!("empty directive value detected");
688 }
689
690 Some(value)
691 }
692
693 fn set_name_directive(&self, line: &DirectiveLine<'_>, directive: &str, value: &mut bool) {
694 *value = *value || self.parse_name_directive(line, directive);
696 }
697
698 fn set_name_value_directive<T>(
699 &self,
700 line: &DirectiveLine<'_>,
701 directive: &str,
702 value: &mut Option<T>,
703 parse: impl FnOnce(String) -> T,
704 ) {
705 if value.is_none() {
706 *value = self.parse_name_value_directive(line, directive).map(parse);
707 }
708 }
709
710 fn push_name_value_directive<T>(
711 &self,
712 line: &DirectiveLine<'_>,
713 directive: &str,
714 values: &mut Vec<T>,
715 parse: impl FnOnce(String) -> T,
716 ) {
717 if let Some(value) = self.parse_name_value_directive(line, directive).map(parse) {
718 values.push(value);
719 }
720 }
721}
722
723fn expand_variables(mut value: String, config: &Config) -> String {
725 const CWD: &str = "{{cwd}}";
726 const SRC_BASE: &str = "{{src-base}}";
727 const TEST_SUITE_BUILD_BASE: &str = "{{build-base}}";
728 const RUST_SRC_BASE: &str = "{{rust-src-base}}";
729 const SYSROOT_BASE: &str = "{{sysroot-base}}";
730 const TARGET_LINKER: &str = "{{target-linker}}";
731 const TARGET: &str = "{{target}}";
732
733 if value.contains(CWD) {
734 let cwd = env::current_dir().unwrap();
735 value = value.replace(CWD, &cwd.to_str().unwrap());
736 }
737
738 if value.contains(SRC_BASE) {
739 value = value.replace(SRC_BASE, &config.src_test_suite_root.as_str());
740 }
741
742 if value.contains(TEST_SUITE_BUILD_BASE) {
743 value = value.replace(TEST_SUITE_BUILD_BASE, &config.build_test_suite_root.as_str());
744 }
745
746 if value.contains(SYSROOT_BASE) {
747 value = value.replace(SYSROOT_BASE, &config.sysroot_base.as_str());
748 }
749
750 if value.contains(TARGET_LINKER) {
751 value = value.replace(TARGET_LINKER, config.target_linker.as_deref().unwrap_or(""));
752 }
753
754 if value.contains(TARGET) {
755 value = value.replace(TARGET, &config.target);
756 }
757
758 if value.contains(RUST_SRC_BASE) {
759 let src_base = config.sysroot_base.join("lib/rustlib/src/rust");
760 src_base.try_exists().expect(&*format!("{} should exists", src_base));
761 let src_base = src_base.read_link_utf8().unwrap_or(src_base);
762 value = value.replace(RUST_SRC_BASE, &src_base.as_str());
763 }
764
765 value
766}
767
768struct NormalizeRule {
769 kind: NormalizeKind,
770 regex: String,
771 replacement: String,
772}
773
774enum NormalizeKind {
775 Stdout,
776 Stderr,
777 Stderr32bit,
778 Stderr64bit,
779}
780
781fn parse_normalize_rule(raw_value: &str) -> Option<(String, String)> {
786 let captures = static_regex!(
788 r#"(?x) # (verbose mode regex)
789 ^
790 \s* # (leading whitespace)
791 "(?<regex>[^"]*)" # "REGEX"
792 \s+->\s+ # ->
793 "(?<replacement>[^"]*)" # "REPLACEMENT"
794 $
795 "#
796 )
797 .captures(raw_value)?;
798 let regex = captures["regex"].to_owned();
799 let replacement = captures["replacement"].to_owned();
800 let replacement = replacement.replace("\\n", "\n");
804 Some((regex, replacement))
805}
806
807pub(crate) fn extract_llvm_version(version: &str) -> Version {
817 let version = version.trim();
820 let uninterested = |c: char| !c.is_ascii_digit() && c != '.';
821 let version_without_suffix = match version.split_once(uninterested) {
822 Some((prefix, _suffix)) => prefix,
823 None => version,
824 };
825
826 let components: Vec<u64> = version_without_suffix
827 .split('.')
828 .map(|s| s.parse().expect("llvm version component should consist of only digits"))
829 .collect();
830
831 match &components[..] {
832 [major] => Version::new(*major, 0, 0),
833 [major, minor] => Version::new(*major, *minor, 0),
834 [major, minor, patch] => Version::new(*major, *minor, *patch),
835 _ => panic!("malformed llvm version string, expected only 1-3 components: {version}"),
836 }
837}
838
839pub(crate) fn extract_llvm_version_from_binary(binary_path: &str) -> Option<Version> {
840 let output = Command::new(binary_path).arg("--version").output().ok()?;
841 if !output.status.success() {
842 return None;
843 }
844 let version = String::from_utf8(output.stdout).ok()?;
845 for line in version.lines() {
846 if let Some(version) = line.split("LLVM version ").nth(1) {
847 return Some(extract_llvm_version(version));
848 }
849 }
850 None
851}
852
853fn extract_version_range<'a, F, VersionTy: Clone>(
859 line: &'a str,
860 parse: F,
861) -> Option<(VersionTy, VersionTy)>
862where
863 F: Fn(&'a str) -> Option<VersionTy>,
864{
865 let mut splits = line.splitn(2, "- ").map(str::trim);
866 let min = splits.next().unwrap();
867 if min.ends_with('-') {
868 return None;
869 }
870
871 let max = splits.next();
872
873 if min.is_empty() {
874 return None;
875 }
876
877 let min = parse(min)?;
878 let max = match max {
879 Some("") => return None,
880 Some(max) => parse(max)?,
881 _ => min.clone(),
882 };
883
884 Some((min, max))
885}
886
887pub(crate) fn make_test_description(
888 config: &Config,
889 cache: &DirectivesCache,
890 name: String,
891 path: &Utf8Path,
892 filterable_path: &Utf8Path,
893 file_directives: &FileDirectives<'_>,
894 test_revision: Option<&str>,
895 poisoned: &mut bool,
896 aux_props: &mut AuxProps,
897) -> CollectedTestDesc {
898 let mut ignore = false;
899 let mut ignore_message = None;
900 let mut should_fail = false;
901
902 iter_directives(config, file_directives, &mut |ln @ &DirectiveLine { line_number, .. }| {
904 if !ln.applies_to_test_revision(test_revision) {
905 return;
906 }
907
908 parse_and_update_aux(config, ln, aux_props);
910
911 macro_rules! decision {
912 ($e:expr) => {
913 match $e {
914 IgnoreDecision::Ignore { reason } => {
915 ignore = true;
916 ignore_message = Some(reason.into());
917 }
918 IgnoreDecision::Error { message } => {
919 error!("{path}:{line_number}: {message}");
920 *poisoned = true;
921 return;
922 }
923 IgnoreDecision::Continue => {}
924 }
925 };
926 }
927
928 decision!(cfg::handle_ignore(&cache.cfg_conditions, ln));
929 decision!(cfg::handle_only(&cache.cfg_conditions, ln));
930 decision!(needs::handle_needs(&cache.needs, config, ln));
931 decision!(ignore_llvm(config, ln));
932 decision!(ignore_backends(config, ln));
933 decision!(needs_backends(config, ln));
934 decision!(ignore_cdb(config, ln));
935 decision!(ignore_gdb(config, ln));
936 decision!(ignore_lldb(config, ln));
937 decision!(ignore_parallel_frontend(config, ln));
938
939 if config.target == "wasm32-unknown-unknown"
940 && config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS)
941 {
942 decision!(IgnoreDecision::Ignore {
943 reason: "ignored on WASM as the run results cannot be checked there".into(),
944 });
945 }
946
947 should_fail |= config.parse_name_directive(ln, "should-fail");
948 });
949
950 let should_fail = if should_fail && config.mode != TestMode::Pretty {
954 ShouldFail::Yes
955 } else {
956 ShouldFail::No
957 };
958
959 CollectedTestDesc {
960 name,
961 filterable_path: filterable_path.to_owned(),
962 ignore,
963 ignore_message,
964 should_fail,
965 }
966}
967
968fn ignore_cdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
969 if config.debugger != Some(Debugger::Cdb) {
970 return IgnoreDecision::Continue;
971 }
972
973 if let Some(actual_version) = config.cdb_version {
974 if line.name == "min-cdb-version"
975 && let Some(rest) = line.value_after_colon().map(str::trim)
976 {
977 let min_version = extract_cdb_version(rest).unwrap_or_else(|| {
978 panic!("couldn't parse version range: {:?}", rest);
979 });
980
981 if actual_version < min_version {
984 return IgnoreDecision::Ignore {
985 reason: format!("ignored when the CDB version is lower than {rest}"),
986 };
987 }
988 }
989 }
990 IgnoreDecision::Continue
991}
992
993fn ignore_gdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
994 if config.debugger != Some(Debugger::Gdb) {
995 return IgnoreDecision::Continue;
996 }
997
998 if let Some(actual_version) = config.gdb_version {
999 if line.name == "min-gdb-version"
1000 && let Some(rest) = line.value_after_colon().map(str::trim)
1001 {
1002 let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version)
1003 .unwrap_or_else(|| {
1004 panic!("couldn't parse version range: {:?}", rest);
1005 });
1006
1007 if start_ver != end_ver {
1008 panic!("Expected single GDB version")
1009 }
1010 if actual_version < start_ver {
1013 return IgnoreDecision::Ignore {
1014 reason: format!("ignored when the GDB version is lower than {rest}"),
1015 };
1016 }
1017 } else if line.name == "ignore-gdb-version"
1018 && let Some(rest) = line.value_after_colon().map(str::trim)
1019 {
1020 let (min_version, max_version) = extract_version_range(rest, extract_gdb_version)
1021 .unwrap_or_else(|| {
1022 panic!("couldn't parse version range: {:?}", rest);
1023 });
1024
1025 if max_version < min_version {
1026 panic!("Malformed GDB version range: max < min")
1027 }
1028
1029 if actual_version >= min_version && actual_version <= max_version {
1030 if min_version == max_version {
1031 return IgnoreDecision::Ignore {
1032 reason: format!("ignored when the GDB version is {rest}"),
1033 };
1034 } else {
1035 return IgnoreDecision::Ignore {
1036 reason: format!("ignored when the GDB version is between {rest}"),
1037 };
1038 }
1039 }
1040 }
1041 }
1042 IgnoreDecision::Continue
1043}
1044
1045fn ignore_lldb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1046 if config.debugger != Some(Debugger::Lldb) {
1047 return IgnoreDecision::Continue;
1048 }
1049
1050 if let Some(actual_version) = config.lldb_version {
1051 if line.name == "min-lldb-version"
1052 && let Some(rest) = line.value_after_colon().map(str::trim)
1053 {
1054 let min_version = rest.parse().unwrap_or_else(|e| {
1055 panic!("Unexpected format of LLDB version string: {}\n{:?}", rest, e);
1056 });
1057 if actual_version < min_version {
1060 return IgnoreDecision::Ignore {
1061 reason: format!("ignored when the LLDB version is {rest}"),
1062 };
1063 }
1064 }
1065 }
1066 IgnoreDecision::Continue
1067}
1068
1069fn ignore_backends(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1070 let path = line.file_path;
1071 if let Some(backends_to_ignore) = config.parse_name_value_directive(line, "ignore-backends") {
1072 for backend in backends_to_ignore.split_whitespace().map(|backend| {
1073 match CodegenBackend::try_from(backend) {
1074 Ok(backend) => backend,
1075 Err(error) => {
1076 panic!("Invalid ignore-backends value `{backend}` in `{path}`: {error}")
1077 }
1078 }
1079 }) {
1080 if !config.bypass_ignore_backends && config.default_codegen_backend == backend {
1081 return IgnoreDecision::Ignore {
1082 reason: format!("{} backend is marked as ignore", backend.as_str()),
1083 };
1084 }
1085 }
1086 }
1087 IgnoreDecision::Continue
1088}
1089
1090fn needs_backends(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1091 let path = line.file_path;
1092 if let Some(needed_backends) = config.parse_name_value_directive(line, "needs-backends") {
1093 if !needed_backends
1094 .split_whitespace()
1095 .map(|backend| match CodegenBackend::try_from(backend) {
1096 Ok(backend) => backend,
1097 Err(error) => {
1098 panic!("Invalid needs-backends value `{backend}` in `{path}`: {error}")
1099 }
1100 })
1101 .any(|backend| config.default_codegen_backend == backend)
1102 {
1103 return IgnoreDecision::Ignore {
1104 reason: format!(
1105 "{} backend is not part of required backends",
1106 config.default_codegen_backend.as_str()
1107 ),
1108 };
1109 }
1110 }
1111 IgnoreDecision::Continue
1112}
1113
1114fn ignore_llvm(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1115 let path = line.file_path;
1116 if let Some(needed_components) =
1117 config.parse_name_value_directive(line, "needs-llvm-components")
1118 {
1119 let components: HashSet<_> = config.llvm_components.split_whitespace().collect();
1120 if let Some(missing_component) = needed_components
1121 .split_whitespace()
1122 .find(|needed_component| !components.contains(needed_component))
1123 {
1124 if env::var_os("COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS").is_some() {
1125 panic!(
1126 "missing LLVM component {missing_component}, \
1127 and COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS is set: {path}",
1128 );
1129 }
1130 return IgnoreDecision::Ignore {
1131 reason: format!("ignored when the {missing_component} LLVM component is missing"),
1132 };
1133 }
1134 }
1135 if let Some(actual_version) = &config.llvm_version {
1136 if let Some(version_string) = config.parse_name_value_directive(line, "min-llvm-version") {
1139 let min_version = extract_llvm_version(&version_string);
1140 if *actual_version < min_version {
1142 return IgnoreDecision::Ignore {
1143 reason: format!(
1144 "ignored when the LLVM version {actual_version} is older than {min_version}"
1145 ),
1146 };
1147 }
1148 } else if let Some(version_string) =
1149 config.parse_name_value_directive(line, "max-llvm-major-version")
1150 {
1151 let max_version = extract_llvm_version(&version_string);
1152 if actual_version.major > max_version.major {
1154 return IgnoreDecision::Ignore {
1155 reason: format!(
1156 "ignored when the LLVM version ({actual_version}) is newer than major\
1157 version {}",
1158 max_version.major
1159 ),
1160 };
1161 }
1162 } else if let Some(version_string) =
1163 config.parse_name_value_directive(line, "min-system-llvm-version")
1164 {
1165 let min_version = extract_llvm_version(&version_string);
1166 if config.system_llvm && *actual_version < min_version {
1169 return IgnoreDecision::Ignore {
1170 reason: format!(
1171 "ignored when the system LLVM version {actual_version} is older than {min_version}"
1172 ),
1173 };
1174 }
1175 } else if let Some(version_range) =
1176 config.parse_name_value_directive(line, "ignore-llvm-version")
1177 {
1178 let (v_min, v_max) =
1180 extract_version_range(&version_range, |s| Some(extract_llvm_version(s)))
1181 .unwrap_or_else(|| {
1182 panic!("couldn't parse version range: \"{version_range}\"");
1183 });
1184 if v_max < v_min {
1185 panic!("malformed LLVM version range where {v_max} < {v_min}")
1186 }
1187 if *actual_version >= v_min && *actual_version <= v_max {
1189 if v_min == v_max {
1190 return IgnoreDecision::Ignore {
1191 reason: format!("ignored when the LLVM version is {actual_version}"),
1192 };
1193 } else {
1194 return IgnoreDecision::Ignore {
1195 reason: format!(
1196 "ignored when the LLVM version is between {v_min} and {v_max}"
1197 ),
1198 };
1199 }
1200 }
1201 } else if let Some(version_string) =
1202 config.parse_name_value_directive(line, "exact-llvm-major-version")
1203 {
1204 let version = extract_llvm_version(&version_string);
1206 if actual_version.major != version.major {
1207 return IgnoreDecision::Ignore {
1208 reason: format!(
1209 "ignored when the actual LLVM major version is {}, but the test only targets major version {}",
1210 actual_version.major, version.major
1211 ),
1212 };
1213 }
1214 }
1215 }
1216 IgnoreDecision::Continue
1217}
1218
1219fn ignore_parallel_frontend(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1220 if config.parallel_frontend_enabled()
1221 && config.parse_name_directive(line, "ignore-parallel-frontend")
1222 {
1223 return IgnoreDecision::Ignore {
1224 reason: "ignored when the parallel frontend is enabled".into(),
1225 };
1226 }
1227 IgnoreDecision::Continue
1228}
1229
1230enum IgnoreDecision {
1231 Ignore { reason: String },
1232 Continue,
1233 Error { message: String },
1234}
1235
1236fn parse_edition_range(config: &Config, line: &DirectiveLine<'_>) -> Option<EditionRange> {
1237 let raw = config.parse_name_value_directive(line, "edition")?;
1238 let &DirectiveLine { file_path: testfile, line_number, .. } = line;
1239
1240 if let Some((lower_bound, upper_bound)) = raw.split_once("..") {
1242 Some(match (maybe_parse_edition(lower_bound), maybe_parse_edition(upper_bound)) {
1243 (Some(lower_bound), Some(upper_bound)) if upper_bound <= lower_bound => {
1244 fatal!(
1245 "{testfile}:{line_number}: the left side of `//@ edition` cannot be greater than or equal to the right side"
1246 );
1247 }
1248 (Some(lower_bound), Some(upper_bound)) => {
1249 EditionRange::Range { lower_bound, upper_bound }
1250 }
1251 (Some(lower_bound), None) => EditionRange::RangeFrom(lower_bound),
1252 (None, Some(_)) => {
1253 fatal!(
1254 "{testfile}:{line_number}: `..edition` is not a supported range in `//@ edition`"
1255 );
1256 }
1257 (None, None) => {
1258 fatal!("{testfile}:{line_number}: `..` is not a supported range in `//@ edition`");
1259 }
1260 })
1261 } else {
1262 match maybe_parse_edition(&raw) {
1263 Some(edition) => Some(EditionRange::Exact(edition)),
1264 None => {
1265 fatal!("{testfile}:{line_number}: empty value for `//@ edition`");
1266 }
1267 }
1268 }
1269}
1270
1271fn maybe_parse_edition(mut input: &str) -> Option<Edition> {
1272 input = input.trim();
1273 if input.is_empty() {
1274 return None;
1275 }
1276 Some(parse_edition(input))
1277}
1278
1279#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1280enum EditionRange {
1281 Exact(Edition),
1282 RangeFrom(Edition),
1283 Range {
1285 lower_bound: Edition,
1286 upper_bound: Edition,
1287 },
1288}
1289
1290impl EditionRange {
1291 fn edition_to_test(&self, requested: impl Into<Option<Edition>>) -> Edition {
1292 let min_edition = Edition::Year(2015);
1293 let requested = requested.into().unwrap_or(min_edition);
1294
1295 match *self {
1296 EditionRange::Exact(exact) => exact,
1297 EditionRange::RangeFrom(lower_bound) => {
1298 if requested >= lower_bound {
1299 requested
1300 } else {
1301 lower_bound
1302 }
1303 }
1304 EditionRange::Range { lower_bound, upper_bound } => {
1305 if requested >= lower_bound && requested < upper_bound {
1306 requested
1307 } else {
1308 lower_bound
1309 }
1310 }
1311 }
1312 }
1313}
1314
1315fn split_flags(flags: &str) -> Vec<String> {
1316 flags
1321 .split('\'')
1322 .enumerate()
1323 .flat_map(|(i, f)| if i % 2 == 1 { vec![f] } else { f.split_whitespace().collect() })
1324 .map(move |s| s.to_owned())
1325 .collect::<Vec<_>>()
1326}