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