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};
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 test_revision: Option<&str>,
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 config.mode == TestMode::DebugInfo {
906 match &config.debugger {
907 Some(Debugger::Cdb) => {
908 if let Some(msg) = check_cdb_support(config) {
909 ignore_message = Some(Cow::Owned(msg));
910 }
911 }
912 Some(Debugger::Gdb) => {
913 if let Some(msg) = check_gdb_support(config) {
914 ignore_message = Some(Cow::Owned(msg));
915 }
916 }
917 Some(Debugger::Lldb) => {
918 if let Some(msg) = check_lldb_support(config) {
919 ignore_message = Some(Cow::Owned(msg));
920 }
921 }
922 None => {}
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(test_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, ln));
962 decision!(ignore_gdb(config, ln));
963 decision!(ignore_lldb(config, 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, line: &DirectiveLine<'_>) -> IgnoreDecision {
1023 if config.debugger != Some(Debugger::Cdb) {
1024 return IgnoreDecision::Continue;
1025 }
1026
1027 if let Some(actual_version) = config.cdb_version {
1028 if line.name == "min-cdb-version"
1029 && let Some(rest) = line.value_after_colon().map(str::trim)
1030 {
1031 let min_version = extract_cdb_version(rest).unwrap_or_else(|| {
1032 panic!("couldn't parse version range: {:?}", rest);
1033 });
1034
1035 if actual_version < min_version {
1038 return IgnoreDecision::Ignore {
1039 reason: format!("ignored when the CDB version is lower than {rest}"),
1040 };
1041 }
1042 }
1043 }
1044 IgnoreDecision::Continue
1045}
1046
1047fn ignore_gdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1048 if config.debugger != Some(Debugger::Gdb) {
1049 return IgnoreDecision::Continue;
1050 }
1051
1052 if let Some(actual_version) = config.gdb_version {
1053 if line.name == "min-gdb-version"
1054 && let Some(rest) = line.value_after_colon().map(str::trim)
1055 {
1056 let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version)
1057 .unwrap_or_else(|| {
1058 panic!("couldn't parse version range: {:?}", rest);
1059 });
1060
1061 if start_ver != end_ver {
1062 panic!("Expected single GDB version")
1063 }
1064 if actual_version < start_ver {
1067 return IgnoreDecision::Ignore {
1068 reason: format!("ignored when the GDB version is lower than {rest}"),
1069 };
1070 }
1071 } else if line.name == "ignore-gdb-version"
1072 && let Some(rest) = line.value_after_colon().map(str::trim)
1073 {
1074 let (min_version, max_version) = extract_version_range(rest, extract_gdb_version)
1075 .unwrap_or_else(|| {
1076 panic!("couldn't parse version range: {:?}", rest);
1077 });
1078
1079 if max_version < min_version {
1080 panic!("Malformed GDB version range: max < min")
1081 }
1082
1083 if actual_version >= min_version && actual_version <= max_version {
1084 if min_version == max_version {
1085 return IgnoreDecision::Ignore {
1086 reason: format!("ignored when the GDB version is {rest}"),
1087 };
1088 } else {
1089 return IgnoreDecision::Ignore {
1090 reason: format!("ignored when the GDB version is between {rest}"),
1091 };
1092 }
1093 }
1094 }
1095 }
1096 IgnoreDecision::Continue
1097}
1098
1099fn ignore_lldb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1100 if config.debugger != Some(Debugger::Lldb) {
1101 return IgnoreDecision::Continue;
1102 }
1103
1104 if let Some(actual_version) = &config.lldb_version {
1105 match (line.name, actual_version) {
1106 ("min-apple-lldb-version", LldbVersion::Apple(vers)) => {
1107 let Some(rest) = line.value_after_colon().map(str::trim) else {
1108 return IgnoreDecision::Continue;
1109 };
1110
1111 let LldbVersion::Apple(min_vers) = LldbVersion::apple_from_str(rest) else {
1112 unreachable!()
1113 };
1114
1115 if vers < &min_vers {
1116 return IgnoreDecision::Ignore {
1117 reason: format!(
1118 "ignored when the Apple LLDB version is {}.{}.{}.{}",
1119 vers[0], vers[1], vers[2], vers[3]
1120 ),
1121 };
1122 }
1123 }
1124 ("min-llvm-lldb-version", LldbVersion::Llvm(vers)) => {
1125 let Some(rest) = line.value_after_colon().map(str::trim) else {
1126 return IgnoreDecision::Continue;
1127 };
1128
1129 let LldbVersion::Llvm(min_vers) = LldbVersion::llvm_from_str(rest) else {
1130 unreachable!()
1131 };
1132
1133 if vers < &min_vers {
1134 return IgnoreDecision::Ignore {
1135 reason: format!(
1136 "ignored when the LLDB version is {}.{}.{}",
1137 vers.major, vers.minor, vers.patch
1138 ),
1139 };
1140 }
1141 }
1142 _ => {}
1143 };
1144 }
1145 IgnoreDecision::Continue
1146}
1147
1148fn ignore_backends(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1149 let path = line.file_path;
1150 if let Some(backends_to_ignore) = config.parse_name_value_directive(line, "ignore-backends") {
1151 for backend in backends_to_ignore.split_whitespace().map(|backend| match backend.parse() {
1152 Ok(backend) => backend,
1153 Err(error) => {
1154 panic!("Invalid ignore-backends value `{backend}` in `{path}`: {error}")
1155 }
1156 }) {
1157 if !config.bypass_ignore_backends && config.default_codegen_backend == backend {
1158 return IgnoreDecision::Ignore {
1159 reason: format!("{} backend is marked as ignore", backend.as_str()),
1160 };
1161 }
1162 }
1163 }
1164 IgnoreDecision::Continue
1165}
1166
1167fn needs_backends(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1168 let path = line.file_path;
1169 if let Some(needed_backends) = config.parse_name_value_directive(line, "needs-backends") {
1170 if !needed_backends
1171 .split_whitespace()
1172 .map(|backend| match backend.parse() {
1173 Ok(backend) => backend,
1174 Err(error) => {
1175 panic!("Invalid needs-backends value `{backend}` in `{path}`: {error}")
1176 }
1177 })
1178 .any(|backend| config.default_codegen_backend == backend)
1179 {
1180 return IgnoreDecision::Ignore {
1181 reason: format!(
1182 "{} backend is not part of required backends",
1183 config.default_codegen_backend.as_str()
1184 ),
1185 };
1186 }
1187 }
1188 IgnoreDecision::Continue
1189}
1190
1191fn ignore_llvm(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1192 let path = line.file_path;
1193 if let Some(needed_components) =
1194 config.parse_name_value_directive(line, "needs-llvm-components")
1195 {
1196 let components: HashSet<_> = config.llvm_components.split_whitespace().collect();
1197 if let Some(missing_component) = needed_components
1198 .split_whitespace()
1199 .find(|needed_component| !components.contains(needed_component))
1200 {
1201 if env::var_os("COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS").is_some() {
1202 panic!(
1203 "missing LLVM component {missing_component}, \
1204 and COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS is set: {path}",
1205 );
1206 }
1207 return IgnoreDecision::Ignore {
1208 reason: format!("ignored when the {missing_component} LLVM component is missing"),
1209 };
1210 }
1211 }
1212 if let Some(actual_version) = &config.llvm_version {
1213 if let Some(version_string) = config.parse_name_value_directive(line, "min-llvm-version") {
1216 let min_version = extract_llvm_version(&version_string);
1217 if *actual_version < min_version {
1219 return IgnoreDecision::Ignore {
1220 reason: format!(
1221 "ignored when the LLVM version {actual_version} is older than {min_version}"
1222 ),
1223 };
1224 }
1225 } else if let Some(version_string) =
1226 config.parse_name_value_directive(line, "max-llvm-major-version")
1227 {
1228 let max_version = extract_llvm_version(&version_string);
1229 if actual_version.major > max_version.major {
1231 return IgnoreDecision::Ignore {
1232 reason: format!(
1233 "ignored when the LLVM version ({actual_version}) is newer than major\
1234 version {}",
1235 max_version.major
1236 ),
1237 };
1238 }
1239 } else if let Some(version_string) =
1240 config.parse_name_value_directive(line, "min-system-llvm-version")
1241 {
1242 let min_version = extract_llvm_version(&version_string);
1243 if config.system_llvm && *actual_version < min_version {
1246 return IgnoreDecision::Ignore {
1247 reason: format!(
1248 "ignored when the system LLVM version {actual_version} is older than {min_version}"
1249 ),
1250 };
1251 }
1252 } else if let Some(version_range) =
1253 config.parse_name_value_directive(line, "ignore-llvm-version")
1254 {
1255 let (v_min, v_max) =
1257 extract_version_range(&version_range, |s| Some(extract_llvm_version(s)))
1258 .unwrap_or_else(|| {
1259 panic!("couldn't parse version range: \"{version_range}\"");
1260 });
1261 if v_max < v_min {
1262 panic!("malformed LLVM version range where {v_max} < {v_min}")
1263 }
1264 if *actual_version >= v_min && *actual_version <= v_max {
1266 if v_min == v_max {
1267 return IgnoreDecision::Ignore {
1268 reason: format!("ignored when the LLVM version is {actual_version}"),
1269 };
1270 } else {
1271 return IgnoreDecision::Ignore {
1272 reason: format!(
1273 "ignored when the LLVM version is between {v_min} and {v_max}"
1274 ),
1275 };
1276 }
1277 }
1278 } else if let Some(version_string) =
1279 config.parse_name_value_directive(line, "exact-llvm-major-version")
1280 {
1281 let version = extract_llvm_version(&version_string);
1283 if actual_version.major != version.major {
1284 return IgnoreDecision::Ignore {
1285 reason: format!(
1286 "ignored when the actual LLVM major version is {}, but the test only targets major version {}",
1287 actual_version.major, version.major
1288 ),
1289 };
1290 }
1291 }
1292 }
1293 IgnoreDecision::Continue
1294}
1295
1296fn ignore_parallel_frontend(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1297 if config.parallel_frontend_enabled()
1298 && config.parse_name_directive(line, "ignore-parallel-frontend")
1299 {
1300 return IgnoreDecision::Ignore {
1301 reason: "ignored when the parallel frontend is enabled".into(),
1302 };
1303 }
1304 IgnoreDecision::Continue
1305}
1306
1307enum IgnoreDecision {
1308 Ignore { reason: String },
1309 Continue,
1310 Error { message: String },
1311}
1312
1313fn parse_edition_range(config: &Config, line: &DirectiveLine<'_>) -> Option<EditionRange> {
1314 let raw = config.parse_name_value_directive(line, "edition")?;
1315 let &DirectiveLine { file_path: testfile, line_number, .. } = line;
1316
1317 if let Some((lower_bound, upper_bound)) = raw.split_once("..") {
1319 Some(match (maybe_parse_edition(lower_bound), maybe_parse_edition(upper_bound)) {
1320 (Some(lower_bound), Some(upper_bound)) if upper_bound <= lower_bound => {
1321 fatal!(
1322 "{testfile}:{line_number}: the left side of `//@ edition` cannot be greater than or equal to the right side"
1323 );
1324 }
1325 (Some(lower_bound), Some(upper_bound)) => {
1326 EditionRange::Range { lower_bound, upper_bound }
1327 }
1328 (Some(lower_bound), None) => EditionRange::RangeFrom(lower_bound),
1329 (None, Some(_)) => {
1330 fatal!(
1331 "{testfile}:{line_number}: `..edition` is not a supported range in `//@ edition`"
1332 );
1333 }
1334 (None, None) => {
1335 fatal!("{testfile}:{line_number}: `..` is not a supported range in `//@ edition`");
1336 }
1337 })
1338 } else {
1339 match maybe_parse_edition(&raw) {
1340 Some(edition) => Some(EditionRange::Exact(edition)),
1341 None => {
1342 fatal!("{testfile}:{line_number}: empty value for `//@ edition`");
1343 }
1344 }
1345 }
1346}
1347
1348fn maybe_parse_edition(mut input: &str) -> Option<Edition> {
1349 input = input.trim();
1350 if input.is_empty() {
1351 return None;
1352 }
1353 Some(parse_edition(input))
1354}
1355
1356#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1357enum EditionRange {
1358 Exact(Edition),
1359 RangeFrom(Edition),
1360 Range {
1362 lower_bound: Edition,
1363 upper_bound: Edition,
1364 },
1365}
1366
1367impl EditionRange {
1368 fn edition_to_test(&self, requested: impl Into<Option<Edition>>) -> Edition {
1369 let min_edition = Edition::Year(2015);
1370 let requested = requested.into().unwrap_or(min_edition);
1371
1372 match *self {
1373 EditionRange::Exact(exact) => exact,
1374 EditionRange::RangeFrom(lower_bound) => {
1375 if requested >= lower_bound {
1376 requested
1377 } else {
1378 lower_bound
1379 }
1380 }
1381 EditionRange::Range { lower_bound, upper_bound } => {
1382 if requested >= lower_bound && requested < upper_bound {
1383 requested
1384 } else {
1385 lower_bound
1386 }
1387 }
1388 }
1389 }
1390}
1391
1392fn split_flags(flags: &str) -> Vec<String> {
1393 flags
1398 .split('\'')
1399 .enumerate()
1400 .flat_map(|(i, f)| if i % 2 == 1 { vec![f] } else { f.split_whitespace().collect() })
1401 .map(move |s| s.to_owned())
1402 .collect::<Vec<_>>()
1403}