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 if let Some(edition) = self.edition.or(config.edition) {
409 self.compile_flags.insert(0, format!("--edition={edition}"));
412 }
413 }
414
415 fn update_pass_fail_mode(&mut self, ln: &DirectiveLine<'_>, config: &Config) {
416 let name = ln.name;
417 if config.mode != TestMode::Ui {
418 panic!("`{name}` directive is only supported in UI tests");
419 }
420 if self.pass_fail_mode.is_some() {
421 panic!("multiple `*-fail` or `*-pass` directives in a single test");
422 }
423
424 let mode = ln.name.parse::<PassFailMode>().unwrap();
425 self.pass_fail_mode = Some(mode);
426 }
427
428 fn update_add_minicore(&mut self, ln: &DirectiveLine<'_>, config: &Config) {
429 let add_minicore = config.parse_name_directive(ln, directives::ADD_MINICORE);
430 if add_minicore {
431 if !matches!(
432 config.mode,
433 TestMode::Ui | TestMode::Codegen | TestMode::Assembly | TestMode::MirOpt
434 ) {
435 panic!(
436 "`add-minicore` is currently only supported for ui, codegen, assembly and mir-opt test modes"
437 );
438 }
439
440 if self.pass_fail_mode == Some(PassFailMode::RunPass) {
443 panic!("`add-minicore` cannot be used to run the test binary");
446 }
447
448 self.add_minicore = add_minicore;
449 }
450 }
451}
452
453pub(crate) fn do_early_directives_check(
454 mode: TestMode,
455 file_directives: &FileDirectives<'_>,
456) -> Result<(), String> {
457 let testfile = file_directives.path;
458
459 for directive_line @ DirectiveLine { line_number, .. } in &file_directives.lines {
460 let CheckDirectiveResult { is_known_directive, trailing_directive } =
461 check_directive(directive_line, mode);
462
463 if !is_known_directive {
464 return Err(format!(
465 "ERROR: unknown compiletest directive `{directive}` at {testfile}:{line_number}",
466 directive = directive_line.display(),
467 ));
468 }
469
470 if let Some(trailing_directive) = &trailing_directive {
471 return Err(format!(
472 "ERROR: detected trailing compiletest directive `{trailing_directive}` at {testfile}:{line_number}\n\
473 HELP: put the directive on its own line: `//@ {trailing_directive}`"
474 ));
475 }
476 }
477
478 Ok(())
479}
480
481pub(crate) struct CheckDirectiveResult<'ln> {
482 is_known_directive: bool,
483 trailing_directive: Option<&'ln str>,
484}
485
486fn check_directive<'a>(
487 directive_ln: &DirectiveLine<'a>,
488 mode: TestMode,
489) -> CheckDirectiveResult<'a> {
490 let &DirectiveLine { name: directive_name, .. } = directive_ln;
491
492 let is_known_directive = KNOWN_DIRECTIVE_NAMES_SET.contains(&directive_name)
493 || match mode {
494 TestMode::RustdocHtml => KNOWN_HTMLDOCCK_DIRECTIVE_NAMES.contains(&directive_name),
495 TestMode::RustdocJson => KNOWN_JSONDOCCK_DIRECTIVE_NAMES.contains(&directive_name),
496 _ => false,
497 };
498
499 let trailing_directive = directive_ln
503 .remark_after_space()
504 .map(|remark| remark.trim_start().split(' ').next().unwrap())
505 .filter(|token| KNOWN_DIRECTIVE_NAMES_SET.contains(token));
506
507 CheckDirectiveResult { is_known_directive, trailing_directive }
513}
514
515fn iter_directives(
516 config: &Config,
517 file_directives: &FileDirectives<'_>,
518 it: &mut dyn FnMut(&DirectiveLine<'_>),
519) {
520 let testfile = file_directives.path;
521
522 let extra_directives = match config.mode {
523 TestMode::CoverageRun => {
524 vec![
529 "//@ needs-profiler-runtime",
530 "//@ ignore-cross-compile",
534 ]
535 }
536 TestMode::Codegen if !file_directives.has_explicit_no_std_core_attribute => {
537 vec!["//@ needs-target-std"]
544 }
545 TestMode::Ui if config.parallel_frontend_enabled() => {
546 vec!["//@ compare-output-by-lines"]
549 }
550
551 _ => {
552 vec![]
554 }
555 };
556
557 for directive_str in extra_directives {
558 let directive_line = line_directive(testfile, LineNumber::ZERO, directive_str)
559 .unwrap_or_else(|| panic!("bad extra-directive line: {directive_str:?}"));
560 it(&directive_line);
561 }
562
563 for directive_line in &file_directives.lines {
564 it(directive_line);
565 }
566}
567
568impl Config {
569 fn parse_and_update_revisions(&self, line: &DirectiveLine<'_>, existing: &mut Vec<String>) {
570 const FORBIDDEN_REVISION_NAMES: [&str; 2] = [
571 "true", "false",
575 ];
576
577 const FILECHECK_FORBIDDEN_REVISION_NAMES: [&str; 9] =
578 ["CHECK", "COM", "NEXT", "SAME", "EMPTY", "NOT", "COUNT", "DAG", "LABEL"];
579
580 if let Some(raw) = self.parse_name_value_directive(line, "revisions") {
581 let &DirectiveLine { file_path: testfile, .. } = line;
582
583 if self.mode == TestMode::RunMake {
584 panic!("`run-make` mode tests do not support revisions: {}", testfile);
585 }
586
587 let mut duplicates: HashSet<_> = existing.iter().cloned().collect();
588 for revision in raw.split_whitespace() {
589 if !duplicates.insert(revision.to_string()) {
590 panic!("duplicate revision: `{}` in line `{}`: {}", revision, raw, testfile);
591 }
592
593 if FORBIDDEN_REVISION_NAMES.contains(&revision) {
594 panic!(
595 "revision name `{revision}` is not permitted: `{}` in line `{}`: {}",
596 revision, raw, testfile
597 );
598 }
599
600 if matches!(self.mode, TestMode::Assembly | TestMode::Codegen | TestMode::MirOpt)
601 && FILECHECK_FORBIDDEN_REVISION_NAMES.contains(&revision)
602 {
603 panic!(
604 "revision name `{revision}` is not permitted in a test suite that uses \
605 `FileCheck` annotations as it is confusing when used as custom `FileCheck` \
606 prefix: `{revision}` in line `{}`: {}",
607 raw, testfile
608 );
609 }
610
611 existing.push(revision.to_string());
612 }
613 }
614 }
615
616 fn parse_env(nv: String) -> (String, String) {
617 let (name, value) = nv.split_once('=').unwrap_or((&nv, ""));
621 let name = name.trim();
624 (name.to_owned(), value.to_owned())
625 }
626
627 fn parse_pp_exact(&self, line: &DirectiveLine<'_>) -> Option<Utf8PathBuf> {
628 if line.value_after_colon().is_some()
631 && let Some(s) = self.parse_name_value_directive(line, "pp-exact")
632 {
633 Some(Utf8PathBuf::from(&s))
634 } else if self.parse_name_directive(line, "pp-exact") {
635 line.file_path.file_name().map(Utf8PathBuf::from)
636 } else {
637 None
638 }
639 }
640
641 fn parse_custom_normalization(&self, line: &DirectiveLine<'_>) -> Option<NormalizeRule> {
642 let &DirectiveLine { name, .. } = line;
643
644 let kind = match name {
645 "normalize-stdout" => NormalizeKind::Stdout,
646 "normalize-stderr" => NormalizeKind::Stderr,
647 "normalize-stderr-32bit" => NormalizeKind::Stderr32bit,
648 "normalize-stderr-64bit" => NormalizeKind::Stderr64bit,
649 _ => return None,
650 };
651
652 let Some((regex, replacement)) = line.value_after_colon().and_then(parse_normalize_rule)
653 else {
654 error!("couldn't parse custom normalization rule: `{}`", line.display());
655 help!("expected syntax is: `{name}: \"REGEX\" -> \"REPLACEMENT\"`");
656 panic!("invalid normalization rule detected");
657 };
658 Some(NormalizeRule { kind, regex, replacement })
659 }
660
661 fn parse_name_directive(&self, line: &DirectiveLine<'_>, directive: &str) -> bool {
662 if line.name != directive {
663 return false;
664 }
665
666 if line.value_after_colon().is_some() {
667 let &DirectiveLine { file_path, line_number, .. } = line;
668 panic!(
669 "{file_path}:{line_number}: directive `{directive}` must not be followed by a colon"
670 );
671 }
672 true
673 }
674
675 fn parse_name_value_directive(
676 &self,
677 line: &DirectiveLine<'_>,
678 directive: &str,
679 ) -> Option<String> {
680 let &DirectiveLine { file_path, line_number, .. } = line;
681
682 if line.name != directive {
683 return None;
684 };
685
686 let value = line.value_after_colon().unwrap_or_else(|| {
687 panic!("{file_path}:{line_number}: directive `{directive}` must be followed by a colon and value");
688 });
689 debug!("{}: {}", directive, value);
690 let value = expand_variables(value.to_owned(), self);
691
692 if value.is_empty() {
693 error!("{file_path}:{line_number}: empty value for directive `{directive}`");
694 help!("expected syntax is: `{directive}: value`");
695 panic!("empty directive value detected");
696 }
697
698 Some(value)
699 }
700
701 fn set_name_directive(&self, line: &DirectiveLine<'_>, directive: &str, value: &mut bool) {
702 *value = *value || self.parse_name_directive(line, directive);
704 }
705
706 fn set_name_value_directive<T>(
707 &self,
708 line: &DirectiveLine<'_>,
709 directive: &str,
710 value: &mut Option<T>,
711 parse: impl FnOnce(String) -> T,
712 ) {
713 if value.is_none() {
714 *value = self.parse_name_value_directive(line, directive).map(parse);
715 }
716 }
717
718 fn push_name_value_directive<T>(
719 &self,
720 line: &DirectiveLine<'_>,
721 directive: &str,
722 values: &mut Vec<T>,
723 parse: impl FnOnce(String) -> T,
724 ) {
725 if let Some(value) = self.parse_name_value_directive(line, directive).map(parse) {
726 values.push(value);
727 }
728 }
729}
730
731fn expand_variables(mut value: String, config: &Config) -> String {
733 const CWD: &str = "{{cwd}}";
734 const SRC_BASE: &str = "{{src-base}}";
735 const TEST_SUITE_BUILD_BASE: &str = "{{build-base}}";
736 const RUST_SRC_BASE: &str = "{{rust-src-base}}";
737 const SYSROOT_BASE: &str = "{{sysroot-base}}";
738 const TARGET_LINKER: &str = "{{target-linker}}";
739 const TARGET: &str = "{{target}}";
740
741 if value.contains(CWD) {
742 let cwd = env::current_dir().unwrap();
743 value = value.replace(CWD, &cwd.to_str().unwrap());
744 }
745
746 if value.contains(SRC_BASE) {
747 value = value.replace(SRC_BASE, &config.src_test_suite_root.as_str());
748 }
749
750 if value.contains(TEST_SUITE_BUILD_BASE) {
751 value = value.replace(TEST_SUITE_BUILD_BASE, &config.build_test_suite_root.as_str());
752 }
753
754 if value.contains(SYSROOT_BASE) {
755 value = value.replace(SYSROOT_BASE, &config.sysroot_base.as_str());
756 }
757
758 if value.contains(TARGET_LINKER) {
759 value = value.replace(TARGET_LINKER, config.target_linker.as_deref().unwrap_or(""));
760 }
761
762 if value.contains(TARGET) {
763 value = value.replace(TARGET, &config.target);
764 }
765
766 if value.contains(RUST_SRC_BASE) {
767 let src_base = config.sysroot_base.join("lib/rustlib/src/rust");
768 src_base.try_exists().expect(&*format!("{} should exists", src_base));
769 let src_base = src_base.read_link_utf8().unwrap_or(src_base);
770 value = value.replace(RUST_SRC_BASE, &src_base.as_str());
771 }
772
773 value
774}
775
776struct NormalizeRule {
777 kind: NormalizeKind,
778 regex: String,
779 replacement: String,
780}
781
782enum NormalizeKind {
783 Stdout,
784 Stderr,
785 Stderr32bit,
786 Stderr64bit,
787}
788
789fn parse_normalize_rule(raw_value: &str) -> Option<(String, String)> {
794 let captures = static_regex!(
796 r#"(?x) # (verbose mode regex)
797 ^
798 \s* # (leading whitespace)
799 "(?<regex>[^"]*)" # "REGEX"
800 \s+->\s+ # ->
801 "(?<replacement>[^"]*)" # "REPLACEMENT"
802 $
803 "#
804 )
805 .captures(raw_value)?;
806 let regex = captures["regex"].to_owned();
807 let replacement = captures["replacement"].to_owned();
808 let replacement = replacement.replace("\\n", "\n");
812 Some((regex, replacement))
813}
814
815pub(crate) fn extract_llvm_version(version: &str) -> Version {
825 let version = version.trim();
828 let uninterested = |c: char| !c.is_ascii_digit() && c != '.';
829 let version_without_suffix = match version.split_once(uninterested) {
830 Some((prefix, _suffix)) => prefix,
831 None => version,
832 };
833
834 let components: Vec<u64> = version_without_suffix
835 .split('.')
836 .map(|s| s.parse().expect("llvm version component should consist of only digits"))
837 .collect();
838
839 match &components[..] {
840 [major] => Version::new(*major, 0, 0),
841 [major, minor] => Version::new(*major, *minor, 0),
842 [major, minor, patch] => Version::new(*major, *minor, *patch),
843 _ => panic!("malformed llvm version string, expected only 1-3 components: {version}"),
844 }
845}
846
847pub(crate) fn extract_llvm_version_from_binary(binary_path: &str) -> Option<Version> {
848 let output = Command::new(binary_path).arg("--version").output().ok()?;
849 if !output.status.success() {
850 return None;
851 }
852 let version = String::from_utf8(output.stdout).ok()?;
853 for line in version.lines() {
854 if let Some(version) = line.split("LLVM version ").nth(1) {
855 return Some(extract_llvm_version(version));
856 }
857 }
858 None
859}
860
861fn extract_version_range<'a, F, VersionTy: Clone>(
867 line: &'a str,
868 parse: F,
869) -> Option<(VersionTy, VersionTy)>
870where
871 F: Fn(&'a str) -> Option<VersionTy>,
872{
873 let mut splits = line.splitn(2, "- ").map(str::trim);
874 let min = splits.next().unwrap();
875 if min.ends_with('-') {
876 return None;
877 }
878
879 let max = splits.next();
880
881 if min.is_empty() {
882 return None;
883 }
884
885 let min = parse(min)?;
886 let max = match max {
887 Some("") => return None,
888 Some(max) => parse(max)?,
889 _ => min.clone(),
890 };
891
892 Some((min, max))
893}
894
895pub(crate) fn make_test_description(
896 config: &Config,
897 cache: &DirectivesCache,
898 name: String,
899 path: &Utf8Path,
900 filterable_path: &Utf8Path,
901 file_directives: &FileDirectives<'_>,
902 variant: &TestVariant,
903 poisoned: &mut bool,
904 aux_props: &mut AuxProps,
905) -> CollectedTestDesc {
906 let mut ignore_message: Option<Cow<'static, str>> = None;
907 let mut should_fail = false;
908
909 if let Some(debugger) = variant.debugger.as_ref() {
914 match debugger {
915 Debugger::Cdb => {
916 if let Some(msg) = check_cdb_support(config) {
917 ignore_message = Some(Cow::Owned(msg));
918 }
919 }
920 Debugger::Gdb => {
921 if let Some(msg) = check_gdb_support(config) {
922 ignore_message = Some(Cow::Owned(msg));
923 }
924 }
925 Debugger::Lldb => {
926 if let Some(msg) = check_lldb_support(config) {
927 ignore_message = Some(Cow::Owned(msg));
928 }
929 }
930 }
931 }
932
933 if ignore_message.is_none() {
934 iter_directives(
936 config,
937 file_directives,
938 &mut |ln @ &DirectiveLine { line_number, .. }| {
939 if !ln.applies_to_test_revision(variant.revision()) {
940 return;
941 }
942
943 parse_and_update_aux(config, ln, aux_props);
945
946 macro_rules! decision {
947 ($e:expr) => {
948 match $e {
949 IgnoreDecision::Ignore { reason } => {
950 ignore_message = Some(reason.into());
951 }
952 IgnoreDecision::Error { message } => {
953 error!("{path}:{line_number}: {message}");
954 *poisoned = true;
955 return;
956 }
957 IgnoreDecision::Continue => {}
958 }
959 };
960 }
961
962 decision!(cfg::handle_ignore(&cache.cfg_conditions, ln));
963 decision!(cfg::handle_only(&cache.cfg_conditions, ln));
964 decision!(needs::handle_needs(&cache.needs, config, ln));
965 decision!(ignore_llvm(config, ln));
966 decision!(ignore_backends(config, ln));
967 decision!(needs_backends(config, ln));
968 decision!(ignore_unsupported_backend_target(config, ln));
969 decision!(ignore_cdb(config, variant, ln));
970 decision!(ignore_gdb(config, variant, ln));
971 decision!(ignore_lldb(config, variant, ln));
972 decision!(ignore_parallel_frontend(config, ln));
973
974 if config.target == "wasm32-unknown-unknown"
975 && config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS)
976 {
977 decision!(IgnoreDecision::Ignore {
978 reason: "ignored on WASM as the run results cannot be checked there".into(),
979 });
980 }
981
982 should_fail |= config.parse_name_directive(ln, "should-fail");
983 },
984 );
985 }
986
987 let should_fail = if should_fail && config.mode != TestMode::Pretty {
991 ShouldFail::Yes
992 } else {
993 ShouldFail::No
994 };
995
996 CollectedTestDesc {
997 name,
998 filterable_path: filterable_path.to_owned(),
999 ignore_message,
1000 should_fail,
1001 }
1002}
1003
1004fn check_cdb_support(config: &Config) -> Option<String> {
1006 if config.cdb.is_none() { Some("cdb is not available".to_string()) } else { None }
1007}
1008
1009fn check_gdb_support(config: &Config) -> Option<String> {
1011 if config.gdb_version.is_none() {
1012 return Some("gdb is not available".to_string());
1013 }
1014
1015 if config.matches_env("msvc") {
1016 return Some("gdb tests do not run on msvc".to_string());
1017 }
1018
1019 if config.remote_test_client.is_some() && !config.target.contains("android") {
1020 return Some("gdb tests are not available when testing with remote".to_string());
1021 }
1022 None
1023}
1024
1025fn check_lldb_support(config: &Config) -> Option<String> {
1027 if config.lldb.is_none() { Some("lldb is not available".to_string()) } else { None }
1028}
1029
1030fn ignore_cdb(config: &Config, variant: &TestVariant, line: &DirectiveLine<'_>) -> IgnoreDecision {
1031 if variant.debugger != Some(Debugger::Cdb) {
1032 return if line.name == "only-cdb" {
1033 IgnoreDecision::Ignore { reason: "debugger is not cdb".to_string() }
1034 } else {
1035 IgnoreDecision::Continue
1036 };
1037 }
1038
1039 if line.name == "ignore-cdb" {
1040 return IgnoreDecision::Ignore { reason: "debugger is cdb".to_string() };
1041 }
1042
1043 if let Some(actual_version) = config.cdb_version {
1044 if line.name == "min-cdb-version"
1045 && let Some(rest) = line.value_after_colon().map(str::trim)
1046 {
1047 let min_version = extract_cdb_version(rest).unwrap_or_else(|| {
1048 panic!("couldn't parse version range: {:?}", rest);
1049 });
1050
1051 if actual_version < min_version {
1054 return IgnoreDecision::Ignore {
1055 reason: format!("ignored when the CDB version is lower than {rest}"),
1056 };
1057 }
1058 }
1059 }
1060 IgnoreDecision::Continue
1061}
1062
1063fn ignore_gdb(config: &Config, variant: &TestVariant, line: &DirectiveLine<'_>) -> IgnoreDecision {
1064 if variant.debugger != Some(Debugger::Gdb) {
1065 return if line.name == "only-gdb" {
1066 IgnoreDecision::Ignore { reason: "debugger is not gdb".to_string() }
1067 } else {
1068 IgnoreDecision::Continue
1069 };
1070 }
1071
1072 if line.name == "ignore-gdb" {
1073 return IgnoreDecision::Ignore { reason: "debugger is gdb".to_string() };
1074 }
1075
1076 if let Some(actual_version) = config.gdb_version {
1077 if line.name == "min-gdb-version"
1078 && let Some(rest) = line.value_after_colon().map(str::trim)
1079 {
1080 let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version)
1081 .unwrap_or_else(|| {
1082 panic!("couldn't parse version range: {:?}", rest);
1083 });
1084
1085 if start_ver != end_ver {
1086 panic!("Expected single GDB version")
1087 }
1088 if actual_version < start_ver {
1091 return IgnoreDecision::Ignore {
1092 reason: format!("ignored when the GDB version is lower than {rest}"),
1093 };
1094 }
1095 } else if line.name == "ignore-gdb-version"
1096 && let Some(rest) = line.value_after_colon().map(str::trim)
1097 {
1098 let (min_version, max_version) = extract_version_range(rest, extract_gdb_version)
1099 .unwrap_or_else(|| {
1100 panic!("couldn't parse version range: {:?}", rest);
1101 });
1102
1103 if max_version < min_version {
1104 panic!("Malformed GDB version range: max < min")
1105 }
1106
1107 if actual_version >= min_version && actual_version <= max_version {
1108 if min_version == max_version {
1109 return IgnoreDecision::Ignore {
1110 reason: format!("ignored when the GDB version is {rest}"),
1111 };
1112 } else {
1113 return IgnoreDecision::Ignore {
1114 reason: format!("ignored when the GDB version is between {rest}"),
1115 };
1116 }
1117 }
1118 }
1119 }
1120 IgnoreDecision::Continue
1121}
1122
1123fn ignore_lldb(config: &Config, variant: &TestVariant, line: &DirectiveLine<'_>) -> IgnoreDecision {
1124 if variant.debugger != Some(Debugger::Lldb) {
1125 return if line.name == "only-lldb" {
1126 IgnoreDecision::Ignore { reason: "debugger is not lldb".to_string() }
1127 } else {
1128 IgnoreDecision::Continue
1129 };
1130 }
1131
1132 if line.name == "ignore-lldb" {
1133 return IgnoreDecision::Ignore { reason: "debugger is lldb".to_string() };
1134 }
1135
1136 if let Some(actual_version) = &config.lldb_version {
1137 match (line.name, actual_version) {
1138 ("min-apple-lldb-version", LldbVersion::Apple(vers)) => {
1139 let Some(rest) = line.value_after_colon().map(str::trim) else {
1140 return IgnoreDecision::Continue;
1141 };
1142
1143 let LldbVersion::Apple(min_vers) = LldbVersion::apple_from_str(rest) else {
1144 unreachable!()
1145 };
1146
1147 if vers < &min_vers {
1148 return IgnoreDecision::Ignore {
1149 reason: format!(
1150 "ignored when the Apple LLDB version is {}.{}.{}.{}",
1151 vers[0], vers[1], vers[2], vers[3]
1152 ),
1153 };
1154 }
1155 }
1156 ("min-llvm-lldb-version", LldbVersion::Llvm(vers)) => {
1157 let Some(rest) = line.value_after_colon().map(str::trim) else {
1158 return IgnoreDecision::Continue;
1159 };
1160
1161 let LldbVersion::Llvm(min_vers) = LldbVersion::llvm_from_str(rest) else {
1162 unreachable!()
1163 };
1164
1165 if vers < &min_vers {
1166 return IgnoreDecision::Ignore {
1167 reason: format!(
1168 "ignored when the LLDB version is {}.{}.{}",
1169 vers.major, vers.minor, vers.patch
1170 ),
1171 };
1172 }
1173 }
1174 _ => {}
1175 };
1176 }
1177 IgnoreDecision::Continue
1178}
1179
1180fn ignore_backends(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1181 let path = line.file_path;
1182 if let Some(backends_to_ignore) = config.parse_name_value_directive(line, "ignore-backends") {
1183 for backend in backends_to_ignore.split_whitespace().map(|backend| match backend.parse() {
1184 Ok(backend) => backend,
1185 Err(error) => {
1186 panic!("Invalid ignore-backends value `{backend}` in `{path}`: {error}")
1187 }
1188 }) {
1189 if !config.bypass_ignore_backends && config.default_codegen_backend == backend {
1190 return IgnoreDecision::Ignore {
1191 reason: format!("{} backend is marked as ignore", backend.as_str()),
1192 };
1193 }
1194 }
1195 }
1196 IgnoreDecision::Continue
1197}
1198
1199fn needs_backends(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1200 let path = line.file_path;
1201 if let Some(needed_backends) = config.parse_name_value_directive(line, "needs-backends") {
1202 if !needed_backends
1203 .split_whitespace()
1204 .map(|backend| match backend.parse() {
1205 Ok(backend) => backend,
1206 Err(error) => {
1207 panic!("Invalid needs-backends value `{backend}` in `{path}`: {error}")
1208 }
1209 })
1210 .any(|backend| config.default_codegen_backend == backend)
1211 {
1212 return IgnoreDecision::Ignore {
1213 reason: format!(
1214 "{} backend is not part of required backends",
1215 config.default_codegen_backend.as_str()
1216 ),
1217 };
1218 }
1219 }
1220 IgnoreDecision::Continue
1221}
1222
1223fn ignore_unsupported_backend_target(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1225 if config.default_codegen_backend != crate::CodegenBackend::Gcc {
1226 return IgnoreDecision::Continue;
1227 }
1228
1229 let Some(compile_flags) = config.parse_name_value_directive(line, "compile-flags") else {
1230 return IgnoreDecision::Continue;
1231 };
1232
1233 let Some((_, rest)) = compile_flags.split_once("--target") else {
1235 return IgnoreDecision::Continue;
1236 };
1237 let Some(target) = rest.trim_start_matches([' ', '=']).split_whitespace().next() else {
1238 return IgnoreDecision::Continue;
1239 };
1240
1241 if target != "x86_64-unknown-linux-gnu" {
1242 IgnoreDecision::Ignore {
1243 reason: format!(
1244 "backend `{}` cannot build for target `{target}`",
1245 config.default_codegen_backend.as_str()
1246 ),
1247 }
1248 } else {
1249 IgnoreDecision::Continue
1250 }
1251}
1252
1253fn ignore_llvm(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1254 let path = line.file_path;
1255 if let Some(needed_components) =
1256 config.parse_name_value_directive(line, "needs-llvm-components")
1257 {
1258 let components: HashSet<_> = config.llvm_components.split_whitespace().collect();
1259 if let Some(missing_component) = needed_components
1260 .split_whitespace()
1261 .find(|needed_component| !components.contains(needed_component))
1262 {
1263 if env::var_os("COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS").is_some() {
1264 panic!(
1265 "missing LLVM component {missing_component}, \
1266 and COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS is set: {path}",
1267 );
1268 }
1269 return IgnoreDecision::Ignore {
1270 reason: format!("ignored when the {missing_component} LLVM component is missing"),
1271 };
1272 }
1273 }
1274 if let Some(actual_version) = &config.llvm_version {
1275 if let Some(version_string) = config.parse_name_value_directive(line, "min-llvm-version") {
1278 let min_version = extract_llvm_version(&version_string);
1279 if *actual_version < min_version {
1281 return IgnoreDecision::Ignore {
1282 reason: format!(
1283 "ignored when the LLVM version {actual_version} is older than {min_version}"
1284 ),
1285 };
1286 }
1287 } else if let Some(version_string) =
1288 config.parse_name_value_directive(line, "max-llvm-major-version")
1289 {
1290 let max_version = extract_llvm_version(&version_string);
1291 if actual_version.major > max_version.major {
1293 return IgnoreDecision::Ignore {
1294 reason: format!(
1295 "ignored when the LLVM version ({actual_version}) is newer than major\
1296 version {}",
1297 max_version.major
1298 ),
1299 };
1300 }
1301 } else if let Some(version_string) =
1302 config.parse_name_value_directive(line, "min-system-llvm-version")
1303 {
1304 let min_version = extract_llvm_version(&version_string);
1305 if config.system_llvm && *actual_version < min_version {
1308 return IgnoreDecision::Ignore {
1309 reason: format!(
1310 "ignored when the system LLVM version {actual_version} is older than {min_version}"
1311 ),
1312 };
1313 }
1314 } else if let Some(version_range) =
1315 config.parse_name_value_directive(line, "ignore-llvm-version")
1316 {
1317 let (v_min, v_max) =
1319 extract_version_range(&version_range, |s| Some(extract_llvm_version(s)))
1320 .unwrap_or_else(|| {
1321 panic!("couldn't parse version range: \"{version_range}\"");
1322 });
1323 if v_max < v_min {
1324 panic!("malformed LLVM version range where {v_max} < {v_min}")
1325 }
1326 if *actual_version >= v_min && *actual_version <= v_max {
1328 if v_min == v_max {
1329 return IgnoreDecision::Ignore {
1330 reason: format!("ignored when the LLVM version is {actual_version}"),
1331 };
1332 } else {
1333 return IgnoreDecision::Ignore {
1334 reason: format!(
1335 "ignored when the LLVM version is between {v_min} and {v_max}"
1336 ),
1337 };
1338 }
1339 }
1340 } else if let Some(version_string) =
1341 config.parse_name_value_directive(line, "exact-llvm-major-version")
1342 {
1343 let version = extract_llvm_version(&version_string);
1345 if actual_version.major != version.major {
1346 return IgnoreDecision::Ignore {
1347 reason: format!(
1348 "ignored when the actual LLVM major version is {}, but the test only targets major version {}",
1349 actual_version.major, version.major
1350 ),
1351 };
1352 }
1353 }
1354 }
1355 IgnoreDecision::Continue
1356}
1357
1358fn ignore_parallel_frontend(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1359 if config.parallel_frontend_enabled()
1360 && config.parse_name_directive(line, "ignore-parallel-frontend")
1361 {
1362 return IgnoreDecision::Ignore {
1363 reason: "ignored when the parallel frontend is enabled".into(),
1364 };
1365 }
1366 IgnoreDecision::Continue
1367}
1368
1369enum IgnoreDecision {
1370 Ignore { reason: String },
1371 Continue,
1372 Error { message: String },
1373}
1374
1375fn parse_edition_range(config: &Config, line: &DirectiveLine<'_>) -> Option<EditionRange> {
1376 let raw = config.parse_name_value_directive(line, "edition")?;
1377 let &DirectiveLine { file_path: testfile, line_number, .. } = line;
1378
1379 if let Some((lower_bound, upper_bound)) = raw.split_once("..") {
1381 Some(match (maybe_parse_edition(lower_bound), maybe_parse_edition(upper_bound)) {
1382 (Some(lower_bound), Some(upper_bound)) if upper_bound <= lower_bound => {
1383 fatal!(
1384 "{testfile}:{line_number}: the left side of `//@ edition` cannot be greater than or equal to the right side"
1385 );
1386 }
1387 (Some(lower_bound), Some(upper_bound)) => {
1388 EditionRange::Range { lower_bound, upper_bound }
1389 }
1390 (Some(lower_bound), None) => EditionRange::RangeFrom(lower_bound),
1391 (None, Some(_)) => {
1392 fatal!(
1393 "{testfile}:{line_number}: `..edition` is not a supported range in `//@ edition`"
1394 );
1395 }
1396 (None, None) => {
1397 fatal!("{testfile}:{line_number}: `..` is not a supported range in `//@ edition`");
1398 }
1399 })
1400 } else {
1401 match maybe_parse_edition(&raw) {
1402 Some(edition) => Some(EditionRange::Exact(edition)),
1403 None => {
1404 fatal!("{testfile}:{line_number}: empty value for `//@ edition`");
1405 }
1406 }
1407 }
1408}
1409
1410fn maybe_parse_edition(mut input: &str) -> Option<Edition> {
1411 input = input.trim();
1412 if input.is_empty() {
1413 return None;
1414 }
1415 Some(parse_edition(input))
1416}
1417
1418#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1419enum EditionRange {
1420 Exact(Edition),
1421 RangeFrom(Edition),
1422 Range {
1424 lower_bound: Edition,
1425 upper_bound: Edition,
1426 },
1427}
1428
1429impl EditionRange {
1430 fn edition_to_test(&self, requested: impl Into<Option<Edition>>) -> Edition {
1431 let min_edition = Edition::Year(2015);
1432 let requested = requested.into().unwrap_or(min_edition);
1433
1434 match *self {
1435 EditionRange::Exact(exact) => exact,
1436 EditionRange::RangeFrom(lower_bound) => {
1437 if requested >= lower_bound {
1438 requested
1439 } else {
1440 lower_bound
1441 }
1442 }
1443 EditionRange::Range { lower_bound, upper_bound } => {
1444 if requested >= lower_bound && requested < upper_bound {
1445 requested
1446 } else {
1447 lower_bound
1448 }
1449 }
1450 }
1451 }
1452}
1453
1454fn split_flags(flags: &str) -> Vec<String> {
1455 flags
1460 .split('\'')
1461 .enumerate()
1462 .flat_map(|(i, f)| if i % 2 == 1 { vec![f] } else { f.split_whitespace().collect() })
1463 .map(move |s| s.to_owned())
1464 .collect::<Vec<_>>()
1465}