1use std::collections::HashSet;
2use std::process::Command;
3use std::{env, fs};
4
5use camino::{Utf8Path, Utf8PathBuf};
6use semver::Version;
7use tracing::*;
8
9use crate::common::{CodegenBackend, Config, Debugger, FailMode, PassMode, RunFailMode, TestMode};
10use crate::debuggers::{extract_cdb_version, extract_gdb_version};
11pub(crate) use crate::directives::auxiliary::AuxProps;
12use crate::directives::auxiliary::parse_and_update_aux;
13use crate::directives::directive_names::{
14 KNOWN_DIRECTIVE_NAMES_SET, KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, KNOWN_JSONDOCCK_DIRECTIVE_NAMES,
15};
16pub(crate) use crate::directives::file::FileDirectives;
17use crate::directives::handlers::DIRECTIVE_HANDLERS_MAP;
18use crate::directives::line::{DirectiveLine, line_directive};
19use crate::directives::needs::CachedNeedsConditions;
20use crate::edition::{Edition, parse_edition};
21use crate::errors::ErrorKind;
22use crate::executor::{CollectedTestDesc, ShouldFail};
23use crate::util::static_regex;
24use crate::{fatal, help};
25
26mod auxiliary;
27mod cfg;
28mod directive_names;
29mod file;
30mod handlers;
31mod line;
32mod needs;
33#[cfg(test)]
34mod tests;
35
36pub struct DirectivesCache {
37 needs: CachedNeedsConditions,
38}
39
40impl DirectivesCache {
41 pub fn load(config: &Config) -> Self {
42 Self { needs: CachedNeedsConditions::load(config) }
43 }
44}
45
46#[derive(Default)]
49pub(crate) struct EarlyProps {
50 pub(crate) revisions: Vec<String>,
51}
52
53impl EarlyProps {
54 pub(crate) fn from_file_directives(
55 config: &Config,
56 file_directives: &FileDirectives<'_>,
57 ) -> Self {
58 let mut props = EarlyProps::default();
59
60 iter_directives(
61 config.mode,
62 file_directives,
63 &mut |ln: &DirectiveLine<'_>| {
65 config.parse_and_update_revisions(ln, &mut props.revisions);
66 },
67 );
68
69 props
70 }
71}
72
73#[derive(Clone, Debug)]
74pub(crate) struct TestProps {
75 pub error_patterns: Vec<String>,
77 pub regex_error_patterns: Vec<String>,
79 pub edition: Option<Edition>,
83 pub compile_flags: Vec<String>,
85 pub run_flags: Vec<String>,
87 pub doc_flags: Vec<String>,
89 pub pp_exact: Option<Utf8PathBuf>,
92 pub(crate) aux: AuxProps,
94 pub rustc_env: Vec<(String, String)>,
96 pub unset_rustc_env: Vec<String>,
99 pub exec_env: Vec<(String, String)>,
101 pub unset_exec_env: Vec<String>,
104 pub build_aux_docs: bool,
106 pub unique_doc_out_dir: bool,
109 pub force_host: bool,
111 pub check_stdout: bool,
113 pub check_run_results: bool,
115 pub dont_check_compiler_stdout: bool,
117 pub dont_check_compiler_stderr: bool,
119 pub no_prefer_dynamic: bool,
125 pub pretty_mode: String,
127 pub pretty_compare_only: bool,
129 pub forbid_output: Vec<String>,
131 pub revisions: Vec<String>,
133 pub incremental_dir: Option<Utf8PathBuf>,
138 pub incremental: bool,
153 pub known_bug: bool,
159 pass_mode: Option<PassMode>,
161 ignore_pass: bool,
163 pub fail_mode: Option<FailMode>,
165 pub check_test_line_numbers_match: bool,
167 pub normalize_stdout: Vec<(String, String)>,
169 pub normalize_stderr: Vec<(String, String)>,
170 pub failure_status: Option<i32>,
171 pub dont_check_failure_status: bool,
173 pub run_rustfix: bool,
176 pub rustfix_only_machine_applicable: bool,
178 pub assembly_output: Option<String>,
179 pub should_ice: bool,
181 pub stderr_per_bitwidth: bool,
183 pub mir_unit_test: Option<String>,
185 pub remap_src_base: bool,
188 pub llvm_cov_flags: Vec<String>,
191 pub filecheck_flags: Vec<String>,
193 pub no_auto_check_cfg: bool,
195 pub add_minicore: bool,
198 pub minicore_compile_flags: Vec<String>,
200 pub dont_require_annotations: HashSet<ErrorKind>,
202 pub disable_gdb_pretty_printers: bool,
204 pub compare_output_by_lines: bool,
206}
207
208mod directives {
209 pub const ERROR_PATTERN: &'static str = "error-pattern";
210 pub const REGEX_ERROR_PATTERN: &'static str = "regex-error-pattern";
211 pub const COMPILE_FLAGS: &'static str = "compile-flags";
212 pub const RUN_FLAGS: &'static str = "run-flags";
213 pub const DOC_FLAGS: &'static str = "doc-flags";
214 pub const SHOULD_ICE: &'static str = "should-ice";
215 pub const BUILD_AUX_DOCS: &'static str = "build-aux-docs";
216 pub const UNIQUE_DOC_OUT_DIR: &'static str = "unique-doc-out-dir";
217 pub const FORCE_HOST: &'static str = "force-host";
218 pub const CHECK_STDOUT: &'static str = "check-stdout";
219 pub const CHECK_RUN_RESULTS: &'static str = "check-run-results";
220 pub const DONT_CHECK_COMPILER_STDOUT: &'static str = "dont-check-compiler-stdout";
221 pub const DONT_CHECK_COMPILER_STDERR: &'static str = "dont-check-compiler-stderr";
222 pub const DONT_REQUIRE_ANNOTATIONS: &'static str = "dont-require-annotations";
223 pub const NO_PREFER_DYNAMIC: &'static str = "no-prefer-dynamic";
224 pub const PRETTY_MODE: &'static str = "pretty-mode";
225 pub const PRETTY_COMPARE_ONLY: &'static str = "pretty-compare-only";
226 pub const AUX_BIN: &'static str = "aux-bin";
227 pub const AUX_BUILD: &'static str = "aux-build";
228 pub const AUX_CRATE: &'static str = "aux-crate";
229 pub const PROC_MACRO: &'static str = "proc-macro";
230 pub const AUX_CODEGEN_BACKEND: &'static str = "aux-codegen-backend";
231 pub const EXEC_ENV: &'static str = "exec-env";
232 pub const RUSTC_ENV: &'static str = "rustc-env";
233 pub const UNSET_EXEC_ENV: &'static str = "unset-exec-env";
234 pub const UNSET_RUSTC_ENV: &'static str = "unset-rustc-env";
235 pub const FORBID_OUTPUT: &'static str = "forbid-output";
236 pub const CHECK_TEST_LINE_NUMBERS_MATCH: &'static str = "check-test-line-numbers-match";
237 pub const IGNORE_PASS: &'static str = "ignore-pass";
238 pub const FAILURE_STATUS: &'static str = "failure-status";
239 pub const DONT_CHECK_FAILURE_STATUS: &'static str = "dont-check-failure-status";
240 pub const RUN_RUSTFIX: &'static str = "run-rustfix";
241 pub const RUSTFIX_ONLY_MACHINE_APPLICABLE: &'static str = "rustfix-only-machine-applicable";
242 pub const ASSEMBLY_OUTPUT: &'static str = "assembly-output";
243 pub const STDERR_PER_BITWIDTH: &'static str = "stderr-per-bitwidth";
244 pub const INCREMENTAL: &'static str = "incremental";
245 pub const KNOWN_BUG: &'static str = "known-bug";
246 pub const TEST_MIR_PASS: &'static str = "test-mir-pass";
247 pub const REMAP_SRC_BASE: &'static str = "remap-src-base";
248 pub const LLVM_COV_FLAGS: &'static str = "llvm-cov-flags";
249 pub const FILECHECK_FLAGS: &'static str = "filecheck-flags";
250 pub const NO_AUTO_CHECK_CFG: &'static str = "no-auto-check-cfg";
251 pub const ADD_MINICORE: &'static str = "add-minicore";
252 pub const MINICORE_COMPILE_FLAGS: &'static str = "minicore-compile-flags";
253 pub const DISABLE_GDB_PRETTY_PRINTERS: &'static str = "disable-gdb-pretty-printers";
254 pub const COMPARE_OUTPUT_BY_LINES: &'static str = "compare-output-by-lines";
255}
256
257impl TestProps {
258 pub fn new() -> Self {
259 TestProps {
260 error_patterns: vec![],
261 regex_error_patterns: vec![],
262 edition: None,
263 compile_flags: vec![],
264 run_flags: vec![],
265 doc_flags: vec![],
266 pp_exact: None,
267 aux: Default::default(),
268 revisions: vec![],
269 rustc_env: vec![
270 ("RUSTC_ICE".to_string(), "0".to_string()),
271 ("RUST_BACKTRACE".to_string(), "short".to_string()),
272 ],
273 unset_rustc_env: vec![("RUSTC_LOG_COLOR".to_string())],
274 exec_env: vec![],
275 unset_exec_env: vec![],
276 build_aux_docs: false,
277 unique_doc_out_dir: false,
278 force_host: false,
279 check_stdout: false,
280 check_run_results: false,
281 dont_check_compiler_stdout: false,
282 dont_check_compiler_stderr: false,
283 no_prefer_dynamic: false,
284 pretty_mode: "normal".to_string(),
285 pretty_compare_only: false,
286 forbid_output: vec![],
287 incremental_dir: None,
288 incremental: false,
289 known_bug: false,
290 pass_mode: None,
291 fail_mode: None,
292 ignore_pass: false,
293 check_test_line_numbers_match: false,
294 normalize_stdout: vec![],
295 normalize_stderr: vec![],
296 failure_status: None,
297 dont_check_failure_status: false,
298 run_rustfix: false,
299 rustfix_only_machine_applicable: false,
300 assembly_output: None,
301 should_ice: false,
302 stderr_per_bitwidth: false,
303 mir_unit_test: None,
304 remap_src_base: false,
305 llvm_cov_flags: vec![],
306 filecheck_flags: vec![],
307 no_auto_check_cfg: false,
308 add_minicore: false,
309 minicore_compile_flags: vec![],
310 dont_require_annotations: Default::default(),
311 disable_gdb_pretty_printers: false,
312 compare_output_by_lines: false,
313 }
314 }
315
316 pub fn from_aux_file(
317 &self,
318 testfile: &Utf8Path,
319 revision: Option<&str>,
320 config: &Config,
321 ) -> Self {
322 let mut props = TestProps::new();
323
324 props.incremental_dir = self.incremental_dir.clone();
326 props.ignore_pass = true;
327 props.load_from(testfile, revision, config);
328
329 props
330 }
331
332 pub fn from_file(testfile: &Utf8Path, revision: Option<&str>, config: &Config) -> Self {
333 let mut props = TestProps::new();
334 props.load_from(testfile, revision, config);
335 props.exec_env.push(("RUSTC".to_string(), config.rustc_path.to_string()));
336
337 match (props.pass_mode, props.fail_mode) {
338 (None, None) if config.mode == TestMode::Ui => props.fail_mode = Some(FailMode::Check),
339 (Some(_), Some(_)) => panic!("cannot use a *-fail and *-pass mode together"),
340 _ => {}
341 }
342
343 props
344 }
345
346 fn load_from(&mut self, testfile: &Utf8Path, test_revision: Option<&str>, config: &Config) {
351 if !testfile.is_dir() {
352 let file_contents = fs::read_to_string(testfile).unwrap();
353 let file_directives = FileDirectives::from_file_contents(testfile, &file_contents);
354
355 iter_directives(
356 config.mode,
357 &file_directives,
358 &mut |ln: &DirectiveLine<'_>| {
360 if !ln.applies_to_test_revision(test_revision) {
361 return;
362 }
363
364 if let Some(handler) = DIRECTIVE_HANDLERS_MAP.get(ln.name) {
365 handler.handle(config, ln, self);
366 }
367 },
368 );
369 }
370
371 if self.should_ice {
372 self.failure_status = Some(101);
373 }
374
375 if config.mode == TestMode::Incremental {
376 self.incremental = true;
377 }
378
379 if config.mode == TestMode::Crashes {
380 self.rustc_env = vec![
384 ("RUST_BACKTRACE".to_string(), "0".to_string()),
385 ("RUSTC_ICE".to_string(), "0".to_string()),
386 ];
387 }
388
389 for key in &["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] {
390 if let Ok(val) = env::var(key) {
391 if !self.exec_env.iter().any(|&(ref x, _)| x == key) {
392 self.exec_env.push(((*key).to_owned(), val))
393 }
394 }
395 }
396
397 if let Some(edition) = self.edition.or(config.edition) {
398 self.compile_flags.insert(0, format!("--edition={edition}"));
401 }
402 }
403
404 fn update_fail_mode(&mut self, ln: &DirectiveLine<'_>, config: &Config) {
405 let check_ui = |mode: &str| {
406 if config.mode != TestMode::Ui && config.mode != TestMode::Crashes {
408 panic!("`{}-fail` directive is only supported in UI tests", mode);
409 }
410 };
411 let fail_mode = if config.parse_name_directive(ln, "check-fail") {
412 check_ui("check");
413 Some(FailMode::Check)
414 } else if config.parse_name_directive(ln, "build-fail") {
415 check_ui("build");
416 Some(FailMode::Build)
417 } else if config.parse_name_directive(ln, "run-fail") {
418 check_ui("run");
419 Some(FailMode::Run(RunFailMode::Fail))
420 } else if config.parse_name_directive(ln, "run-crash") {
421 check_ui("run");
422 Some(FailMode::Run(RunFailMode::Crash))
423 } else if config.parse_name_directive(ln, "run-fail-or-crash") {
424 check_ui("run");
425 Some(FailMode::Run(RunFailMode::FailOrCrash))
426 } else {
427 None
428 };
429 match (self.fail_mode, fail_mode) {
430 (None, Some(_)) => self.fail_mode = fail_mode,
431 (Some(_), Some(_)) => panic!("multiple `*-fail` directives in a single test"),
432 (_, None) => {}
433 }
434 }
435
436 fn update_pass_mode(&mut self, ln: &DirectiveLine<'_>, config: &Config) {
437 let check_no_run = |s| match (config.mode, s) {
438 (TestMode::Ui, _) => (),
439 (TestMode::Crashes, _) => (),
440 (TestMode::Codegen, "build-pass") => (),
441 (TestMode::Incremental, _) => {
442 if self.revisions.iter().any(|r| !r.starts_with("cfail")) {
445 panic!("`{s}` directive is only supported in `cfail` incremental tests")
446 }
447 }
448 (mode, _) => panic!("`{s}` directive is not supported in `{mode}` tests"),
449 };
450 let pass_mode = if config.parse_name_directive(ln, "check-pass") {
451 check_no_run("check-pass");
452 Some(PassMode::Check)
453 } else if config.parse_name_directive(ln, "build-pass") {
454 check_no_run("build-pass");
455 Some(PassMode::Build)
456 } else if config.parse_name_directive(ln, "run-pass") {
457 check_no_run("run-pass");
458 Some(PassMode::Run)
459 } else {
460 None
461 };
462 match (self.pass_mode, pass_mode) {
463 (None, Some(_)) => self.pass_mode = pass_mode,
464 (Some(_), Some(_)) => panic!("multiple `*-pass` directives in a single test"),
465 (_, None) => {}
466 }
467 }
468
469 pub fn pass_mode(&self, config: &Config) -> Option<PassMode> {
470 if !self.ignore_pass && self.fail_mode.is_none() {
471 if let mode @ Some(_) = config.force_pass_mode {
472 return mode;
473 }
474 }
475 self.pass_mode
476 }
477
478 pub fn local_pass_mode(&self) -> Option<PassMode> {
480 self.pass_mode
481 }
482
483 fn update_add_minicore(&mut self, ln: &DirectiveLine<'_>, config: &Config) {
484 let add_minicore = config.parse_name_directive(ln, directives::ADD_MINICORE);
485 if add_minicore {
486 if !matches!(config.mode, TestMode::Ui | TestMode::Codegen | TestMode::Assembly) {
487 panic!(
488 "`add-minicore` is currently only supported for ui, codegen and assembly test modes"
489 );
490 }
491
492 if self.local_pass_mode().is_some_and(|pm| pm == PassMode::Run) {
495 panic!("`add-minicore` cannot be used to run the test binary");
498 }
499
500 self.add_minicore = add_minicore;
501 }
502 }
503}
504
505pub(crate) fn do_early_directives_check(
506 mode: TestMode,
507 file_directives: &FileDirectives<'_>,
508) -> Result<(), String> {
509 let testfile = file_directives.path;
510
511 for directive_line @ DirectiveLine { line_number, .. } in &file_directives.lines {
512 let CheckDirectiveResult { is_known_directive, trailing_directive } =
513 check_directive(directive_line, mode);
514
515 if !is_known_directive {
516 return Err(format!(
517 "ERROR: unknown compiletest directive `{directive}` at {testfile}:{line_number}",
518 directive = directive_line.display(),
519 ));
520 }
521
522 if let Some(trailing_directive) = &trailing_directive {
523 return Err(format!(
524 "ERROR: detected trailing compiletest directive `{trailing_directive}` at {testfile}:{line_number}\n\
525 HELP: put the directive on its own line: `//@ {trailing_directive}`"
526 ));
527 }
528 }
529
530 Ok(())
531}
532
533pub(crate) struct CheckDirectiveResult<'ln> {
534 is_known_directive: bool,
535 trailing_directive: Option<&'ln str>,
536}
537
538fn check_directive<'a>(
539 directive_ln: &DirectiveLine<'a>,
540 mode: TestMode,
541) -> CheckDirectiveResult<'a> {
542 let &DirectiveLine { name: directive_name, .. } = directive_ln;
543
544 let is_known_directive = KNOWN_DIRECTIVE_NAMES_SET.contains(&directive_name)
545 || match mode {
546 TestMode::Rustdoc => KNOWN_HTMLDOCCK_DIRECTIVE_NAMES.contains(&directive_name),
547 TestMode::RustdocJson => KNOWN_JSONDOCCK_DIRECTIVE_NAMES.contains(&directive_name),
548 _ => false,
549 };
550
551 let trailing_directive = directive_ln
555 .remark_after_space()
556 .map(|remark| remark.trim_start().split(' ').next().unwrap())
557 .filter(|token| KNOWN_DIRECTIVE_NAMES_SET.contains(token));
558
559 CheckDirectiveResult { is_known_directive, trailing_directive }
565}
566
567fn iter_directives(
568 mode: TestMode,
569 file_directives: &FileDirectives<'_>,
570 it: &mut dyn FnMut(&DirectiveLine<'_>),
571) {
572 let testfile = file_directives.path;
573
574 if mode == TestMode::CoverageRun {
579 let extra_directives: &[&str] = &[
580 "//@ needs-profiler-runtime",
581 "//@ ignore-cross-compile",
585 ];
586 for directive_str in extra_directives {
588 let directive_line = line_directive(testfile, 0, directive_str)
589 .unwrap_or_else(|| panic!("bad extra-directive line: {directive_str:?}"));
590 it(&directive_line);
591 }
592 }
593
594 for directive_line in &file_directives.lines {
595 it(directive_line);
596 }
597}
598
599impl Config {
600 fn parse_and_update_revisions(&self, line: &DirectiveLine<'_>, existing: &mut Vec<String>) {
601 const FORBIDDEN_REVISION_NAMES: [&str; 2] = [
602 "true", "false",
606 ];
607
608 const FILECHECK_FORBIDDEN_REVISION_NAMES: [&str; 9] =
609 ["CHECK", "COM", "NEXT", "SAME", "EMPTY", "NOT", "COUNT", "DAG", "LABEL"];
610
611 if let Some(raw) = self.parse_name_value_directive(line, "revisions") {
612 let &DirectiveLine { file_path: testfile, .. } = line;
613
614 if self.mode == TestMode::RunMake {
615 panic!("`run-make` mode tests do not support revisions: {}", testfile);
616 }
617
618 let mut duplicates: HashSet<_> = existing.iter().cloned().collect();
619 for revision in raw.split_whitespace() {
620 if !duplicates.insert(revision.to_string()) {
621 panic!("duplicate revision: `{}` in line `{}`: {}", revision, raw, testfile);
622 }
623
624 if FORBIDDEN_REVISION_NAMES.contains(&revision) {
625 panic!(
626 "revision name `{revision}` is not permitted: `{}` in line `{}`: {}",
627 revision, raw, testfile
628 );
629 }
630
631 if matches!(self.mode, TestMode::Assembly | TestMode::Codegen | TestMode::MirOpt)
632 && FILECHECK_FORBIDDEN_REVISION_NAMES.contains(&revision)
633 {
634 panic!(
635 "revision name `{revision}` is not permitted in a test suite that uses \
636 `FileCheck` annotations as it is confusing when used as custom `FileCheck` \
637 prefix: `{revision}` in line `{}`: {}",
638 raw, testfile
639 );
640 }
641
642 existing.push(revision.to_string());
643 }
644 }
645 }
646
647 fn parse_env(nv: String) -> (String, String) {
648 let (name, value) = nv.split_once('=').unwrap_or((&nv, ""));
652 let name = name.trim();
655 (name.to_owned(), value.to_owned())
656 }
657
658 fn parse_pp_exact(&self, line: &DirectiveLine<'_>) -> Option<Utf8PathBuf> {
659 if let Some(s) = self.parse_name_value_directive(line, "pp-exact") {
660 Some(Utf8PathBuf::from(&s))
661 } else if self.parse_name_directive(line, "pp-exact") {
662 line.file_path.file_name().map(Utf8PathBuf::from)
663 } else {
664 None
665 }
666 }
667
668 fn parse_custom_normalization(&self, line: &DirectiveLine<'_>) -> Option<NormalizeRule> {
669 let &DirectiveLine { name, .. } = line;
670
671 let kind = match name {
672 "normalize-stdout" => NormalizeKind::Stdout,
673 "normalize-stderr" => NormalizeKind::Stderr,
674 "normalize-stderr-32bit" => NormalizeKind::Stderr32bit,
675 "normalize-stderr-64bit" => NormalizeKind::Stderr64bit,
676 _ => return None,
677 };
678
679 let Some((regex, replacement)) = line.value_after_colon().and_then(parse_normalize_rule)
680 else {
681 error!("couldn't parse custom normalization rule: `{}`", line.display());
682 help!("expected syntax is: `{name}: \"REGEX\" -> \"REPLACEMENT\"`");
683 panic!("invalid normalization rule detected");
684 };
685 Some(NormalizeRule { kind, regex, replacement })
686 }
687
688 fn parse_name_directive(&self, line: &DirectiveLine<'_>, directive: &str) -> bool {
689 line.name == directive
693 }
694
695 fn parse_name_value_directive(
696 &self,
697 line: &DirectiveLine<'_>,
698 directive: &str,
699 ) -> Option<String> {
700 let &DirectiveLine { file_path, line_number, .. } = line;
701
702 if line.name != directive {
703 return None;
704 };
705
706 let value = line.value_after_colon()?;
710 debug!("{}: {}", directive, value);
711 let value = expand_variables(value.to_owned(), self);
712
713 if value.is_empty() {
714 error!("{file_path}:{line_number}: empty value for directive `{directive}`");
715 help!("expected syntax is: `{directive}: value`");
716 panic!("empty directive value detected");
717 }
718
719 Some(value)
720 }
721
722 fn set_name_directive(&self, line: &DirectiveLine<'_>, directive: &str, value: &mut bool) {
723 *value = *value || self.parse_name_directive(line, directive);
725 }
726
727 fn set_name_value_directive<T>(
728 &self,
729 line: &DirectiveLine<'_>,
730 directive: &str,
731 value: &mut Option<T>,
732 parse: impl FnOnce(String) -> T,
733 ) {
734 if value.is_none() {
735 *value = self.parse_name_value_directive(line, directive).map(parse);
736 }
737 }
738
739 fn push_name_value_directive<T>(
740 &self,
741 line: &DirectiveLine<'_>,
742 directive: &str,
743 values: &mut Vec<T>,
744 parse: impl FnOnce(String) -> T,
745 ) {
746 if let Some(value) = self.parse_name_value_directive(line, directive).map(parse) {
747 values.push(value);
748 }
749 }
750}
751
752fn expand_variables(mut value: String, config: &Config) -> String {
754 const CWD: &str = "{{cwd}}";
755 const SRC_BASE: &str = "{{src-base}}";
756 const TEST_SUITE_BUILD_BASE: &str = "{{build-base}}";
757 const RUST_SRC_BASE: &str = "{{rust-src-base}}";
758 const SYSROOT_BASE: &str = "{{sysroot-base}}";
759 const TARGET_LINKER: &str = "{{target-linker}}";
760 const TARGET: &str = "{{target}}";
761
762 if value.contains(CWD) {
763 let cwd = env::current_dir().unwrap();
764 value = value.replace(CWD, &cwd.to_str().unwrap());
765 }
766
767 if value.contains(SRC_BASE) {
768 value = value.replace(SRC_BASE, &config.src_test_suite_root.as_str());
769 }
770
771 if value.contains(TEST_SUITE_BUILD_BASE) {
772 value = value.replace(TEST_SUITE_BUILD_BASE, &config.build_test_suite_root.as_str());
773 }
774
775 if value.contains(SYSROOT_BASE) {
776 value = value.replace(SYSROOT_BASE, &config.sysroot_base.as_str());
777 }
778
779 if value.contains(TARGET_LINKER) {
780 value = value.replace(TARGET_LINKER, config.target_linker.as_deref().unwrap_or(""));
781 }
782
783 if value.contains(TARGET) {
784 value = value.replace(TARGET, &config.target);
785 }
786
787 if value.contains(RUST_SRC_BASE) {
788 let src_base = config.sysroot_base.join("lib/rustlib/src/rust");
789 src_base.try_exists().expect(&*format!("{} should exists", src_base));
790 let src_base = src_base.read_link_utf8().unwrap_or(src_base);
791 value = value.replace(RUST_SRC_BASE, &src_base.as_str());
792 }
793
794 value
795}
796
797struct NormalizeRule {
798 kind: NormalizeKind,
799 regex: String,
800 replacement: String,
801}
802
803enum NormalizeKind {
804 Stdout,
805 Stderr,
806 Stderr32bit,
807 Stderr64bit,
808}
809
810fn parse_normalize_rule(raw_value: &str) -> Option<(String, String)> {
815 let captures = static_regex!(
817 r#"(?x) # (verbose mode regex)
818 ^
819 \s* # (leading whitespace)
820 "(?<regex>[^"]*)" # "REGEX"
821 \s+->\s+ # ->
822 "(?<replacement>[^"]*)" # "REPLACEMENT"
823 $
824 "#
825 )
826 .captures(raw_value)?;
827 let regex = captures["regex"].to_owned();
828 let replacement = captures["replacement"].to_owned();
829 let replacement = replacement.replace("\\n", "\n");
833 Some((regex, replacement))
834}
835
836pub fn extract_llvm_version(version: &str) -> Version {
846 let version = version.trim();
849 let uninterested = |c: char| !c.is_ascii_digit() && c != '.';
850 let version_without_suffix = match version.split_once(uninterested) {
851 Some((prefix, _suffix)) => prefix,
852 None => version,
853 };
854
855 let components: Vec<u64> = version_without_suffix
856 .split('.')
857 .map(|s| s.parse().expect("llvm version component should consist of only digits"))
858 .collect();
859
860 match &components[..] {
861 [major] => Version::new(*major, 0, 0),
862 [major, minor] => Version::new(*major, *minor, 0),
863 [major, minor, patch] => Version::new(*major, *minor, *patch),
864 _ => panic!("malformed llvm version string, expected only 1-3 components: {version}"),
865 }
866}
867
868pub fn extract_llvm_version_from_binary(binary_path: &str) -> Option<Version> {
869 let output = Command::new(binary_path).arg("--version").output().ok()?;
870 if !output.status.success() {
871 return None;
872 }
873 let version = String::from_utf8(output.stdout).ok()?;
874 for line in version.lines() {
875 if let Some(version) = line.split("LLVM version ").nth(1) {
876 return Some(extract_llvm_version(version));
877 }
878 }
879 None
880}
881
882pub fn llvm_has_libzstd(config: &Config) -> bool {
886 fn is_zstd_in_config(llvm_bin_dir: &Utf8Path) -> Option<()> {
894 let llvm_config_path = llvm_bin_dir.join("llvm-config");
895 let output = Command::new(llvm_config_path).arg("--system-libs").output().ok()?;
896 assert!(output.status.success(), "running llvm-config --system-libs failed");
897
898 let libs = String::from_utf8(output.stdout).ok()?;
899 for lib in libs.split_whitespace() {
900 if lib.ends_with("libzstd.a") && Utf8Path::new(lib).exists() {
901 return Some(());
902 }
903 }
904
905 None
906 }
907
908 #[cfg(unix)]
918 fn is_lld_built_with_zstd(llvm_bin_dir: &Utf8Path) -> Option<()> {
919 let lld_path = llvm_bin_dir.join("lld");
920 if lld_path.exists() {
921 let lld_symlink_path = llvm_bin_dir.join("ld.lld");
924 if !lld_symlink_path.exists() {
925 std::os::unix::fs::symlink(lld_path, &lld_symlink_path).ok()?;
926 }
927
928 let output = Command::new(&lld_symlink_path)
931 .arg("--compress-debug-sections=zstd")
932 .output()
933 .ok()?;
934 assert!(!output.status.success());
935
936 let stderr = String::from_utf8(output.stderr).ok()?;
939 let zstd_available = !stderr.contains("LLVM was not built with LLVM_ENABLE_ZSTD");
940
941 std::fs::remove_file(lld_symlink_path).ok()?;
944
945 if zstd_available {
946 return Some(());
947 }
948 }
949
950 None
951 }
952
953 #[cfg(not(unix))]
954 fn is_lld_built_with_zstd(_llvm_bin_dir: &Utf8Path) -> Option<()> {
955 None
956 }
957
958 if let Some(llvm_bin_dir) = &config.llvm_bin_dir {
959 if is_zstd_in_config(llvm_bin_dir).is_some() {
961 return true;
962 }
963
964 if config.target == "x86_64-unknown-linux-gnu"
972 && config.host == config.target
973 && is_lld_built_with_zstd(llvm_bin_dir).is_some()
974 {
975 return true;
976 }
977 }
978
979 false
981}
982
983fn extract_version_range<'a, F, VersionTy: Clone>(
989 line: &'a str,
990 parse: F,
991) -> Option<(VersionTy, VersionTy)>
992where
993 F: Fn(&'a str) -> Option<VersionTy>,
994{
995 let mut splits = line.splitn(2, "- ").map(str::trim);
996 let min = splits.next().unwrap();
997 if min.ends_with('-') {
998 return None;
999 }
1000
1001 let max = splits.next();
1002
1003 if min.is_empty() {
1004 return None;
1005 }
1006
1007 let min = parse(min)?;
1008 let max = match max {
1009 Some("") => return None,
1010 Some(max) => parse(max)?,
1011 _ => min.clone(),
1012 };
1013
1014 Some((min, max))
1015}
1016
1017pub(crate) fn make_test_description(
1018 config: &Config,
1019 cache: &DirectivesCache,
1020 name: String,
1021 path: &Utf8Path,
1022 filterable_path: &Utf8Path,
1023 file_directives: &FileDirectives<'_>,
1024 test_revision: Option<&str>,
1025 poisoned: &mut bool,
1026 aux_props: &mut AuxProps,
1027) -> CollectedTestDesc {
1028 let mut ignore = false;
1029 let mut ignore_message = None;
1030 let mut should_fail = false;
1031
1032 iter_directives(
1034 config.mode,
1035 file_directives,
1036 &mut |ln @ &DirectiveLine { line_number, .. }| {
1037 if !ln.applies_to_test_revision(test_revision) {
1038 return;
1039 }
1040
1041 parse_and_update_aux(config, ln, aux_props);
1043
1044 macro_rules! decision {
1045 ($e:expr) => {
1046 match $e {
1047 IgnoreDecision::Ignore { reason } => {
1048 ignore = true;
1049 ignore_message = Some(reason.into());
1050 }
1051 IgnoreDecision::Error { message } => {
1052 error!("{path}:{line_number}: {message}");
1053 *poisoned = true;
1054 return;
1055 }
1056 IgnoreDecision::Continue => {}
1057 }
1058 };
1059 }
1060
1061 decision!(cfg::handle_ignore(config, ln));
1062 decision!(cfg::handle_only(config, ln));
1063 decision!(needs::handle_needs(&cache.needs, config, ln));
1064 decision!(ignore_llvm(config, ln));
1065 decision!(ignore_backends(config, ln));
1066 decision!(needs_backends(config, ln));
1067 decision!(ignore_cdb(config, ln));
1068 decision!(ignore_gdb(config, ln));
1069 decision!(ignore_lldb(config, ln));
1070
1071 if config.target == "wasm32-unknown-unknown"
1072 && config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS)
1073 {
1074 decision!(IgnoreDecision::Ignore {
1075 reason: "ignored on WASM as the run results cannot be checked there".into(),
1076 });
1077 }
1078
1079 should_fail |= config.parse_name_directive(ln, "should-fail");
1080 },
1081 );
1082
1083 let should_fail = if should_fail && config.mode != TestMode::Pretty {
1087 ShouldFail::Yes
1088 } else {
1089 ShouldFail::No
1090 };
1091
1092 CollectedTestDesc {
1093 name,
1094 filterable_path: filterable_path.to_owned(),
1095 ignore,
1096 ignore_message,
1097 should_fail,
1098 }
1099}
1100
1101fn ignore_cdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1102 if config.debugger != Some(Debugger::Cdb) {
1103 return IgnoreDecision::Continue;
1104 }
1105
1106 if let Some(actual_version) = config.cdb_version {
1107 if line.name == "min-cdb-version"
1108 && let Some(rest) = line.value_after_colon().map(str::trim)
1109 {
1110 let min_version = extract_cdb_version(rest).unwrap_or_else(|| {
1111 panic!("couldn't parse version range: {:?}", rest);
1112 });
1113
1114 if actual_version < min_version {
1117 return IgnoreDecision::Ignore {
1118 reason: format!("ignored when the CDB version is lower than {rest}"),
1119 };
1120 }
1121 }
1122 }
1123 IgnoreDecision::Continue
1124}
1125
1126fn ignore_gdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1127 if config.debugger != Some(Debugger::Gdb) {
1128 return IgnoreDecision::Continue;
1129 }
1130
1131 if let Some(actual_version) = config.gdb_version {
1132 if line.name == "min-gdb-version"
1133 && let Some(rest) = line.value_after_colon().map(str::trim)
1134 {
1135 let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version)
1136 .unwrap_or_else(|| {
1137 panic!("couldn't parse version range: {:?}", rest);
1138 });
1139
1140 if start_ver != end_ver {
1141 panic!("Expected single GDB version")
1142 }
1143 if actual_version < start_ver {
1146 return IgnoreDecision::Ignore {
1147 reason: format!("ignored when the GDB version is lower than {rest}"),
1148 };
1149 }
1150 } else if line.name == "ignore-gdb-version"
1151 && let Some(rest) = line.value_after_colon().map(str::trim)
1152 {
1153 let (min_version, max_version) = extract_version_range(rest, extract_gdb_version)
1154 .unwrap_or_else(|| {
1155 panic!("couldn't parse version range: {:?}", rest);
1156 });
1157
1158 if max_version < min_version {
1159 panic!("Malformed GDB version range: max < min")
1160 }
1161
1162 if actual_version >= min_version && actual_version <= max_version {
1163 if min_version == max_version {
1164 return IgnoreDecision::Ignore {
1165 reason: format!("ignored when the GDB version is {rest}"),
1166 };
1167 } else {
1168 return IgnoreDecision::Ignore {
1169 reason: format!("ignored when the GDB version is between {rest}"),
1170 };
1171 }
1172 }
1173 }
1174 }
1175 IgnoreDecision::Continue
1176}
1177
1178fn ignore_lldb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1179 if config.debugger != Some(Debugger::Lldb) {
1180 return IgnoreDecision::Continue;
1181 }
1182
1183 if let Some(actual_version) = config.lldb_version {
1184 if line.name == "min-lldb-version"
1185 && let Some(rest) = line.value_after_colon().map(str::trim)
1186 {
1187 let min_version = rest.parse().unwrap_or_else(|e| {
1188 panic!("Unexpected format of LLDB version string: {}\n{:?}", rest, e);
1189 });
1190 if actual_version < min_version {
1193 return IgnoreDecision::Ignore {
1194 reason: format!("ignored when the LLDB version is {rest}"),
1195 };
1196 }
1197 }
1198 }
1199 IgnoreDecision::Continue
1200}
1201
1202fn ignore_backends(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1203 let path = line.file_path;
1204 if let Some(backends_to_ignore) = config.parse_name_value_directive(line, "ignore-backends") {
1205 for backend in backends_to_ignore.split_whitespace().map(|backend| {
1206 match CodegenBackend::try_from(backend) {
1207 Ok(backend) => backend,
1208 Err(error) => {
1209 panic!("Invalid ignore-backends value `{backend}` in `{path}`: {error}")
1210 }
1211 }
1212 }) {
1213 if !config.bypass_ignore_backends && config.default_codegen_backend == backend {
1214 return IgnoreDecision::Ignore {
1215 reason: format!("{} backend is marked as ignore", backend.as_str()),
1216 };
1217 }
1218 }
1219 }
1220 IgnoreDecision::Continue
1221}
1222
1223fn needs_backends(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1224 let path = line.file_path;
1225 if let Some(needed_backends) = config.parse_name_value_directive(line, "needs-backends") {
1226 if !needed_backends
1227 .split_whitespace()
1228 .map(|backend| match CodegenBackend::try_from(backend) {
1229 Ok(backend) => backend,
1230 Err(error) => {
1231 panic!("Invalid needs-backends value `{backend}` in `{path}`: {error}")
1232 }
1233 })
1234 .any(|backend| config.default_codegen_backend == backend)
1235 {
1236 return IgnoreDecision::Ignore {
1237 reason: format!(
1238 "{} backend is not part of required backends",
1239 config.default_codegen_backend.as_str()
1240 ),
1241 };
1242 }
1243 }
1244 IgnoreDecision::Continue
1245}
1246
1247fn ignore_llvm(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1248 let path = line.file_path;
1249 if let Some(needed_components) =
1250 config.parse_name_value_directive(line, "needs-llvm-components")
1251 {
1252 let components: HashSet<_> = config.llvm_components.split_whitespace().collect();
1253 if let Some(missing_component) = needed_components
1254 .split_whitespace()
1255 .find(|needed_component| !components.contains(needed_component))
1256 {
1257 if env::var_os("COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS").is_some() {
1258 panic!(
1259 "missing LLVM component {missing_component}, \
1260 and COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS is set: {path}",
1261 );
1262 }
1263 return IgnoreDecision::Ignore {
1264 reason: format!("ignored when the {missing_component} LLVM component is missing"),
1265 };
1266 }
1267 }
1268 if let Some(actual_version) = &config.llvm_version {
1269 if let Some(version_string) = config.parse_name_value_directive(line, "min-llvm-version") {
1272 let min_version = extract_llvm_version(&version_string);
1273 if *actual_version < min_version {
1275 return IgnoreDecision::Ignore {
1276 reason: format!(
1277 "ignored when the LLVM version {actual_version} is older than {min_version}"
1278 ),
1279 };
1280 }
1281 } else if let Some(version_string) =
1282 config.parse_name_value_directive(line, "max-llvm-major-version")
1283 {
1284 let max_version = extract_llvm_version(&version_string);
1285 if actual_version.major > max_version.major {
1287 return IgnoreDecision::Ignore {
1288 reason: format!(
1289 "ignored when the LLVM version ({actual_version}) is newer than major\
1290 version {}",
1291 max_version.major
1292 ),
1293 };
1294 }
1295 } else if let Some(version_string) =
1296 config.parse_name_value_directive(line, "min-system-llvm-version")
1297 {
1298 let min_version = extract_llvm_version(&version_string);
1299 if config.system_llvm && *actual_version < min_version {
1302 return IgnoreDecision::Ignore {
1303 reason: format!(
1304 "ignored when the system LLVM version {actual_version} is older than {min_version}"
1305 ),
1306 };
1307 }
1308 } else if let Some(version_range) =
1309 config.parse_name_value_directive(line, "ignore-llvm-version")
1310 {
1311 let (v_min, v_max) =
1313 extract_version_range(&version_range, |s| Some(extract_llvm_version(s)))
1314 .unwrap_or_else(|| {
1315 panic!("couldn't parse version range: \"{version_range}\"");
1316 });
1317 if v_max < v_min {
1318 panic!("malformed LLVM version range where {v_max} < {v_min}")
1319 }
1320 if *actual_version >= v_min && *actual_version <= v_max {
1322 if v_min == v_max {
1323 return IgnoreDecision::Ignore {
1324 reason: format!("ignored when the LLVM version is {actual_version}"),
1325 };
1326 } else {
1327 return IgnoreDecision::Ignore {
1328 reason: format!(
1329 "ignored when the LLVM version is between {v_min} and {v_max}"
1330 ),
1331 };
1332 }
1333 }
1334 } else if let Some(version_string) =
1335 config.parse_name_value_directive(line, "exact-llvm-major-version")
1336 {
1337 let version = extract_llvm_version(&version_string);
1339 if actual_version.major != version.major {
1340 return IgnoreDecision::Ignore {
1341 reason: format!(
1342 "ignored when the actual LLVM major version is {}, but the test only targets major version {}",
1343 actual_version.major, version.major
1344 ),
1345 };
1346 }
1347 }
1348 }
1349 IgnoreDecision::Continue
1350}
1351
1352enum IgnoreDecision {
1353 Ignore { reason: String },
1354 Continue,
1355 Error { message: String },
1356}
1357
1358fn parse_edition_range(config: &Config, line: &DirectiveLine<'_>) -> Option<EditionRange> {
1359 let raw = config.parse_name_value_directive(line, "edition")?;
1360 let &DirectiveLine { file_path: testfile, line_number, .. } = line;
1361
1362 if let Some((lower_bound, upper_bound)) = raw.split_once("..") {
1364 Some(match (maybe_parse_edition(lower_bound), maybe_parse_edition(upper_bound)) {
1365 (Some(lower_bound), Some(upper_bound)) if upper_bound <= lower_bound => {
1366 fatal!(
1367 "{testfile}:{line_number}: the left side of `//@ edition` cannot be greater than or equal to the right side"
1368 );
1369 }
1370 (Some(lower_bound), Some(upper_bound)) => {
1371 EditionRange::Range { lower_bound, upper_bound }
1372 }
1373 (Some(lower_bound), None) => EditionRange::RangeFrom(lower_bound),
1374 (None, Some(_)) => {
1375 fatal!(
1376 "{testfile}:{line_number}: `..edition` is not a supported range in `//@ edition`"
1377 );
1378 }
1379 (None, None) => {
1380 fatal!("{testfile}:{line_number}: `..` is not a supported range in `//@ edition`");
1381 }
1382 })
1383 } else {
1384 match maybe_parse_edition(&raw) {
1385 Some(edition) => Some(EditionRange::Exact(edition)),
1386 None => {
1387 fatal!("{testfile}:{line_number}: empty value for `//@ edition`");
1388 }
1389 }
1390 }
1391}
1392
1393fn maybe_parse_edition(mut input: &str) -> Option<Edition> {
1394 input = input.trim();
1395 if input.is_empty() {
1396 return None;
1397 }
1398 Some(parse_edition(input))
1399}
1400
1401#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1402enum EditionRange {
1403 Exact(Edition),
1404 RangeFrom(Edition),
1405 Range {
1407 lower_bound: Edition,
1408 upper_bound: Edition,
1409 },
1410}
1411
1412impl EditionRange {
1413 fn edition_to_test(&self, requested: impl Into<Option<Edition>>) -> Edition {
1414 let min_edition = Edition::Year(2015);
1415 let requested = requested.into().unwrap_or(min_edition);
1416
1417 match *self {
1418 EditionRange::Exact(exact) => exact,
1419 EditionRange::RangeFrom(lower_bound) => {
1420 if requested >= lower_bound {
1421 requested
1422 } else {
1423 lower_bound
1424 }
1425 }
1426 EditionRange::Range { lower_bound, upper_bound } => {
1427 if requested >= lower_bound && requested < upper_bound {
1428 requested
1429 } else {
1430 lower_bound
1431 }
1432 }
1433 }
1434 }
1435}
1436
1437fn split_flags(flags: &str) -> Vec<String> {
1438 flags
1443 .split('\'')
1444 .enumerate()
1445 .flat_map(|(i, f)| if i % 2 == 1 { vec![f] } else { f.split_whitespace().collect() })
1446 .map(move |s| s.to_owned())
1447 .collect::<Vec<_>>()
1448}