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