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