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_cdb(config, variant, ln));
962 decision!(ignore_gdb(config, variant, ln));
963 decision!(ignore_lldb(config, variant, ln));
964 decision!(ignore_parallel_frontend(config, ln));
965
966 if config.target == "wasm32-unknown-unknown"
967 && config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS)
968 {
969 decision!(IgnoreDecision::Ignore {
970 reason: "ignored on WASM as the run results cannot be checked there".into(),
971 });
972 }
973
974 should_fail |= config.parse_name_directive(ln, "should-fail");
975 },
976 );
977 }
978
979 let should_fail = if should_fail && config.mode != TestMode::Pretty {
983 ShouldFail::Yes
984 } else {
985 ShouldFail::No
986 };
987
988 CollectedTestDesc {
989 name,
990 filterable_path: filterable_path.to_owned(),
991 ignore_message,
992 should_fail,
993 }
994}
995
996fn check_cdb_support(config: &Config) -> Option<String> {
998 if config.cdb.is_none() { Some("cdb is not available".to_string()) } else { None }
999}
1000
1001fn check_gdb_support(config: &Config) -> Option<String> {
1003 if config.gdb_version.is_none() {
1004 return Some("gdb is not available".to_string());
1005 }
1006
1007 if config.matches_env("msvc") {
1008 return Some("gdb tests do not run on msvc".to_string());
1009 }
1010
1011 if config.remote_test_client.is_some() && !config.target.contains("android") {
1012 return Some("gdb tests are not available when testing with remote".to_string());
1013 }
1014 None
1015}
1016
1017fn check_lldb_support(config: &Config) -> Option<String> {
1019 if config.lldb.is_none() { Some("lldb is not available".to_string()) } else { None }
1020}
1021
1022fn ignore_cdb(config: &Config, variant: &TestVariant, line: &DirectiveLine<'_>) -> IgnoreDecision {
1023 if variant.debugger != Some(Debugger::Cdb) {
1024 return if line.name == "only-cdb" {
1025 IgnoreDecision::Ignore { reason: "debugger is not cdb".to_string() }
1026 } else {
1027 IgnoreDecision::Continue
1028 };
1029 }
1030
1031 if line.name == "ignore-cdb" {
1032 return IgnoreDecision::Ignore { reason: "debugger is cdb".to_string() };
1033 }
1034
1035 if let Some(actual_version) = config.cdb_version {
1036 if line.name == "min-cdb-version"
1037 && let Some(rest) = line.value_after_colon().map(str::trim)
1038 {
1039 let min_version = extract_cdb_version(rest).unwrap_or_else(|| {
1040 panic!("couldn't parse version range: {:?}", rest);
1041 });
1042
1043 if actual_version < min_version {
1046 return IgnoreDecision::Ignore {
1047 reason: format!("ignored when the CDB version is lower than {rest}"),
1048 };
1049 }
1050 }
1051 }
1052 IgnoreDecision::Continue
1053}
1054
1055fn ignore_gdb(config: &Config, variant: &TestVariant, line: &DirectiveLine<'_>) -> IgnoreDecision {
1056 if variant.debugger != Some(Debugger::Gdb) {
1057 return if line.name == "only-gdb" {
1058 IgnoreDecision::Ignore { reason: "debugger is not gdb".to_string() }
1059 } else {
1060 IgnoreDecision::Continue
1061 };
1062 }
1063
1064 if line.name == "ignore-gdb" {
1065 return IgnoreDecision::Ignore { reason: "debugger is gdb".to_string() };
1066 }
1067
1068 if let Some(actual_version) = config.gdb_version {
1069 if line.name == "min-gdb-version"
1070 && let Some(rest) = line.value_after_colon().map(str::trim)
1071 {
1072 let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version)
1073 .unwrap_or_else(|| {
1074 panic!("couldn't parse version range: {:?}", rest);
1075 });
1076
1077 if start_ver != end_ver {
1078 panic!("Expected single GDB version")
1079 }
1080 if actual_version < start_ver {
1083 return IgnoreDecision::Ignore {
1084 reason: format!("ignored when the GDB version is lower than {rest}"),
1085 };
1086 }
1087 } else if line.name == "ignore-gdb-version"
1088 && let Some(rest) = line.value_after_colon().map(str::trim)
1089 {
1090 let (min_version, max_version) = extract_version_range(rest, extract_gdb_version)
1091 .unwrap_or_else(|| {
1092 panic!("couldn't parse version range: {:?}", rest);
1093 });
1094
1095 if max_version < min_version {
1096 panic!("Malformed GDB version range: max < min")
1097 }
1098
1099 if actual_version >= min_version && actual_version <= max_version {
1100 if min_version == max_version {
1101 return IgnoreDecision::Ignore {
1102 reason: format!("ignored when the GDB version is {rest}"),
1103 };
1104 } else {
1105 return IgnoreDecision::Ignore {
1106 reason: format!("ignored when the GDB version is between {rest}"),
1107 };
1108 }
1109 }
1110 }
1111 }
1112 IgnoreDecision::Continue
1113}
1114
1115fn ignore_lldb(config: &Config, variant: &TestVariant, line: &DirectiveLine<'_>) -> IgnoreDecision {
1116 if variant.debugger != Some(Debugger::Lldb) {
1117 return if line.name == "only-lldb" {
1118 IgnoreDecision::Ignore { reason: "debugger is not lldb".to_string() }
1119 } else {
1120 IgnoreDecision::Continue
1121 };
1122 }
1123
1124 if line.name == "ignore-lldb" {
1125 return IgnoreDecision::Ignore { reason: "debugger is lldb".to_string() };
1126 }
1127
1128 if let Some(actual_version) = &config.lldb_version {
1129 match (line.name, actual_version) {
1130 ("min-apple-lldb-version", LldbVersion::Apple(vers)) => {
1131 let Some(rest) = line.value_after_colon().map(str::trim) else {
1132 return IgnoreDecision::Continue;
1133 };
1134
1135 let LldbVersion::Apple(min_vers) = LldbVersion::apple_from_str(rest) else {
1136 unreachable!()
1137 };
1138
1139 if vers < &min_vers {
1140 return IgnoreDecision::Ignore {
1141 reason: format!(
1142 "ignored when the Apple LLDB version is {}.{}.{}.{}",
1143 vers[0], vers[1], vers[2], vers[3]
1144 ),
1145 };
1146 }
1147 }
1148 ("min-llvm-lldb-version", LldbVersion::Llvm(vers)) => {
1149 let Some(rest) = line.value_after_colon().map(str::trim) else {
1150 return IgnoreDecision::Continue;
1151 };
1152
1153 let LldbVersion::Llvm(min_vers) = LldbVersion::llvm_from_str(rest) else {
1154 unreachable!()
1155 };
1156
1157 if vers < &min_vers {
1158 return IgnoreDecision::Ignore {
1159 reason: format!(
1160 "ignored when the LLDB version is {}.{}.{}",
1161 vers.major, vers.minor, vers.patch
1162 ),
1163 };
1164 }
1165 }
1166 _ => {}
1167 };
1168 }
1169 IgnoreDecision::Continue
1170}
1171
1172fn ignore_backends(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1173 let path = line.file_path;
1174 if let Some(backends_to_ignore) = config.parse_name_value_directive(line, "ignore-backends") {
1175 for backend in backends_to_ignore.split_whitespace().map(|backend| match backend.parse() {
1176 Ok(backend) => backend,
1177 Err(error) => {
1178 panic!("Invalid ignore-backends value `{backend}` in `{path}`: {error}")
1179 }
1180 }) {
1181 if !config.bypass_ignore_backends && config.default_codegen_backend == backend {
1182 return IgnoreDecision::Ignore {
1183 reason: format!("{} backend is marked as ignore", backend.as_str()),
1184 };
1185 }
1186 }
1187 }
1188 IgnoreDecision::Continue
1189}
1190
1191fn needs_backends(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1192 let path = line.file_path;
1193 if let Some(needed_backends) = config.parse_name_value_directive(line, "needs-backends") {
1194 if !needed_backends
1195 .split_whitespace()
1196 .map(|backend| match backend.parse() {
1197 Ok(backend) => backend,
1198 Err(error) => {
1199 panic!("Invalid needs-backends value `{backend}` in `{path}`: {error}")
1200 }
1201 })
1202 .any(|backend| config.default_codegen_backend == backend)
1203 {
1204 return IgnoreDecision::Ignore {
1205 reason: format!(
1206 "{} backend is not part of required backends",
1207 config.default_codegen_backend.as_str()
1208 ),
1209 };
1210 }
1211 }
1212 IgnoreDecision::Continue
1213}
1214
1215fn ignore_llvm(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1216 let path = line.file_path;
1217 if let Some(needed_components) =
1218 config.parse_name_value_directive(line, "needs-llvm-components")
1219 {
1220 let components: HashSet<_> = config.llvm_components.split_whitespace().collect();
1221 if let Some(missing_component) = needed_components
1222 .split_whitespace()
1223 .find(|needed_component| !components.contains(needed_component))
1224 {
1225 if env::var_os("COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS").is_some() {
1226 panic!(
1227 "missing LLVM component {missing_component}, \
1228 and COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS is set: {path}",
1229 );
1230 }
1231 return IgnoreDecision::Ignore {
1232 reason: format!("ignored when the {missing_component} LLVM component is missing"),
1233 };
1234 }
1235 }
1236 if let Some(actual_version) = &config.llvm_version {
1237 if let Some(version_string) = config.parse_name_value_directive(line, "min-llvm-version") {
1240 let min_version = extract_llvm_version(&version_string);
1241 if *actual_version < min_version {
1243 return IgnoreDecision::Ignore {
1244 reason: format!(
1245 "ignored when the LLVM version {actual_version} is older than {min_version}"
1246 ),
1247 };
1248 }
1249 } else if let Some(version_string) =
1250 config.parse_name_value_directive(line, "max-llvm-major-version")
1251 {
1252 let max_version = extract_llvm_version(&version_string);
1253 if actual_version.major > max_version.major {
1255 return IgnoreDecision::Ignore {
1256 reason: format!(
1257 "ignored when the LLVM version ({actual_version}) is newer than major\
1258 version {}",
1259 max_version.major
1260 ),
1261 };
1262 }
1263 } else if let Some(version_string) =
1264 config.parse_name_value_directive(line, "min-system-llvm-version")
1265 {
1266 let min_version = extract_llvm_version(&version_string);
1267 if config.system_llvm && *actual_version < min_version {
1270 return IgnoreDecision::Ignore {
1271 reason: format!(
1272 "ignored when the system LLVM version {actual_version} is older than {min_version}"
1273 ),
1274 };
1275 }
1276 } else if let Some(version_range) =
1277 config.parse_name_value_directive(line, "ignore-llvm-version")
1278 {
1279 let (v_min, v_max) =
1281 extract_version_range(&version_range, |s| Some(extract_llvm_version(s)))
1282 .unwrap_or_else(|| {
1283 panic!("couldn't parse version range: \"{version_range}\"");
1284 });
1285 if v_max < v_min {
1286 panic!("malformed LLVM version range where {v_max} < {v_min}")
1287 }
1288 if *actual_version >= v_min && *actual_version <= v_max {
1290 if v_min == v_max {
1291 return IgnoreDecision::Ignore {
1292 reason: format!("ignored when the LLVM version is {actual_version}"),
1293 };
1294 } else {
1295 return IgnoreDecision::Ignore {
1296 reason: format!(
1297 "ignored when the LLVM version is between {v_min} and {v_max}"
1298 ),
1299 };
1300 }
1301 }
1302 } else if let Some(version_string) =
1303 config.parse_name_value_directive(line, "exact-llvm-major-version")
1304 {
1305 let version = extract_llvm_version(&version_string);
1307 if actual_version.major != version.major {
1308 return IgnoreDecision::Ignore {
1309 reason: format!(
1310 "ignored when the actual LLVM major version is {}, but the test only targets major version {}",
1311 actual_version.major, version.major
1312 ),
1313 };
1314 }
1315 }
1316 }
1317 IgnoreDecision::Continue
1318}
1319
1320fn ignore_parallel_frontend(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1321 if config.parallel_frontend_enabled()
1322 && config.parse_name_directive(line, "ignore-parallel-frontend")
1323 {
1324 return IgnoreDecision::Ignore {
1325 reason: "ignored when the parallel frontend is enabled".into(),
1326 };
1327 }
1328 IgnoreDecision::Continue
1329}
1330
1331enum IgnoreDecision {
1332 Ignore { reason: String },
1333 Continue,
1334 Error { message: String },
1335}
1336
1337fn parse_edition_range(config: &Config, line: &DirectiveLine<'_>) -> Option<EditionRange> {
1338 let raw = config.parse_name_value_directive(line, "edition")?;
1339 let &DirectiveLine { file_path: testfile, line_number, .. } = line;
1340
1341 if let Some((lower_bound, upper_bound)) = raw.split_once("..") {
1343 Some(match (maybe_parse_edition(lower_bound), maybe_parse_edition(upper_bound)) {
1344 (Some(lower_bound), Some(upper_bound)) if upper_bound <= lower_bound => {
1345 fatal!(
1346 "{testfile}:{line_number}: the left side of `//@ edition` cannot be greater than or equal to the right side"
1347 );
1348 }
1349 (Some(lower_bound), Some(upper_bound)) => {
1350 EditionRange::Range { lower_bound, upper_bound }
1351 }
1352 (Some(lower_bound), None) => EditionRange::RangeFrom(lower_bound),
1353 (None, Some(_)) => {
1354 fatal!(
1355 "{testfile}:{line_number}: `..edition` is not a supported range in `//@ edition`"
1356 );
1357 }
1358 (None, None) => {
1359 fatal!("{testfile}:{line_number}: `..` is not a supported range in `//@ edition`");
1360 }
1361 })
1362 } else {
1363 match maybe_parse_edition(&raw) {
1364 Some(edition) => Some(EditionRange::Exact(edition)),
1365 None => {
1366 fatal!("{testfile}:{line_number}: empty value for `//@ edition`");
1367 }
1368 }
1369 }
1370}
1371
1372fn maybe_parse_edition(mut input: &str) -> Option<Edition> {
1373 input = input.trim();
1374 if input.is_empty() {
1375 return None;
1376 }
1377 Some(parse_edition(input))
1378}
1379
1380#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1381enum EditionRange {
1382 Exact(Edition),
1383 RangeFrom(Edition),
1384 Range {
1386 lower_bound: Edition,
1387 upper_bound: Edition,
1388 },
1389}
1390
1391impl EditionRange {
1392 fn edition_to_test(&self, requested: impl Into<Option<Edition>>) -> Edition {
1393 let min_edition = Edition::Year(2015);
1394 let requested = requested.into().unwrap_or(min_edition);
1395
1396 match *self {
1397 EditionRange::Exact(exact) => exact,
1398 EditionRange::RangeFrom(lower_bound) => {
1399 if requested >= lower_bound {
1400 requested
1401 } else {
1402 lower_bound
1403 }
1404 }
1405 EditionRange::Range { lower_bound, upper_bound } => {
1406 if requested >= lower_bound && requested < upper_bound {
1407 requested
1408 } else {
1409 lower_bound
1410 }
1411 }
1412 }
1413 }
1414}
1415
1416fn split_flags(flags: &str) -> Vec<String> {
1417 flags
1422 .split('\'')
1423 .enumerate()
1424 .flat_map(|(i, f)| if i % 2 == 1 { vec![f] } else { f.split_whitespace().collect() })
1425 .map(move |s| s.to_owned())
1426 .collect::<Vec<_>>()
1427}