From cf5788d30dfbb3532c1e4fc18b009bfa51edb310 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Mon, 30 Jun 2025 17:15:32 +0800 Subject: Rename `header` -> `directives` compiletest has confusingly two terminology to refer to the same concept -- "headers" and "directives". To make this more self-consistent and less confusing, stick with "directives" only. This commit **intentionally** tries to be limited to move-only (modulo some key usage reference renames) to help git history. --- src/tools/compiletest/src/directives.rs | 1656 ++++++++++++++++++++ src/tools/compiletest/src/directives/auxiliary.rs | 65 + src/tools/compiletest/src/directives/cfg.rs | 414 +++++ src/tools/compiletest/src/directives/needs.rs | 428 +++++ .../directives/test-auxillary/error_annotation.rs | 6 + .../directives/test-auxillary/known_directive.rs | 4 + .../src/directives/test-auxillary/not_rs.Makefile | 1 + .../directives/test-auxillary/unknown_directive.rs | 1 + src/tools/compiletest/src/directives/tests.rs | 958 +++++++++++ src/tools/compiletest/src/header.rs | 1656 -------------------- src/tools/compiletest/src/header/auxiliary.rs | 65 - src/tools/compiletest/src/header/cfg.rs | 414 ----- src/tools/compiletest/src/header/needs.rs | 428 ----- .../src/header/test-auxillary/error_annotation.rs | 6 - .../src/header/test-auxillary/known_directive.rs | 4 - .../src/header/test-auxillary/not_rs.Makefile | 1 - .../src/header/test-auxillary/unknown_directive.rs | 1 - src/tools/compiletest/src/header/tests.rs | 958 ----------- src/tools/compiletest/src/lib.rs | 10 +- src/tools/compiletest/src/runtest.rs | 2 +- src/tools/rustdoc-gui-test/src/main.rs | 2 +- 21 files changed, 3540 insertions(+), 3540 deletions(-) create mode 100644 src/tools/compiletest/src/directives.rs create mode 100644 src/tools/compiletest/src/directives/auxiliary.rs create mode 100644 src/tools/compiletest/src/directives/cfg.rs create mode 100644 src/tools/compiletest/src/directives/needs.rs create mode 100644 src/tools/compiletest/src/directives/test-auxillary/error_annotation.rs create mode 100644 src/tools/compiletest/src/directives/test-auxillary/known_directive.rs create mode 100644 src/tools/compiletest/src/directives/test-auxillary/not_rs.Makefile create mode 100644 src/tools/compiletest/src/directives/test-auxillary/unknown_directive.rs create mode 100644 src/tools/compiletest/src/directives/tests.rs delete mode 100644 src/tools/compiletest/src/header.rs delete mode 100644 src/tools/compiletest/src/header/auxiliary.rs delete mode 100644 src/tools/compiletest/src/header/cfg.rs delete mode 100644 src/tools/compiletest/src/header/needs.rs delete mode 100644 src/tools/compiletest/src/header/test-auxillary/error_annotation.rs delete mode 100644 src/tools/compiletest/src/header/test-auxillary/known_directive.rs delete mode 100644 src/tools/compiletest/src/header/test-auxillary/not_rs.Makefile delete mode 100644 src/tools/compiletest/src/header/test-auxillary/unknown_directive.rs delete mode 100644 src/tools/compiletest/src/header/tests.rs (limited to 'src') diff --git a/src/tools/compiletest/src/directives.rs b/src/tools/compiletest/src/directives.rs new file mode 100644 index 00000000000..f5201cdb4b3 --- /dev/null +++ b/src/tools/compiletest/src/directives.rs @@ -0,0 +1,1656 @@ +use std::collections::HashSet; +use std::env; +use std::fs::File; +use std::io::BufReader; +use std::io::prelude::*; +use std::process::Command; + +use camino::{Utf8Path, Utf8PathBuf}; +use semver::Version; +use tracing::*; + +use crate::common::{Config, Debugger, FailMode, Mode, PassMode}; +use crate::debuggers::{extract_cdb_version, extract_gdb_version}; +use crate::errors::ErrorKind; +use crate::executor::{CollectedTestDesc, ShouldPanic}; +use crate::directives::auxiliary::{AuxProps, parse_and_update_aux}; +use crate::directives::needs::CachedNeedsConditions; +use crate::help; +use crate::util::static_regex; + +pub(crate) mod auxiliary; +mod cfg; +mod needs; +#[cfg(test)] +mod tests; + +pub struct HeadersCache { + needs: CachedNeedsConditions, +} + +impl HeadersCache { + pub fn load(config: &Config) -> Self { + Self { needs: CachedNeedsConditions::load(config) } + } +} + +/// Properties which must be known very early, before actually running +/// the test. +#[derive(Default)] +pub struct EarlyProps { + /// Auxiliary crates that should be built and made available to this test. + /// Included in [`EarlyProps`] so that the indicated files can participate + /// in up-to-date checking. Building happens via [`TestProps::aux`] instead. + pub(crate) aux: AuxProps, + pub revisions: Vec, +} + +impl EarlyProps { + pub fn from_file(config: &Config, testfile: &Utf8Path) -> Self { + let file = File::open(testfile.as_std_path()).expect("open test file to parse earlyprops"); + Self::from_reader(config, testfile, file) + } + + pub fn from_reader(config: &Config, testfile: &Utf8Path, rdr: R) -> Self { + let mut props = EarlyProps::default(); + let mut poisoned = false; + iter_header( + config.mode, + &config.suite, + &mut poisoned, + testfile, + rdr, + &mut |DirectiveLine { raw_directive: ln, .. }| { + parse_and_update_aux(config, ln, &mut props.aux); + config.parse_and_update_revisions(testfile, ln, &mut props.revisions); + }, + ); + + if poisoned { + eprintln!("errors encountered during EarlyProps parsing: {}", testfile); + panic!("errors encountered during EarlyProps parsing"); + } + + props + } +} + +#[derive(Clone, Debug)] +pub struct TestProps { + // Lines that should be expected, in order, on standard out + pub error_patterns: Vec, + // Regexes that should be expected, in order, on standard out + pub regex_error_patterns: Vec, + // Extra flags to pass to the compiler + pub compile_flags: Vec, + // Extra flags to pass when the compiled code is run (such as --bench) + pub run_flags: Vec, + /// Extra flags to pass to rustdoc but not the compiler. + pub doc_flags: Vec, + // If present, the name of a file that this test should match when + // pretty-printed + pub pp_exact: Option, + /// Auxiliary crates that should be built and made available to this test. + pub(crate) aux: AuxProps, + // Environment settings to use for compiling + pub rustc_env: Vec<(String, String)>, + // Environment variables to unset prior to compiling. + // Variables are unset before applying 'rustc_env'. + pub unset_rustc_env: Vec, + // Environment settings to use during execution + pub exec_env: Vec<(String, String)>, + // Environment variables to unset prior to execution. + // Variables are unset before applying 'exec_env' + pub unset_exec_env: Vec, + // Build documentation for all specified aux-builds as well + pub build_aux_docs: bool, + /// Build the documentation for each crate in a unique output directory. + /// Uses `/docs//doc`. + pub unique_doc_out_dir: bool, + // Flag to force a crate to be built with the host architecture + pub force_host: bool, + // Check stdout for error-pattern output as well as stderr + pub check_stdout: bool, + // Check stdout & stderr for output of run-pass test + pub check_run_results: bool, + // For UI tests, allows compiler to generate arbitrary output to stdout + pub dont_check_compiler_stdout: bool, + // For UI tests, allows compiler to generate arbitrary output to stderr + pub dont_check_compiler_stderr: bool, + // Don't force a --crate-type=dylib flag on the command line + // + // Set this for example if you have an auxiliary test file that contains + // a proc-macro and needs `#![crate_type = "proc-macro"]`. This ensures + // that the aux file is compiled as a `proc-macro` and not as a `dylib`. + pub no_prefer_dynamic: bool, + // Which pretty mode are we testing with, default to 'normal' + pub pretty_mode: String, + // Only compare pretty output and don't try compiling + pub pretty_compare_only: bool, + // Patterns which must not appear in the output of a cfail test. + pub forbid_output: Vec, + // Revisions to test for incremental compilation. + pub revisions: Vec, + // Directory (if any) to use for incremental compilation. This is + // not set by end-users; rather it is set by the incremental + // testing harness and used when generating compilation + // arguments. (In particular, it propagates to the aux-builds.) + pub incremental_dir: Option, + // If `true`, this test will use incremental compilation. + // + // This can be set manually with the `incremental` header, or implicitly + // by being a part of an incremental mode test. Using the `incremental` + // header should be avoided if possible; using an incremental mode test is + // preferred. Incremental mode tests support multiple passes, which can + // verify that the incremental cache can be loaded properly after being + // created. Just setting the header will only verify the behavior with + // creating an incremental cache, but doesn't check that it is created + // correctly. + // + // Compiletest will create the incremental directory, and ensure it is + // empty before the test starts. Incremental mode tests will reuse the + // incremental directory between passes in the same test. + pub incremental: bool, + // If `true`, this test is a known bug. + // + // When set, some requirements are relaxed. Currently, this only means no + // error annotations are needed, but this may be updated in the future to + // include other relaxations. + pub known_bug: bool, + // How far should the test proceed while still passing. + pass_mode: Option, + // Ignore `--pass` overrides from the command line for this test. + ignore_pass: bool, + // How far this test should proceed to start failing. + pub fail_mode: Option, + // rustdoc will test the output of the `--test` option + pub check_test_line_numbers_match: bool, + // customized normalization rules + pub normalize_stdout: Vec<(String, String)>, + pub normalize_stderr: Vec<(String, String)>, + pub failure_status: Option, + // For UI tests, allows compiler to exit with arbitrary failure status + pub dont_check_failure_status: bool, + // Whether or not `rustfix` should apply the `CodeSuggestion`s of this test and compile the + // resulting Rust code. + pub run_rustfix: bool, + // If true, `rustfix` will only apply `MachineApplicable` suggestions. + pub rustfix_only_machine_applicable: bool, + pub assembly_output: Option, + // If true, the test is expected to ICE + pub should_ice: bool, + // If true, the stderr is expected to be different across bit-widths. + pub stderr_per_bitwidth: bool, + // The MIR opt to unit test, if any + pub mir_unit_test: Option, + // Whether to tell `rustc` to remap the "src base" directory to a fake + // directory. + pub remap_src_base: bool, + /// Extra flags to pass to `llvm-cov` when producing coverage reports. + /// Only used by the "coverage-run" test mode. + pub llvm_cov_flags: Vec, + /// Extra flags to pass to LLVM's `filecheck` tool, in tests that use it. + pub filecheck_flags: Vec, + /// Don't automatically insert any `--check-cfg` args + pub no_auto_check_cfg: bool, + /// Run tests which require enzyme being build + pub has_enzyme: bool, + /// Build and use `minicore` as `core` stub for `no_core` tests in cross-compilation scenarios + /// that don't otherwise want/need `-Z build-std`. + pub add_core_stubs: bool, + /// Whether line annotatins are required for the given error kind. + pub dont_require_annotations: HashSet, +} + +mod directives { + pub const ERROR_PATTERN: &'static str = "error-pattern"; + pub const REGEX_ERROR_PATTERN: &'static str = "regex-error-pattern"; + pub const COMPILE_FLAGS: &'static str = "compile-flags"; + pub const RUN_FLAGS: &'static str = "run-flags"; + pub const DOC_FLAGS: &'static str = "doc-flags"; + pub const SHOULD_ICE: &'static str = "should-ice"; + pub const BUILD_AUX_DOCS: &'static str = "build-aux-docs"; + pub const UNIQUE_DOC_OUT_DIR: &'static str = "unique-doc-out-dir"; + pub const FORCE_HOST: &'static str = "force-host"; + pub const CHECK_STDOUT: &'static str = "check-stdout"; + pub const CHECK_RUN_RESULTS: &'static str = "check-run-results"; + pub const DONT_CHECK_COMPILER_STDOUT: &'static str = "dont-check-compiler-stdout"; + pub const DONT_CHECK_COMPILER_STDERR: &'static str = "dont-check-compiler-stderr"; + pub const DONT_REQUIRE_ANNOTATIONS: &'static str = "dont-require-annotations"; + pub const NO_PREFER_DYNAMIC: &'static str = "no-prefer-dynamic"; + pub const PRETTY_MODE: &'static str = "pretty-mode"; + pub const PRETTY_COMPARE_ONLY: &'static str = "pretty-compare-only"; + pub const AUX_BIN: &'static str = "aux-bin"; + pub const AUX_BUILD: &'static str = "aux-build"; + pub const AUX_CRATE: &'static str = "aux-crate"; + pub const PROC_MACRO: &'static str = "proc-macro"; + pub const AUX_CODEGEN_BACKEND: &'static str = "aux-codegen-backend"; + pub const EXEC_ENV: &'static str = "exec-env"; + pub const RUSTC_ENV: &'static str = "rustc-env"; + pub const UNSET_EXEC_ENV: &'static str = "unset-exec-env"; + pub const UNSET_RUSTC_ENV: &'static str = "unset-rustc-env"; + pub const FORBID_OUTPUT: &'static str = "forbid-output"; + pub const CHECK_TEST_LINE_NUMBERS_MATCH: &'static str = "check-test-line-numbers-match"; + pub const IGNORE_PASS: &'static str = "ignore-pass"; + pub const FAILURE_STATUS: &'static str = "failure-status"; + pub const DONT_CHECK_FAILURE_STATUS: &'static str = "dont-check-failure-status"; + pub const RUN_RUSTFIX: &'static str = "run-rustfix"; + pub const RUSTFIX_ONLY_MACHINE_APPLICABLE: &'static str = "rustfix-only-machine-applicable"; + pub const ASSEMBLY_OUTPUT: &'static str = "assembly-output"; + pub const STDERR_PER_BITWIDTH: &'static str = "stderr-per-bitwidth"; + pub const INCREMENTAL: &'static str = "incremental"; + pub const KNOWN_BUG: &'static str = "known-bug"; + pub const TEST_MIR_PASS: &'static str = "test-mir-pass"; + pub const REMAP_SRC_BASE: &'static str = "remap-src-base"; + pub const LLVM_COV_FLAGS: &'static str = "llvm-cov-flags"; + pub const FILECHECK_FLAGS: &'static str = "filecheck-flags"; + pub const NO_AUTO_CHECK_CFG: &'static str = "no-auto-check-cfg"; + pub const ADD_CORE_STUBS: &'static str = "add-core-stubs"; + // This isn't a real directive, just one that is probably mistyped often + pub const INCORRECT_COMPILER_FLAGS: &'static str = "compiler-flags"; +} + +impl TestProps { + pub fn new() -> Self { + TestProps { + error_patterns: vec![], + regex_error_patterns: vec![], + compile_flags: vec![], + run_flags: vec![], + doc_flags: vec![], + pp_exact: None, + aux: Default::default(), + revisions: vec![], + rustc_env: vec![ + ("RUSTC_ICE".to_string(), "0".to_string()), + ("RUST_BACKTRACE".to_string(), "short".to_string()), + ], + unset_rustc_env: vec![("RUSTC_LOG_COLOR".to_string())], + exec_env: vec![], + unset_exec_env: vec![], + build_aux_docs: false, + unique_doc_out_dir: false, + force_host: false, + check_stdout: false, + check_run_results: false, + dont_check_compiler_stdout: false, + dont_check_compiler_stderr: false, + no_prefer_dynamic: false, + pretty_mode: "normal".to_string(), + pretty_compare_only: false, + forbid_output: vec![], + incremental_dir: None, + incremental: false, + known_bug: false, + pass_mode: None, + fail_mode: None, + ignore_pass: false, + check_test_line_numbers_match: false, + normalize_stdout: vec![], + normalize_stderr: vec![], + failure_status: None, + dont_check_failure_status: false, + run_rustfix: false, + rustfix_only_machine_applicable: false, + assembly_output: None, + should_ice: false, + stderr_per_bitwidth: false, + mir_unit_test: None, + remap_src_base: false, + llvm_cov_flags: vec![], + filecheck_flags: vec![], + no_auto_check_cfg: false, + has_enzyme: false, + add_core_stubs: false, + dont_require_annotations: Default::default(), + } + } + + pub fn from_aux_file( + &self, + testfile: &Utf8Path, + revision: Option<&str>, + config: &Config, + ) -> Self { + let mut props = TestProps::new(); + + // copy over select properties to the aux build: + props.incremental_dir = self.incremental_dir.clone(); + props.ignore_pass = true; + props.load_from(testfile, revision, config); + + props + } + + pub fn from_file(testfile: &Utf8Path, revision: Option<&str>, config: &Config) -> Self { + let mut props = TestProps::new(); + props.load_from(testfile, revision, config); + props.exec_env.push(("RUSTC".to_string(), config.rustc_path.to_string())); + + match (props.pass_mode, props.fail_mode) { + (None, None) if config.mode == Mode::Ui => props.fail_mode = Some(FailMode::Check), + (Some(_), Some(_)) => panic!("cannot use a *-fail and *-pass mode together"), + _ => {} + } + + props + } + + /// Loads properties from `testfile` into `props`. If a property is + /// tied to a particular revision `foo` (indicated by writing + /// `//@[foo]`), then the property is ignored unless `test_revision` is + /// `Some("foo")`. + fn load_from(&mut self, testfile: &Utf8Path, test_revision: Option<&str>, config: &Config) { + let mut has_edition = false; + if !testfile.is_dir() { + let file = File::open(testfile.as_std_path()).unwrap(); + + let mut poisoned = false; + + iter_header( + config.mode, + &config.suite, + &mut poisoned, + testfile, + file, + &mut |directive @ DirectiveLine { raw_directive: ln, .. }| { + if !directive.applies_to_test_revision(test_revision) { + return; + } + + use directives::*; + + config.push_name_value_directive( + ln, + ERROR_PATTERN, + &mut self.error_patterns, + |r| r, + ); + config.push_name_value_directive( + ln, + REGEX_ERROR_PATTERN, + &mut self.regex_error_patterns, + |r| r, + ); + + config.push_name_value_directive(ln, DOC_FLAGS, &mut self.doc_flags, |r| r); + + fn split_flags(flags: &str) -> Vec { + // Individual flags can be single-quoted to preserve spaces; see + // . + flags + .split('\'') + .enumerate() + .flat_map(|(i, f)| { + if i % 2 == 1 { vec![f] } else { f.split_whitespace().collect() } + }) + .map(move |s| s.to_owned()) + .collect::>() + } + + if let Some(flags) = config.parse_name_value_directive(ln, COMPILE_FLAGS) { + let flags = split_flags(&flags); + for flag in &flags { + if flag == "--edition" || flag.starts_with("--edition=") { + panic!("you must use `//@ edition` to configure the edition"); + } + } + self.compile_flags.extend(flags); + } + if config.parse_name_value_directive(ln, INCORRECT_COMPILER_FLAGS).is_some() { + panic!("`compiler-flags` directive should be spelled `compile-flags`"); + } + + if let Some(edition) = config.parse_edition(ln) { + // The edition is added at the start, since flags from //@compile-flags must + // be passed to rustc last. + self.compile_flags.insert(0, format!("--edition={}", edition.trim())); + has_edition = true; + } + + config.parse_and_update_revisions(testfile, ln, &mut self.revisions); + + if let Some(flags) = config.parse_name_value_directive(ln, RUN_FLAGS) { + self.run_flags.extend(split_flags(&flags)); + } + + if self.pp_exact.is_none() { + self.pp_exact = config.parse_pp_exact(ln, testfile); + } + + config.set_name_directive(ln, SHOULD_ICE, &mut self.should_ice); + config.set_name_directive(ln, BUILD_AUX_DOCS, &mut self.build_aux_docs); + config.set_name_directive(ln, UNIQUE_DOC_OUT_DIR, &mut self.unique_doc_out_dir); + + config.set_name_directive(ln, FORCE_HOST, &mut self.force_host); + config.set_name_directive(ln, CHECK_STDOUT, &mut self.check_stdout); + config.set_name_directive(ln, CHECK_RUN_RESULTS, &mut self.check_run_results); + config.set_name_directive( + ln, + DONT_CHECK_COMPILER_STDOUT, + &mut self.dont_check_compiler_stdout, + ); + config.set_name_directive( + ln, + DONT_CHECK_COMPILER_STDERR, + &mut self.dont_check_compiler_stderr, + ); + config.set_name_directive(ln, NO_PREFER_DYNAMIC, &mut self.no_prefer_dynamic); + + if let Some(m) = config.parse_name_value_directive(ln, PRETTY_MODE) { + self.pretty_mode = m; + } + + config.set_name_directive( + ln, + PRETTY_COMPARE_ONLY, + &mut self.pretty_compare_only, + ); + + // Call a helper method to deal with aux-related directives. + parse_and_update_aux(config, ln, &mut self.aux); + + config.push_name_value_directive( + ln, + EXEC_ENV, + &mut self.exec_env, + Config::parse_env, + ); + config.push_name_value_directive( + ln, + UNSET_EXEC_ENV, + &mut self.unset_exec_env, + |r| r.trim().to_owned(), + ); + config.push_name_value_directive( + ln, + RUSTC_ENV, + &mut self.rustc_env, + Config::parse_env, + ); + config.push_name_value_directive( + ln, + UNSET_RUSTC_ENV, + &mut self.unset_rustc_env, + |r| r.trim().to_owned(), + ); + config.push_name_value_directive( + ln, + FORBID_OUTPUT, + &mut self.forbid_output, + |r| r, + ); + config.set_name_directive( + ln, + CHECK_TEST_LINE_NUMBERS_MATCH, + &mut self.check_test_line_numbers_match, + ); + + self.update_pass_mode(ln, test_revision, config); + self.update_fail_mode(ln, config); + + config.set_name_directive(ln, IGNORE_PASS, &mut self.ignore_pass); + + if let Some(NormalizeRule { kind, regex, replacement }) = + config.parse_custom_normalization(ln) + { + let rule_tuple = (regex, replacement); + match kind { + NormalizeKind::Stdout => self.normalize_stdout.push(rule_tuple), + NormalizeKind::Stderr => self.normalize_stderr.push(rule_tuple), + NormalizeKind::Stderr32bit => { + if config.target_cfg().pointer_width == 32 { + self.normalize_stderr.push(rule_tuple); + } + } + NormalizeKind::Stderr64bit => { + if config.target_cfg().pointer_width == 64 { + self.normalize_stderr.push(rule_tuple); + } + } + } + } + + if let Some(code) = config + .parse_name_value_directive(ln, FAILURE_STATUS) + .and_then(|code| code.trim().parse::().ok()) + { + self.failure_status = Some(code); + } + + config.set_name_directive( + ln, + DONT_CHECK_FAILURE_STATUS, + &mut self.dont_check_failure_status, + ); + + config.set_name_directive(ln, RUN_RUSTFIX, &mut self.run_rustfix); + config.set_name_directive( + ln, + RUSTFIX_ONLY_MACHINE_APPLICABLE, + &mut self.rustfix_only_machine_applicable, + ); + config.set_name_value_directive( + ln, + ASSEMBLY_OUTPUT, + &mut self.assembly_output, + |r| r.trim().to_string(), + ); + config.set_name_directive( + ln, + STDERR_PER_BITWIDTH, + &mut self.stderr_per_bitwidth, + ); + config.set_name_directive(ln, INCREMENTAL, &mut self.incremental); + + // Unlike the other `name_value_directive`s this needs to be handled manually, + // because it sets a `bool` flag. + if let Some(known_bug) = config.parse_name_value_directive(ln, KNOWN_BUG) { + let known_bug = known_bug.trim(); + if known_bug == "unknown" + || known_bug.split(',').all(|issue_ref| { + issue_ref + .trim() + .split_once('#') + .filter(|(_, number)| { + number.chars().all(|digit| digit.is_numeric()) + }) + .is_some() + }) + { + self.known_bug = true; + } else { + panic!( + "Invalid known-bug value: {known_bug}\nIt requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`." + ); + } + } else if config.parse_name_directive(ln, KNOWN_BUG) { + panic!( + "Invalid known-bug attribute, requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`." + ); + } + + config.set_name_value_directive( + ln, + TEST_MIR_PASS, + &mut self.mir_unit_test, + |s| s.trim().to_string(), + ); + config.set_name_directive(ln, REMAP_SRC_BASE, &mut self.remap_src_base); + + if let Some(flags) = config.parse_name_value_directive(ln, LLVM_COV_FLAGS) { + self.llvm_cov_flags.extend(split_flags(&flags)); + } + + if let Some(flags) = config.parse_name_value_directive(ln, FILECHECK_FLAGS) { + self.filecheck_flags.extend(split_flags(&flags)); + } + + config.set_name_directive(ln, NO_AUTO_CHECK_CFG, &mut self.no_auto_check_cfg); + + self.update_add_core_stubs(ln, config); + + if let Some(err_kind) = + config.parse_name_value_directive(ln, DONT_REQUIRE_ANNOTATIONS) + { + self.dont_require_annotations + .insert(ErrorKind::expect_from_user_str(err_kind.trim())); + } + }, + ); + + if poisoned { + eprintln!("errors encountered during TestProps parsing: {}", testfile); + panic!("errors encountered during TestProps parsing"); + } + } + + if self.should_ice { + self.failure_status = Some(101); + } + + if config.mode == Mode::Incremental { + self.incremental = true; + } + + if config.mode == Mode::Crashes { + // we don't want to pollute anything with backtrace-files + // also turn off backtraces in order to save some execution + // time on the tests; we only need to know IF it crashes + self.rustc_env = vec![ + ("RUST_BACKTRACE".to_string(), "0".to_string()), + ("RUSTC_ICE".to_string(), "0".to_string()), + ]; + } + + for key in &["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] { + if let Ok(val) = env::var(key) { + if !self.exec_env.iter().any(|&(ref x, _)| x == key) { + self.exec_env.push(((*key).to_owned(), val)) + } + } + } + + if let (Some(edition), false) = (&config.edition, has_edition) { + // The edition is added at the start, since flags from //@compile-flags must be passed + // to rustc last. + self.compile_flags.insert(0, format!("--edition={}", edition)); + } + } + + fn update_fail_mode(&mut self, ln: &str, config: &Config) { + let check_ui = |mode: &str| { + // Mode::Crashes may need build-fail in order to trigger llvm errors or stack overflows + if config.mode != Mode::Ui && config.mode != Mode::Crashes { + panic!("`{}-fail` header is only supported in UI tests", mode); + } + }; + if config.mode == Mode::Ui && config.parse_name_directive(ln, "compile-fail") { + panic!("`compile-fail` header is useless in UI tests"); + } + let fail_mode = if config.parse_name_directive(ln, "check-fail") { + check_ui("check"); + Some(FailMode::Check) + } else if config.parse_name_directive(ln, "build-fail") { + check_ui("build"); + Some(FailMode::Build) + } else if config.parse_name_directive(ln, "run-fail") { + check_ui("run"); + Some(FailMode::Run) + } else { + None + }; + match (self.fail_mode, fail_mode) { + (None, Some(_)) => self.fail_mode = fail_mode, + (Some(_), Some(_)) => panic!("multiple `*-fail` headers in a single test"), + (_, None) => {} + } + } + + fn update_pass_mode(&mut self, ln: &str, revision: Option<&str>, config: &Config) { + let check_no_run = |s| match (config.mode, s) { + (Mode::Ui, _) => (), + (Mode::Crashes, _) => (), + (Mode::Codegen, "build-pass") => (), + (Mode::Incremental, _) => { + if revision.is_some() && !self.revisions.iter().all(|r| r.starts_with("cfail")) { + panic!("`{s}` header is only supported in `cfail` incremental tests") + } + } + (mode, _) => panic!("`{s}` header is not supported in `{mode}` tests"), + }; + let pass_mode = if config.parse_name_directive(ln, "check-pass") { + check_no_run("check-pass"); + Some(PassMode::Check) + } else if config.parse_name_directive(ln, "build-pass") { + check_no_run("build-pass"); + Some(PassMode::Build) + } else if config.parse_name_directive(ln, "run-pass") { + check_no_run("run-pass"); + Some(PassMode::Run) + } else { + None + }; + match (self.pass_mode, pass_mode) { + (None, Some(_)) => self.pass_mode = pass_mode, + (Some(_), Some(_)) => panic!("multiple `*-pass` headers in a single test"), + (_, None) => {} + } + } + + pub fn pass_mode(&self, config: &Config) -> Option { + if !self.ignore_pass && self.fail_mode.is_none() { + if let mode @ Some(_) = config.force_pass_mode { + return mode; + } + } + self.pass_mode + } + + // does not consider CLI override for pass mode + pub fn local_pass_mode(&self) -> Option { + self.pass_mode + } + + pub fn update_add_core_stubs(&mut self, ln: &str, config: &Config) { + let add_core_stubs = config.parse_name_directive(ln, directives::ADD_CORE_STUBS); + if add_core_stubs { + if !matches!(config.mode, Mode::Ui | Mode::Codegen | Mode::Assembly) { + panic!( + "`add-core-stubs` is currently only supported for ui, codegen and assembly test modes" + ); + } + + // FIXME(jieyouxu): this check is currently order-dependent, but we should probably + // collect all directives in one go then perform a validation pass after that. + if self.local_pass_mode().is_some_and(|pm| pm == PassMode::Run) { + // `minicore` can only be used with non-run modes, because it's `core` prelude stubs + // and can't run. + panic!("`add-core-stubs` cannot be used to run the test binary"); + } + + self.add_core_stubs = add_core_stubs; + } + } +} + +/// If the given line begins with the appropriate comment prefix for a directive, +/// returns a struct containing various parts of the directive. +fn line_directive<'line>( + line_number: usize, + original_line: &'line str, +) -> Option> { + // Ignore lines that don't start with the comment prefix. + let after_comment = + original_line.trim_start().strip_prefix(COMPILETEST_DIRECTIVE_PREFIX)?.trim_start(); + + let revision; + let raw_directive; + + if let Some(after_open_bracket) = after_comment.strip_prefix('[') { + // A comment like `//@[foo]` only applies to revision `foo`. + let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else { + panic!( + "malformed condition directive: expected `{COMPILETEST_DIRECTIVE_PREFIX}[foo]`, found `{original_line}`" + ) + }; + + revision = Some(line_revision); + raw_directive = after_close_bracket.trim_start(); + } else { + revision = None; + raw_directive = after_comment; + }; + + Some(DirectiveLine { line_number, revision, raw_directive }) +} + +// To prevent duplicating the list of directives between `compiletest`,`htmldocck` and `jsondocck`, +// we put it into a common file which is included in rust code and parsed here. +// FIXME: This setup is temporary until we figure out how to improve this situation. +// See . +include!("directive-list.rs"); + +const KNOWN_HTMLDOCCK_DIRECTIVE_NAMES: &[&str] = &[ + "count", + "!count", + "files", + "!files", + "has", + "!has", + "has-dir", + "!has-dir", + "hasraw", + "!hasraw", + "matches", + "!matches", + "matchesraw", + "!matchesraw", + "snapshot", + "!snapshot", +]; + +const KNOWN_JSONDOCCK_DIRECTIVE_NAMES: &[&str] = + &["count", "!count", "has", "!has", "is", "!is", "ismany", "!ismany", "set", "!set"]; + +/// The (partly) broken-down contents of a line containing a test directive, +/// which [`iter_header`] passes to its callback function. +/// +/// For example: +/// +/// ```text +/// //@ compile-flags: -O +/// ^^^^^^^^^^^^^^^^^ raw_directive +/// +/// //@ [foo] compile-flags: -O +/// ^^^ revision +/// ^^^^^^^^^^^^^^^^^ raw_directive +/// ``` +struct DirectiveLine<'ln> { + line_number: usize, + /// Some test directives start with a revision name in square brackets + /// (e.g. `[foo]`), and only apply to that revision of the test. + /// If present, this field contains the revision name (e.g. `foo`). + revision: Option<&'ln str>, + /// The main part of the directive, after removing the comment prefix + /// and the optional revision specifier. + /// + /// This is "raw" because the directive's name and colon-separated value + /// (if present) have not yet been extracted or checked. + raw_directive: &'ln str, +} + +impl<'ln> DirectiveLine<'ln> { + fn applies_to_test_revision(&self, test_revision: Option<&str>) -> bool { + self.revision.is_none() || self.revision == test_revision + } +} + +pub(crate) struct CheckDirectiveResult<'ln> { + is_known_directive: bool, + trailing_directive: Option<&'ln str>, +} + +pub(crate) fn check_directive<'a>( + directive_ln: &'a str, + mode: Mode, + original_line: &str, +) -> CheckDirectiveResult<'a> { + let (directive_name, post) = directive_ln.split_once([':', ' ']).unwrap_or((directive_ln, "")); + + let trailing = post.trim().split_once(' ').map(|(pre, _)| pre).unwrap_or(post); + let is_known = |s: &str| { + KNOWN_DIRECTIVE_NAMES.contains(&s) + || match mode { + Mode::Rustdoc | Mode::RustdocJson => { + original_line.starts_with("//@") + && match mode { + Mode::Rustdoc => KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, + Mode::RustdocJson => KNOWN_JSONDOCCK_DIRECTIVE_NAMES, + _ => unreachable!(), + } + .contains(&s) + } + _ => false, + } + }; + let trailing_directive = { + // 1. is the directive name followed by a space? (to exclude `:`) + matches!(directive_ln.get(directive_name.len()..), Some(s) if s.starts_with(' ')) + // 2. is what is after that directive also a directive (ex: "only-x86 only-arm") + && is_known(trailing) + } + .then_some(trailing); + + CheckDirectiveResult { is_known_directive: is_known(&directive_name), trailing_directive } +} + +const COMPILETEST_DIRECTIVE_PREFIX: &str = "//@"; + +fn iter_header( + mode: Mode, + _suite: &str, + poisoned: &mut bool, + testfile: &Utf8Path, + rdr: impl Read, + it: &mut dyn FnMut(DirectiveLine<'_>), +) { + if testfile.is_dir() { + return; + } + + // Coverage tests in coverage-run mode always have these extra directives, without needing to + // specify them manually in every test file. + // + // FIXME(jieyouxu): I feel like there's a better way to do this, leaving for later. + if mode == Mode::CoverageRun { + let extra_directives: &[&str] = &[ + "needs-profiler-runtime", + // FIXME(pietroalbini): this test currently does not work on cross-compiled targets + // because remote-test is not capable of sending back the *.profraw files generated by + // the LLVM instrumentation. + "ignore-cross-compile", + ]; + // Process the extra implied directives, with a dummy line number of 0. + for raw_directive in extra_directives { + it(DirectiveLine { line_number: 0, revision: None, raw_directive }); + } + } + + let mut rdr = BufReader::with_capacity(1024, rdr); + let mut ln = String::new(); + let mut line_number = 0; + + loop { + line_number += 1; + ln.clear(); + if rdr.read_line(&mut ln).unwrap() == 0 { + break; + } + let ln = ln.trim(); + + let Some(directive_line) = line_directive(line_number, ln) else { + continue; + }; + + // Perform unknown directive check on Rust files. + if testfile.extension().map(|e| e == "rs").unwrap_or(false) { + let CheckDirectiveResult { is_known_directive, trailing_directive } = + check_directive(directive_line.raw_directive, mode, ln); + + if !is_known_directive { + *poisoned = true; + + error!( + "{testfile}:{line_number}: detected unknown compiletest test directive `{}`", + directive_line.raw_directive, + ); + + return; + } + + if let Some(trailing_directive) = &trailing_directive { + *poisoned = true; + + error!( + "{testfile}:{line_number}: detected trailing compiletest test directive `{}`", + trailing_directive, + ); + help!("put the trailing directive in it's own line: `//@ {}`", trailing_directive); + + return; + } + } + + it(directive_line); + } +} + +impl Config { + fn parse_and_update_revisions( + &self, + testfile: &Utf8Path, + line: &str, + existing: &mut Vec, + ) { + const FORBIDDEN_REVISION_NAMES: [&str; 2] = [ + // `//@ revisions: true false` Implying `--cfg=true` and `--cfg=false` makes it very + // weird for the test, since if the test writer wants a cfg of the same revision name + // they'd have to use `cfg(r#true)` and `cfg(r#false)`. + "true", "false", + ]; + + const FILECHECK_FORBIDDEN_REVISION_NAMES: [&str; 9] = + ["CHECK", "COM", "NEXT", "SAME", "EMPTY", "NOT", "COUNT", "DAG", "LABEL"]; + + if let Some(raw) = self.parse_name_value_directive(line, "revisions") { + if self.mode == Mode::RunMake { + panic!("`run-make` tests do not support revisions: {}", testfile); + } + + let mut duplicates: HashSet<_> = existing.iter().cloned().collect(); + for revision in raw.split_whitespace() { + if !duplicates.insert(revision.to_string()) { + panic!("duplicate revision: `{}` in line `{}`: {}", revision, raw, testfile); + } + + if FORBIDDEN_REVISION_NAMES.contains(&revision) { + panic!( + "revision name `{revision}` is not permitted: `{}` in line `{}`: {}", + revision, raw, testfile + ); + } + + if matches!(self.mode, Mode::Assembly | Mode::Codegen | Mode::MirOpt) + && FILECHECK_FORBIDDEN_REVISION_NAMES.contains(&revision) + { + panic!( + "revision name `{revision}` is not permitted in a test suite that uses \ + `FileCheck` annotations as it is confusing when used as custom `FileCheck` \ + prefix: `{revision}` in line `{}`: {}", + raw, testfile + ); + } + + existing.push(revision.to_string()); + } + } + } + + fn parse_env(nv: String) -> (String, String) { + // nv is either FOO or FOO=BAR + // FIXME(Zalathar): The form without `=` seems to be unused; should + // we drop support for it? + let (name, value) = nv.split_once('=').unwrap_or((&nv, "")); + // Trim whitespace from the name, so that `//@ exec-env: FOO=BAR` + // sees the name as `FOO` and not ` FOO`. + let name = name.trim(); + (name.to_owned(), value.to_owned()) + } + + fn parse_pp_exact(&self, line: &str, testfile: &Utf8Path) -> Option { + if let Some(s) = self.parse_name_value_directive(line, "pp-exact") { + Some(Utf8PathBuf::from(&s)) + } else if self.parse_name_directive(line, "pp-exact") { + testfile.file_name().map(Utf8PathBuf::from) + } else { + None + } + } + + fn parse_custom_normalization(&self, raw_directive: &str) -> Option { + // FIXME(Zalathar): Integrate name/value splitting into `DirectiveLine` + // instead of doing it here. + let (directive_name, raw_value) = raw_directive.split_once(':')?; + + let kind = match directive_name { + "normalize-stdout" => NormalizeKind::Stdout, + "normalize-stderr" => NormalizeKind::Stderr, + "normalize-stderr-32bit" => NormalizeKind::Stderr32bit, + "normalize-stderr-64bit" => NormalizeKind::Stderr64bit, + _ => return None, + }; + + let Some((regex, replacement)) = parse_normalize_rule(raw_value) else { + error!("couldn't parse custom normalization rule: `{raw_directive}`"); + help!("expected syntax is: `{directive_name}: \"REGEX\" -> \"REPLACEMENT\"`"); + panic!("invalid normalization rule detected"); + }; + Some(NormalizeRule { kind, regex, replacement }) + } + + fn parse_name_directive(&self, line: &str, directive: &str) -> bool { + // Ensure the directive is a whole word. Do not match "ignore-x86" when + // the line says "ignore-x86_64". + line.starts_with(directive) + && matches!(line.as_bytes().get(directive.len()), None | Some(&b' ') | Some(&b':')) + } + + fn parse_negative_name_directive(&self, line: &str, directive: &str) -> bool { + line.starts_with("no-") && self.parse_name_directive(&line[3..], directive) + } + + pub fn parse_name_value_directive(&self, line: &str, directive: &str) -> Option { + let colon = directive.len(); + if line.starts_with(directive) && line.as_bytes().get(colon) == Some(&b':') { + let value = line[(colon + 1)..].to_owned(); + debug!("{}: {}", directive, value); + Some(expand_variables(value, self)) + } else { + None + } + } + + fn parse_edition(&self, line: &str) -> Option { + self.parse_name_value_directive(line, "edition") + } + + fn set_name_directive(&self, line: &str, directive: &str, value: &mut bool) { + match value { + true => { + if self.parse_negative_name_directive(line, directive) { + *value = false; + } + } + false => { + if self.parse_name_directive(line, directive) { + *value = true; + } + } + } + } + + fn set_name_value_directive( + &self, + line: &str, + directive: &str, + value: &mut Option, + parse: impl FnOnce(String) -> T, + ) { + if value.is_none() { + *value = self.parse_name_value_directive(line, directive).map(parse); + } + } + + fn push_name_value_directive( + &self, + line: &str, + directive: &str, + values: &mut Vec, + parse: impl FnOnce(String) -> T, + ) { + if let Some(value) = self.parse_name_value_directive(line, directive).map(parse) { + values.push(value); + } + } +} + +// FIXME(jieyouxu): fix some of these variable names to more accurately reflect what they do. +fn expand_variables(mut value: String, config: &Config) -> String { + const CWD: &str = "{{cwd}}"; + const SRC_BASE: &str = "{{src-base}}"; + const TEST_SUITE_BUILD_BASE: &str = "{{build-base}}"; + const RUST_SRC_BASE: &str = "{{rust-src-base}}"; + const SYSROOT_BASE: &str = "{{sysroot-base}}"; + const TARGET_LINKER: &str = "{{target-linker}}"; + const TARGET: &str = "{{target}}"; + + if value.contains(CWD) { + let cwd = env::current_dir().unwrap(); + value = value.replace(CWD, &cwd.to_str().unwrap()); + } + + if value.contains(SRC_BASE) { + value = value.replace(SRC_BASE, &config.src_test_suite_root.as_str()); + } + + if value.contains(TEST_SUITE_BUILD_BASE) { + value = value.replace(TEST_SUITE_BUILD_BASE, &config.build_test_suite_root.as_str()); + } + + if value.contains(SYSROOT_BASE) { + value = value.replace(SYSROOT_BASE, &config.sysroot_base.as_str()); + } + + if value.contains(TARGET_LINKER) { + value = value.replace(TARGET_LINKER, config.target_linker.as_deref().unwrap_or("")); + } + + if value.contains(TARGET) { + value = value.replace(TARGET, &config.target); + } + + if value.contains(RUST_SRC_BASE) { + let src_base = config.sysroot_base.join("lib/rustlib/src/rust"); + src_base.try_exists().expect(&*format!("{} should exists", src_base)); + let src_base = src_base.read_link_utf8().unwrap_or(src_base); + value = value.replace(RUST_SRC_BASE, &src_base.as_str()); + } + + value +} + +struct NormalizeRule { + kind: NormalizeKind, + regex: String, + replacement: String, +} + +enum NormalizeKind { + Stdout, + Stderr, + Stderr32bit, + Stderr64bit, +} + +/// Parses the regex and replacement values of a `//@ normalize-*` header, +/// in the format: +/// ```text +/// "REGEX" -> "REPLACEMENT" +/// ``` +fn parse_normalize_rule(raw_value: &str) -> Option<(String, String)> { + // FIXME: Support escaped double-quotes in strings. + let captures = static_regex!( + r#"(?x) # (verbose mode regex) + ^ + \s* # (leading whitespace) + "(?[^"]*)" # "REGEX" + \s+->\s+ # -> + "(?[^"]*)" # "REPLACEMENT" + $ + "# + ) + .captures(raw_value)?; + let regex = captures["regex"].to_owned(); + let replacement = captures["replacement"].to_owned(); + // A `\n` sequence in the replacement becomes an actual newline. + // FIXME: Do unescaping in a less ad-hoc way, and perhaps support escaped + // backslashes and double-quotes. + let replacement = replacement.replace("\\n", "\n"); + Some((regex, replacement)) +} + +/// Given an llvm version string that looks like `1.2.3-rc1`, extract as semver. Note that this +/// accepts more than just strict `semver` syntax (as in `major.minor.patch`); this permits omitting +/// minor and patch version components so users can write e.g. `//@ min-llvm-version: 19` instead of +/// having to write `//@ min-llvm-version: 19.0.0`. +/// +/// Currently panics if the input string is malformed, though we really should not use panic as an +/// error handling strategy. +/// +/// FIXME(jieyouxu): improve error handling +pub fn extract_llvm_version(version: &str) -> Version { + // The version substring we're interested in usually looks like the `1.2.3`, without any of the + // fancy suffix like `-rc1` or `meow`. + let version = version.trim(); + let uninterested = |c: char| !c.is_ascii_digit() && c != '.'; + let version_without_suffix = match version.split_once(uninterested) { + Some((prefix, _suffix)) => prefix, + None => version, + }; + + let components: Vec = version_without_suffix + .split('.') + .map(|s| s.parse().expect("llvm version component should consist of only digits")) + .collect(); + + match &components[..] { + [major] => Version::new(*major, 0, 0), + [major, minor] => Version::new(*major, *minor, 0), + [major, minor, patch] => Version::new(*major, *minor, *patch), + _ => panic!("malformed llvm version string, expected only 1-3 components: {version}"), + } +} + +pub fn extract_llvm_version_from_binary(binary_path: &str) -> Option { + let output = Command::new(binary_path).arg("--version").output().ok()?; + if !output.status.success() { + return None; + } + let version = String::from_utf8(output.stdout).ok()?; + for line in version.lines() { + if let Some(version) = line.split("LLVM version ").nth(1) { + return Some(extract_llvm_version(version)); + } + } + None +} + +/// For tests using the `needs-llvm-zstd` directive: +/// - for local LLVM builds, try to find the static zstd library in the llvm-config system libs. +/// - for `download-ci-llvm`, see if `lld` was built with zstd support. +pub fn llvm_has_libzstd(config: &Config) -> bool { + // Strategy 1: works for local builds but not with `download-ci-llvm`. + // + // We check whether `llvm-config` returns the zstd library. Bootstrap's `llvm.libzstd` will only + // ask to statically link it when building LLVM, so we only check if the list of system libs + // contains a path to that static lib, and that it exists. + // + // See compiler/rustc_llvm/build.rs for more details and similar expectations. + fn is_zstd_in_config(llvm_bin_dir: &Utf8Path) -> Option<()> { + let llvm_config_path = llvm_bin_dir.join("llvm-config"); + let output = Command::new(llvm_config_path).arg("--system-libs").output().ok()?; + assert!(output.status.success(), "running llvm-config --system-libs failed"); + + let libs = String::from_utf8(output.stdout).ok()?; + for lib in libs.split_whitespace() { + if lib.ends_with("libzstd.a") && Utf8Path::new(lib).exists() { + return Some(()); + } + } + + None + } + + // Strategy 2: `download-ci-llvm`'s `llvm-config --system-libs` will not return any libs to + // use. + // + // The CI artifacts also don't contain the bootstrap config used to build them: otherwise we + // could have looked at the `llvm.libzstd` config. + // + // We infer whether `LLVM_ENABLE_ZSTD` was used to build LLVM as a byproduct of testing whether + // `lld` supports it. If not, an error will be emitted: "LLVM was not built with + // LLVM_ENABLE_ZSTD or did not find zstd at build time". + #[cfg(unix)] + fn is_lld_built_with_zstd(llvm_bin_dir: &Utf8Path) -> Option<()> { + let lld_path = llvm_bin_dir.join("lld"); + if lld_path.exists() { + // We can't call `lld` as-is, it expects to be invoked by a compiler driver using a + // different name. Prepare a temporary symlink to do that. + let lld_symlink_path = llvm_bin_dir.join("ld.lld"); + if !lld_symlink_path.exists() { + std::os::unix::fs::symlink(lld_path, &lld_symlink_path).ok()?; + } + + // Run `lld` with a zstd flag. We expect this command to always error here, we don't + // want to link actual files and don't pass any. + let output = Command::new(&lld_symlink_path) + .arg("--compress-debug-sections=zstd") + .output() + .ok()?; + assert!(!output.status.success()); + + // Look for a specific error caused by LLVM not being built with zstd support. We could + // also look for the "no input files" message, indicating the zstd flag was accepted. + let stderr = String::from_utf8(output.stderr).ok()?; + let zstd_available = !stderr.contains("LLVM was not built with LLVM_ENABLE_ZSTD"); + + // We don't particularly need to clean the link up (so the previous commands could fail + // in theory but won't in practice), but we can try. + std::fs::remove_file(lld_symlink_path).ok()?; + + if zstd_available { + return Some(()); + } + } + + None + } + + #[cfg(not(unix))] + fn is_lld_built_with_zstd(_llvm_bin_dir: &Utf8Path) -> Option<()> { + None + } + + if let Some(llvm_bin_dir) = &config.llvm_bin_dir { + // Strategy 1: for local LLVM builds. + if is_zstd_in_config(llvm_bin_dir).is_some() { + return true; + } + + // Strategy 2: for LLVM artifacts built on CI via `download-ci-llvm`. + // + // It doesn't work for cases where the artifacts don't contain the linker, but it's + // best-effort: CI has `llvm.libzstd` and `lld` enabled on the x64 linux artifacts, so it + // will at least work there. + // + // If this can be improved and expanded to less common cases in the future, it should. + if config.target == "x86_64-unknown-linux-gnu" + && config.host == config.target + && is_lld_built_with_zstd(llvm_bin_dir).is_some() + { + return true; + } + } + + // Otherwise, all hope is lost. + false +} + +/// Takes a directive of the form `" [- ]"`, returns the numeric representation +/// of `` and `` as tuple: `(, )`. +/// +/// If the `` part is omitted, the second component of the tuple is the same as +/// ``. +fn extract_version_range<'a, F, VersionTy: Clone>( + line: &'a str, + parse: F, +) -> Option<(VersionTy, VersionTy)> +where + F: Fn(&'a str) -> Option, +{ + let mut splits = line.splitn(2, "- ").map(str::trim); + let min = splits.next().unwrap(); + if min.ends_with('-') { + return None; + } + + let max = splits.next(); + + if min.is_empty() { + return None; + } + + let min = parse(min)?; + let max = match max { + Some("") => return None, + Some(max) => parse(max)?, + _ => min.clone(), + }; + + Some((min, max)) +} + +pub(crate) fn make_test_description( + config: &Config, + cache: &HeadersCache, + name: String, + path: &Utf8Path, + src: R, + test_revision: Option<&str>, + poisoned: &mut bool, +) -> CollectedTestDesc { + let mut ignore = false; + let mut ignore_message = None; + let mut should_fail = false; + + let mut local_poisoned = false; + + // Scan through the test file to handle `ignore-*`, `only-*`, and `needs-*` directives. + iter_header( + config.mode, + &config.suite, + &mut local_poisoned, + path, + src, + &mut |directive @ DirectiveLine { line_number, raw_directive: ln, .. }| { + if !directive.applies_to_test_revision(test_revision) { + return; + } + + macro_rules! decision { + ($e:expr) => { + match $e { + IgnoreDecision::Ignore { reason } => { + ignore = true; + ignore_message = Some(reason.into()); + } + IgnoreDecision::Error { message } => { + error!("{path}:{line_number}: {message}"); + *poisoned = true; + return; + } + IgnoreDecision::Continue => {} + } + }; + } + + decision!(cfg::handle_ignore(config, ln)); + decision!(cfg::handle_only(config, ln)); + decision!(needs::handle_needs(&cache.needs, config, ln)); + decision!(ignore_llvm(config, path, ln)); + decision!(ignore_cdb(config, ln)); + decision!(ignore_gdb(config, ln)); + decision!(ignore_lldb(config, ln)); + + if config.target == "wasm32-unknown-unknown" + && config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS) + { + decision!(IgnoreDecision::Ignore { + reason: "ignored on WASM as the run results cannot be checked there".into(), + }); + } + + should_fail |= config.parse_name_directive(ln, "should-fail"); + }, + ); + + if local_poisoned { + eprintln!("errors encountered when trying to make test description: {}", path); + panic!("errors encountered when trying to make test description"); + } + + // The `should-fail` annotation doesn't apply to pretty tests, + // since we run the pretty printer across all tests by default. + // If desired, we could add a `should-fail-pretty` annotation. + let should_panic = match config.mode { + crate::common::Pretty => ShouldPanic::No, + _ if should_fail => ShouldPanic::Yes, + _ => ShouldPanic::No, + }; + + CollectedTestDesc { name, ignore, ignore_message, should_panic } +} + +fn ignore_cdb(config: &Config, line: &str) -> IgnoreDecision { + if config.debugger != Some(Debugger::Cdb) { + return IgnoreDecision::Continue; + } + + if let Some(actual_version) = config.cdb_version { + if let Some(rest) = line.strip_prefix("min-cdb-version:").map(str::trim) { + let min_version = extract_cdb_version(rest).unwrap_or_else(|| { + panic!("couldn't parse version range: {:?}", rest); + }); + + // Ignore if actual version is smaller than the minimum + // required version + if actual_version < min_version { + return IgnoreDecision::Ignore { + reason: format!("ignored when the CDB version is lower than {rest}"), + }; + } + } + } + IgnoreDecision::Continue +} + +fn ignore_gdb(config: &Config, line: &str) -> IgnoreDecision { + if config.debugger != Some(Debugger::Gdb) { + return IgnoreDecision::Continue; + } + + if let Some(actual_version) = config.gdb_version { + if let Some(rest) = line.strip_prefix("min-gdb-version:").map(str::trim) { + let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version) + .unwrap_or_else(|| { + panic!("couldn't parse version range: {:?}", rest); + }); + + if start_ver != end_ver { + panic!("Expected single GDB version") + } + // Ignore if actual version is smaller than the minimum + // required version + if actual_version < start_ver { + return IgnoreDecision::Ignore { + reason: format!("ignored when the GDB version is lower than {rest}"), + }; + } + } else if let Some(rest) = line.strip_prefix("ignore-gdb-version:").map(str::trim) { + let (min_version, max_version) = extract_version_range(rest, extract_gdb_version) + .unwrap_or_else(|| { + panic!("couldn't parse version range: {:?}", rest); + }); + + if max_version < min_version { + panic!("Malformed GDB version range: max < min") + } + + if actual_version >= min_version && actual_version <= max_version { + if min_version == max_version { + return IgnoreDecision::Ignore { + reason: format!("ignored when the GDB version is {rest}"), + }; + } else { + return IgnoreDecision::Ignore { + reason: format!("ignored when the GDB version is between {rest}"), + }; + } + } + } + } + IgnoreDecision::Continue +} + +fn ignore_lldb(config: &Config, line: &str) -> IgnoreDecision { + if config.debugger != Some(Debugger::Lldb) { + return IgnoreDecision::Continue; + } + + if let Some(actual_version) = config.lldb_version { + if let Some(rest) = line.strip_prefix("min-lldb-version:").map(str::trim) { + let min_version = rest.parse().unwrap_or_else(|e| { + panic!("Unexpected format of LLDB version string: {}\n{:?}", rest, e); + }); + // Ignore if actual version is smaller the minimum required + // version + if actual_version < min_version { + return IgnoreDecision::Ignore { + reason: format!("ignored when the LLDB version is {rest}"), + }; + } + } + } + IgnoreDecision::Continue +} + +fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision { + if let Some(needed_components) = + config.parse_name_value_directive(line, "needs-llvm-components") + { + let components: HashSet<_> = config.llvm_components.split_whitespace().collect(); + if let Some(missing_component) = needed_components + .split_whitespace() + .find(|needed_component| !components.contains(needed_component)) + { + if env::var_os("COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS").is_some() { + panic!( + "missing LLVM component {}, and COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS is set: {}", + missing_component, path + ); + } + return IgnoreDecision::Ignore { + reason: format!("ignored when the {missing_component} LLVM component is missing"), + }; + } + } + if let Some(actual_version) = &config.llvm_version { + // Note that these `min` versions will check for not just major versions. + + if let Some(version_string) = config.parse_name_value_directive(line, "min-llvm-version") { + let min_version = extract_llvm_version(&version_string); + // Ignore if actual version is smaller than the minimum required version. + if *actual_version < min_version { + return IgnoreDecision::Ignore { + reason: format!( + "ignored when the LLVM version {actual_version} is older than {min_version}" + ), + }; + } + } else if let Some(version_string) = + config.parse_name_value_directive(line, "max-llvm-major-version") + { + let max_version = extract_llvm_version(&version_string); + // Ignore if actual major version is larger than the maximum required major version. + if actual_version.major > max_version.major { + return IgnoreDecision::Ignore { + reason: format!( + "ignored when the LLVM version ({actual_version}) is newer than major\ + version {}", + max_version.major + ), + }; + } + } else if let Some(version_string) = + config.parse_name_value_directive(line, "min-system-llvm-version") + { + let min_version = extract_llvm_version(&version_string); + // Ignore if using system LLVM and actual version + // is smaller the minimum required version + if config.system_llvm && *actual_version < min_version { + return IgnoreDecision::Ignore { + reason: format!( + "ignored when the system LLVM version {actual_version} is older than {min_version}" + ), + }; + } + } else if let Some(version_range) = + config.parse_name_value_directive(line, "ignore-llvm-version") + { + // Syntax is: "ignore-llvm-version: [- ]" + let (v_min, v_max) = + extract_version_range(&version_range, |s| Some(extract_llvm_version(s))) + .unwrap_or_else(|| { + panic!("couldn't parse version range: \"{version_range}\""); + }); + if v_max < v_min { + panic!("malformed LLVM version range where {v_max} < {v_min}") + } + // Ignore if version lies inside of range. + if *actual_version >= v_min && *actual_version <= v_max { + if v_min == v_max { + return IgnoreDecision::Ignore { + reason: format!("ignored when the LLVM version is {actual_version}"), + }; + } else { + return IgnoreDecision::Ignore { + reason: format!( + "ignored when the LLVM version is between {v_min} and {v_max}" + ), + }; + } + } + } else if let Some(version_string) = + config.parse_name_value_directive(line, "exact-llvm-major-version") + { + // Syntax is "exact-llvm-major-version: " + let version = extract_llvm_version(&version_string); + if actual_version.major != version.major { + return IgnoreDecision::Ignore { + reason: format!( + "ignored when the actual LLVM major version is {}, but the test only targets major version {}", + actual_version.major, version.major + ), + }; + } + } + } + IgnoreDecision::Continue +} + +enum IgnoreDecision { + Ignore { reason: String }, + Continue, + Error { message: String }, +} diff --git a/src/tools/compiletest/src/directives/auxiliary.rs b/src/tools/compiletest/src/directives/auxiliary.rs new file mode 100644 index 00000000000..0e1f3a785f8 --- /dev/null +++ b/src/tools/compiletest/src/directives/auxiliary.rs @@ -0,0 +1,65 @@ +//! Code for dealing with test directives that request an "auxiliary" crate to +//! be built and made available to the test in some way. + +use std::iter; + +use crate::common::Config; +use crate::header::directives::{AUX_BIN, AUX_BUILD, AUX_CODEGEN_BACKEND, AUX_CRATE, PROC_MACRO}; + +/// Properties parsed from `aux-*` test directives. +#[derive(Clone, Debug, Default)] +pub(crate) struct AuxProps { + /// Other crates that should be built and made available to this test. + /// These are filenames relative to `./auxiliary/` in the test's directory. + pub(crate) builds: Vec, + /// Auxiliary crates that should be compiled as `#![crate_type = "bin"]`. + pub(crate) bins: Vec, + /// Similar to `builds`, but a list of NAME=somelib.rs of dependencies + /// to build and pass with the `--extern` flag. + pub(crate) crates: Vec<(String, String)>, + /// Same as `builds`, but for proc-macros. + pub(crate) proc_macros: Vec, + /// Similar to `builds`, but also uses the resulting dylib as a + /// `-Zcodegen-backend` when compiling the test file. + pub(crate) codegen_backend: Option, +} + +impl AuxProps { + /// Yields all of the paths (relative to `./auxiliary/`) that have been + /// specified in `aux-*` directives for this test. + pub(crate) fn all_aux_path_strings(&self) -> impl Iterator { + let Self { builds, bins, crates, proc_macros, codegen_backend } = self; + + iter::empty() + .chain(builds.iter().map(String::as_str)) + .chain(bins.iter().map(String::as_str)) + .chain(crates.iter().map(|(_, path)| path.as_str())) + .chain(proc_macros.iter().map(String::as_str)) + .chain(codegen_backend.iter().map(String::as_str)) + } +} + +/// If the given test directive line contains an `aux-*` directive, parse it +/// and update [`AuxProps`] accordingly. +pub(super) fn parse_and_update_aux(config: &Config, ln: &str, aux: &mut AuxProps) { + if !(ln.starts_with("aux-") || ln.starts_with("proc-macro")) { + return; + } + + config.push_name_value_directive(ln, AUX_BUILD, &mut aux.builds, |r| r.trim().to_string()); + config.push_name_value_directive(ln, AUX_BIN, &mut aux.bins, |r| r.trim().to_string()); + config.push_name_value_directive(ln, AUX_CRATE, &mut aux.crates, parse_aux_crate); + config + .push_name_value_directive(ln, PROC_MACRO, &mut aux.proc_macros, |r| r.trim().to_string()); + if let Some(r) = config.parse_name_value_directive(ln, AUX_CODEGEN_BACKEND) { + aux.codegen_backend = Some(r.trim().to_owned()); + } +} + +fn parse_aux_crate(r: String) -> (String, String) { + let mut parts = r.trim().splitn(2, '='); + ( + parts.next().expect("missing aux-crate name (e.g. log=log.rs)").to_string(), + parts.next().expect("missing aux-crate value (e.g. log=log.rs)").to_string(), + ) +} diff --git a/src/tools/compiletest/src/directives/cfg.rs b/src/tools/compiletest/src/directives/cfg.rs new file mode 100644 index 00000000000..35f6a9e1644 --- /dev/null +++ b/src/tools/compiletest/src/directives/cfg.rs @@ -0,0 +1,414 @@ +use std::collections::HashSet; + +use crate::common::{CompareMode, Config, Debugger}; +use crate::directives::IgnoreDecision; + +const EXTRA_ARCHS: &[&str] = &["spirv"]; + +pub(super) fn handle_ignore(config: &Config, line: &str) -> IgnoreDecision { + let parsed = parse_cfg_name_directive(config, line, "ignore"); + match parsed.outcome { + MatchOutcome::NoMatch => IgnoreDecision::Continue, + MatchOutcome::Match => IgnoreDecision::Ignore { + reason: match parsed.comment { + Some(comment) => format!("ignored {} ({comment})", parsed.pretty_reason.unwrap()), + None => format!("ignored {}", parsed.pretty_reason.unwrap()), + }, + }, + MatchOutcome::Invalid => IgnoreDecision::Error { message: format!("invalid line: {line}") }, + MatchOutcome::External => IgnoreDecision::Continue, + MatchOutcome::NotADirective => IgnoreDecision::Continue, + } +} + +pub(super) fn handle_only(config: &Config, line: &str) -> IgnoreDecision { + let parsed = parse_cfg_name_directive(config, line, "only"); + match parsed.outcome { + MatchOutcome::Match => IgnoreDecision::Continue, + MatchOutcome::NoMatch => IgnoreDecision::Ignore { + reason: match parsed.comment { + Some(comment) => { + format!("only executed {} ({comment})", parsed.pretty_reason.unwrap()) + } + None => format!("only executed {}", parsed.pretty_reason.unwrap()), + }, + }, + MatchOutcome::Invalid => IgnoreDecision::Error { message: format!("invalid line: {line}") }, + MatchOutcome::External => IgnoreDecision::Continue, + MatchOutcome::NotADirective => IgnoreDecision::Continue, + } +} + +/// Parses a name-value directive which contains config-specific information, e.g., `ignore-x86` +/// or `only-windows`. +fn parse_cfg_name_directive<'a>( + config: &Config, + line: &'a str, + prefix: &str, +) -> ParsedNameDirective<'a> { + if !line.as_bytes().starts_with(prefix.as_bytes()) { + return ParsedNameDirective::not_a_directive(); + } + if line.as_bytes().get(prefix.len()) != Some(&b'-') { + return ParsedNameDirective::not_a_directive(); + } + let line = &line[prefix.len() + 1..]; + + let (name, comment) = + line.split_once(&[':', ' ']).map(|(l, c)| (l, Some(c))).unwrap_or((line, None)); + + // Some of the matchers might be "" depending on what the target information is. To avoid + // problems we outright reject empty directives. + if name.is_empty() { + return ParsedNameDirective::not_a_directive(); + } + + let mut outcome = MatchOutcome::Invalid; + let mut message = None; + + macro_rules! condition { + ( + name: $name:expr, + $(allowed_names: $allowed_names:expr,)? + $(condition: $condition:expr,)? + message: $($message:tt)* + ) => {{ + // This is not inlined to avoid problems with macro repetitions. + let format_message = || format!($($message)*); + + if outcome != MatchOutcome::Invalid { + // Ignore all other matches if we already found one + } else if $name.custom_matches(name) { + message = Some(format_message()); + if true $(&& $condition)? { + outcome = MatchOutcome::Match; + } else { + outcome = MatchOutcome::NoMatch; + } + } + $(else if $allowed_names.custom_contains(name) { + message = Some(format_message()); + outcome = MatchOutcome::NoMatch; + })? + }}; + } + + let target_cfgs = config.target_cfgs(); + let target_cfg = config.target_cfg(); + + condition! { + name: "test", + message: "always" + } + condition! { + name: "auxiliary", + message: "used by another main test file" + } + condition! { + name: &config.target, + allowed_names: &target_cfgs.all_targets, + message: "when the target is {name}" + } + condition! { + name: &[ + Some(&*target_cfg.os), + // If something is ignored for emscripten, it likely also needs to be + // ignored for wasm32-unknown-unknown. + (config.target == "wasm32-unknown-unknown").then_some("emscripten"), + ], + allowed_names: &target_cfgs.all_oses, + message: "when the operating system is {name}" + } + condition! { + name: &target_cfg.env, + allowed_names: &target_cfgs.all_envs, + message: "when the target environment is {name}" + } + condition! { + name: &target_cfg.os_and_env(), + allowed_names: &target_cfgs.all_oses_and_envs, + message: "when the operating system and target environment are {name}" + } + condition! { + name: &target_cfg.abi, + allowed_names: &target_cfgs.all_abis, + message: "when the ABI is {name}" + } + condition! { + name: &target_cfg.arch, + allowed_names: ContainsEither { a: &target_cfgs.all_archs, b: &EXTRA_ARCHS }, + message: "when the architecture is {name}" + } + condition! { + name: format!("{}bit", target_cfg.pointer_width), + allowed_names: &target_cfgs.all_pointer_widths, + message: "when the pointer width is {name}" + } + condition! { + name: &*target_cfg.families, + allowed_names: &target_cfgs.all_families, + message: "when the target family is {name}" + } + + // `wasm32-bare` is an alias to refer to just wasm32-unknown-unknown + // (in contrast to `wasm32` which also matches non-bare targets) + condition! { + name: "wasm32-bare", + condition: config.target == "wasm32-unknown-unknown", + message: "when the target is WASM" + } + + condition! { + name: "thumb", + condition: config.target.starts_with("thumb"), + message: "when the architecture is part of the Thumb family" + } + + condition! { + name: "apple", + condition: config.target.contains("apple"), + message: "when the target vendor is Apple" + } + + condition! { + name: "elf", + condition: !config.target.contains("windows") + && !config.target.contains("wasm") + && !config.target.contains("apple") + && !config.target.contains("aix") + && !config.target.contains("uefi"), + message: "when the target binary format is ELF" + } + + condition! { + name: "enzyme", + condition: config.has_enzyme, + message: "when rustc is built with LLVM Enzyme" + } + + // Technically the locally built compiler uses the "dev" channel rather than the "nightly" + // channel, even though most people don't know or won't care about it. To avoid confusion, we + // treat the "dev" channel as the "nightly" channel when processing the directive. + condition! { + name: if config.channel == "dev" { "nightly" } else { &config.channel }, + allowed_names: &["stable", "beta", "nightly"], + message: "when the release channel is {name}", + } + + condition! { + name: "cross-compile", + condition: config.target != config.host, + message: "when cross-compiling" + } + condition! { + name: "endian-big", + condition: config.is_big_endian(), + message: "on big-endian targets", + } + condition! { + name: format!("stage{}", config.stage).as_str(), + allowed_names: &["stage0", "stage1", "stage2"], + message: "when the bootstrapping stage is {name}", + } + condition! { + name: "remote", + condition: config.remote_test_client.is_some(), + message: "when running tests remotely", + } + condition! { + name: "rustc-debug-assertions", + condition: config.with_rustc_debug_assertions, + message: "when rustc is built with debug assertions", + } + condition! { + name: "std-debug-assertions", + condition: config.with_std_debug_assertions, + message: "when std is built with debug assertions", + } + condition! { + name: config.debugger.as_ref().map(|d| d.to_str()), + allowed_names: &Debugger::STR_VARIANTS, + message: "when the debugger is {name}", + } + condition! { + name: config.compare_mode + .as_ref() + .map(|d| format!("compare-mode-{}", d.to_str())), + allowed_names: ContainsPrefixed { + prefix: "compare-mode-", + inner: CompareMode::STR_VARIANTS, + }, + message: "when comparing with {name}", + } + // Coverage tests run the same test file in multiple modes. + // If a particular test should not be run in one of the modes, ignore it + // with "ignore-coverage-map" or "ignore-coverage-run". + condition! { + name: config.mode.to_str(), + allowed_names: ["coverage-map", "coverage-run"], + message: "when the test mode is {name}", + } + condition! { + name: target_cfg.rustc_abi.as_ref().map(|abi| format!("rustc_abi-{abi}")).unwrap_or_default(), + allowed_names: ContainsPrefixed { + prefix: "rustc_abi-", + inner: target_cfgs.all_rustc_abis.clone(), + }, + message: "when the target `rustc_abi` is {name}", + } + + condition! { + name: "dist", + condition: std::env::var("COMPILETEST_ENABLE_DIST_TESTS") == Ok("1".to_string()), + message: "when performing tests on dist toolchain" + } + + if prefix == "ignore" && outcome == MatchOutcome::Invalid { + // Don't error out for ignore-tidy-* diretives, as those are not handled by compiletest. + if name.starts_with("tidy-") { + outcome = MatchOutcome::External; + } + + // Don't error out for ignore-pass, as that is handled elsewhere. + if name == "pass" { + outcome = MatchOutcome::External; + } + + // Don't error out for ignore-llvm-version, that has a custom syntax and is handled + // elsewhere. + if name == "llvm-version" { + outcome = MatchOutcome::External; + } + + // Don't error out for ignore-llvm-version, that has a custom syntax and is handled + // elsewhere. + if name == "gdb-version" { + outcome = MatchOutcome::External; + } + } + + ParsedNameDirective { + name: Some(name), + comment: comment.map(|c| c.trim().trim_start_matches('-').trim()), + outcome, + pretty_reason: message, + } +} + +/// The result of parse_cfg_name_directive. +#[derive(Clone, PartialEq, Debug)] +pub(super) struct ParsedNameDirective<'a> { + pub(super) name: Option<&'a str>, + pub(super) pretty_reason: Option, + pub(super) comment: Option<&'a str>, + pub(super) outcome: MatchOutcome, +} + +impl ParsedNameDirective<'_> { + fn not_a_directive() -> Self { + Self { + name: None, + pretty_reason: None, + comment: None, + outcome: MatchOutcome::NotADirective, + } + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub(super) enum MatchOutcome { + /// No match. + NoMatch, + /// Match. + Match, + /// The directive was invalid. + Invalid, + /// The directive is handled by other parts of our tooling. + External, + /// The line is not actually a directive. + NotADirective, +} + +trait CustomContains { + fn custom_contains(&self, item: &str) -> bool; +} + +impl CustomContains for HashSet { + fn custom_contains(&self, item: &str) -> bool { + self.contains(item) + } +} + +impl CustomContains for &[&str] { + fn custom_contains(&self, item: &str) -> bool { + self.contains(&item) + } +} + +impl CustomContains for [&str; N] { + fn custom_contains(&self, item: &str) -> bool { + self.contains(&item) + } +} + +struct ContainsPrefixed { + prefix: &'static str, + inner: T, +} + +impl CustomContains for ContainsPrefixed { + fn custom_contains(&self, item: &str) -> bool { + match item.strip_prefix(self.prefix) { + Some(stripped) => self.inner.custom_contains(stripped), + None => false, + } + } +} + +struct ContainsEither<'a, A: CustomContains, B: CustomContains> { + a: &'a A, + b: &'a B, +} + +impl CustomContains for ContainsEither<'_, A, B> { + fn custom_contains(&self, item: &str) -> bool { + self.a.custom_contains(item) || self.b.custom_contains(item) + } +} + +trait CustomMatches { + fn custom_matches(&self, name: &str) -> bool; +} + +impl CustomMatches for &str { + fn custom_matches(&self, name: &str) -> bool { + name == *self + } +} + +impl CustomMatches for String { + fn custom_matches(&self, name: &str) -> bool { + name == self + } +} + +impl CustomMatches for &[T] { + fn custom_matches(&self, name: &str) -> bool { + self.iter().any(|m| m.custom_matches(name)) + } +} + +impl CustomMatches for [T; N] { + fn custom_matches(&self, name: &str) -> bool { + self.iter().any(|m| m.custom_matches(name)) + } +} + +impl CustomMatches for Option { + fn custom_matches(&self, name: &str) -> bool { + match self { + Some(inner) => inner.custom_matches(name), + None => false, + } + } +} diff --git a/src/tools/compiletest/src/directives/needs.rs b/src/tools/compiletest/src/directives/needs.rs new file mode 100644 index 00000000000..ee46f4c70cb --- /dev/null +++ b/src/tools/compiletest/src/directives/needs.rs @@ -0,0 +1,428 @@ +use crate::common::{Config, KNOWN_CRATE_TYPES, KNOWN_TARGET_HAS_ATOMIC_WIDTHS, Sanitizer}; +use crate::directives::{IgnoreDecision, llvm_has_libzstd}; + +pub(super) fn handle_needs( + cache: &CachedNeedsConditions, + config: &Config, + ln: &str, +) -> IgnoreDecision { + // Note that we intentionally still put the needs- prefix here to make the file show up when + // grepping for a directive name, even though we could technically strip that. + let needs = &[ + Need { + name: "needs-asm-support", + condition: config.has_asm_support(), + ignore_reason: "ignored on targets without inline assembly support", + }, + Need { + name: "needs-sanitizer-support", + condition: cache.sanitizer_support, + ignore_reason: "ignored on targets without sanitizers support", + }, + Need { + name: "needs-sanitizer-address", + condition: cache.sanitizer_address, + ignore_reason: "ignored on targets without address sanitizer", + }, + Need { + name: "needs-sanitizer-cfi", + condition: cache.sanitizer_cfi, + ignore_reason: "ignored on targets without CFI sanitizer", + }, + Need { + name: "needs-sanitizer-dataflow", + condition: cache.sanitizer_dataflow, + ignore_reason: "ignored on targets without dataflow sanitizer", + }, + Need { + name: "needs-sanitizer-kcfi", + condition: cache.sanitizer_kcfi, + ignore_reason: "ignored on targets without kernel CFI sanitizer", + }, + Need { + name: "needs-sanitizer-kasan", + condition: cache.sanitizer_kasan, + ignore_reason: "ignored on targets without kernel address sanitizer", + }, + Need { + name: "needs-sanitizer-leak", + condition: cache.sanitizer_leak, + ignore_reason: "ignored on targets without leak sanitizer", + }, + Need { + name: "needs-sanitizer-memory", + condition: cache.sanitizer_memory, + ignore_reason: "ignored on targets without memory sanitizer", + }, + Need { + name: "needs-sanitizer-thread", + condition: cache.sanitizer_thread, + ignore_reason: "ignored on targets without thread sanitizer", + }, + Need { + name: "needs-sanitizer-hwaddress", + condition: cache.sanitizer_hwaddress, + ignore_reason: "ignored on targets without hardware-assisted address sanitizer", + }, + Need { + name: "needs-sanitizer-memtag", + condition: cache.sanitizer_memtag, + ignore_reason: "ignored on targets without memory tagging sanitizer", + }, + Need { + name: "needs-sanitizer-shadow-call-stack", + condition: cache.sanitizer_shadow_call_stack, + ignore_reason: "ignored on targets without shadow call stacks", + }, + Need { + name: "needs-sanitizer-safestack", + condition: cache.sanitizer_safestack, + ignore_reason: "ignored on targets without SafeStack support", + }, + Need { + name: "needs-enzyme", + condition: config.has_enzyme, + ignore_reason: "ignored when LLVM Enzyme is disabled", + }, + Need { + name: "needs-run-enabled", + condition: config.run_enabled(), + ignore_reason: "ignored when running the resulting test binaries is disabled", + }, + Need { + name: "needs-threads", + condition: config.has_threads(), + ignore_reason: "ignored on targets without threading support", + }, + Need { + name: "needs-subprocess", + condition: config.has_subprocess_support(), + ignore_reason: "ignored on targets without subprocess support", + }, + Need { + name: "needs-unwind", + condition: config.can_unwind(), + ignore_reason: "ignored on targets without unwinding support", + }, + Need { + name: "needs-profiler-runtime", + condition: config.profiler_runtime, + ignore_reason: "ignored when the profiler runtime is not available", + }, + Need { + name: "needs-force-clang-based-tests", + condition: config.run_clang_based_tests_with.is_some(), + ignore_reason: "ignored when RUSTBUILD_FORCE_CLANG_BASED_TESTS is not set", + }, + Need { + name: "needs-xray", + condition: cache.xray, + ignore_reason: "ignored on targets without xray tracing", + }, + Need { + name: "needs-rust-lld", + condition: cache.rust_lld, + ignore_reason: "ignored on targets without Rust's LLD", + }, + Need { + name: "needs-dlltool", + condition: cache.dlltool, + ignore_reason: "ignored when dlltool for the current architecture is not present", + }, + Need { + name: "needs-git-hash", + condition: config.git_hash, + ignore_reason: "ignored when git hashes have been omitted for building", + }, + Need { + name: "needs-dynamic-linking", + condition: config.target_cfg().dynamic_linking, + ignore_reason: "ignored on targets without dynamic linking", + }, + Need { + name: "needs-relocation-model-pic", + condition: config.target_cfg().relocation_model == "pic", + ignore_reason: "ignored on targets without PIC relocation model", + }, + Need { + name: "needs-deterministic-layouts", + condition: !config.rust_randomized_layout, + ignore_reason: "ignored when randomizing layouts", + }, + Need { + name: "needs-wasmtime", + condition: config.runner.as_ref().is_some_and(|r| r.contains("wasmtime")), + ignore_reason: "ignored when wasmtime runner is not available", + }, + Need { + name: "needs-symlink", + condition: cache.symlinks, + ignore_reason: "ignored if symlinks are unavailable", + }, + Need { + name: "needs-llvm-zstd", + condition: cache.llvm_zstd, + ignore_reason: "ignored if LLVM wasn't build with zstd for ELF section compression", + }, + Need { + name: "needs-rustc-debug-assertions", + condition: config.with_rustc_debug_assertions, + ignore_reason: "ignored if rustc wasn't built with debug assertions", + }, + Need { + name: "needs-std-debug-assertions", + condition: config.with_std_debug_assertions, + ignore_reason: "ignored if std wasn't built with debug assertions", + }, + Need { + name: "needs-target-std", + condition: build_helper::targets::target_supports_std(&config.target), + ignore_reason: "ignored if target does not support std", + }, + ]; + + let (name, rest) = match ln.split_once([':', ' ']) { + Some((name, rest)) => (name, Some(rest)), + None => (ln, None), + }; + + // FIXME(jieyouxu): tighten up this parsing to reject using both `:` and ` ` as means to + // delineate value. + if name == "needs-target-has-atomic" { + let Some(rest) = rest else { + return IgnoreDecision::Error { + message: "expected `needs-target-has-atomic` to have a comma-separated list of atomic widths".to_string(), + }; + }; + + // Expect directive value to be a list of comma-separated atomic widths. + let specified_widths = rest + .split(',') + .map(|width| width.trim()) + .map(ToString::to_string) + .collect::>(); + + for width in &specified_widths { + if !KNOWN_TARGET_HAS_ATOMIC_WIDTHS.contains(&width.as_str()) { + return IgnoreDecision::Error { + message: format!( + "unknown width specified in `needs-target-has-atomic`: `{width}` is not a \ + known `target_has_atomic_width`, known values are `{:?}`", + KNOWN_TARGET_HAS_ATOMIC_WIDTHS + ), + }; + } + } + + let satisfies_all_specified_widths = specified_widths + .iter() + .all(|specified| config.target_cfg().target_has_atomic.contains(specified)); + if satisfies_all_specified_widths { + return IgnoreDecision::Continue; + } else { + return IgnoreDecision::Ignore { + reason: format!( + "skipping test as target does not support all of the required `target_has_atomic` widths `{:?}`", + specified_widths + ), + }; + } + } + + // FIXME(jieyouxu): share multi-value directive logic with `needs-target-has-atomic` above. + if name == "needs-crate-type" { + let Some(rest) = rest else { + return IgnoreDecision::Error { + message: + "expected `needs-crate-type` to have a comma-separated list of crate types" + .to_string(), + }; + }; + + // Expect directive value to be a list of comma-separated crate-types. + let specified_crate_types = rest + .split(',') + .map(|crate_type| crate_type.trim()) + .map(ToString::to_string) + .collect::>(); + + for crate_type in &specified_crate_types { + if !KNOWN_CRATE_TYPES.contains(&crate_type.as_str()) { + return IgnoreDecision::Error { + message: format!( + "unknown crate type specified in `needs-crate-type`: `{crate_type}` is not \ + a known crate type, known values are `{:?}`", + KNOWN_CRATE_TYPES + ), + }; + } + } + + let satisfies_all_crate_types = specified_crate_types + .iter() + .all(|specified| config.supported_crate_types().contains(specified)); + if satisfies_all_crate_types { + return IgnoreDecision::Continue; + } else { + return IgnoreDecision::Ignore { + reason: format!( + "skipping test as target does not support all of the crate types `{:?}`", + specified_crate_types + ), + }; + } + } + + if !name.starts_with("needs-") { + return IgnoreDecision::Continue; + } + + // Handled elsewhere. + if name == "needs-llvm-components" { + return IgnoreDecision::Continue; + } + + let mut found_valid = false; + for need in needs { + if need.name == name { + if need.condition { + found_valid = true; + break; + } else { + return IgnoreDecision::Ignore { + reason: if let Some(comment) = rest { + format!("{} ({})", need.ignore_reason, comment.trim()) + } else { + need.ignore_reason.into() + }, + }; + } + } + } + + if found_valid { + IgnoreDecision::Continue + } else { + IgnoreDecision::Error { message: format!("invalid needs directive: {name}") } + } +} + +struct Need { + name: &'static str, + condition: bool, + ignore_reason: &'static str, +} + +pub(super) struct CachedNeedsConditions { + sanitizer_support: bool, + sanitizer_address: bool, + sanitizer_cfi: bool, + sanitizer_dataflow: bool, + sanitizer_kcfi: bool, + sanitizer_kasan: bool, + sanitizer_leak: bool, + sanitizer_memory: bool, + sanitizer_thread: bool, + sanitizer_hwaddress: bool, + sanitizer_memtag: bool, + sanitizer_shadow_call_stack: bool, + sanitizer_safestack: bool, + xray: bool, + rust_lld: bool, + dlltool: bool, + symlinks: bool, + /// Whether LLVM built with zstd, for the `needs-llvm-zstd` directive. + llvm_zstd: bool, +} + +impl CachedNeedsConditions { + pub(super) fn load(config: &Config) -> Self { + let target = &&*config.target; + let sanitizers = &config.target_cfg().sanitizers; + Self { + sanitizer_support: std::env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(), + sanitizer_address: sanitizers.contains(&Sanitizer::Address), + sanitizer_cfi: sanitizers.contains(&Sanitizer::Cfi), + sanitizer_dataflow: sanitizers.contains(&Sanitizer::Dataflow), + sanitizer_kcfi: sanitizers.contains(&Sanitizer::Kcfi), + sanitizer_kasan: sanitizers.contains(&Sanitizer::KernelAddress), + sanitizer_leak: sanitizers.contains(&Sanitizer::Leak), + sanitizer_memory: sanitizers.contains(&Sanitizer::Memory), + sanitizer_thread: sanitizers.contains(&Sanitizer::Thread), + sanitizer_hwaddress: sanitizers.contains(&Sanitizer::Hwaddress), + sanitizer_memtag: sanitizers.contains(&Sanitizer::Memtag), + sanitizer_shadow_call_stack: sanitizers.contains(&Sanitizer::ShadowCallStack), + sanitizer_safestack: sanitizers.contains(&Sanitizer::Safestack), + xray: config.target_cfg().xray, + + // For tests using the `needs-rust-lld` directive (e.g. for `-Clink-self-contained=+linker`), + // we need to find whether `rust-lld` is present in the compiler under test. + // + // The --compile-lib-path is the path to host shared libraries, but depends on the OS. For + // example: + // - on linux, it can be /lib + // - on windows, it can be /bin + // + // However, `rust-lld` is only located under the lib path, so we look for it there. + rust_lld: config + .compile_lib_path + .parent() + .expect("couldn't traverse to the parent of the specified --compile-lib-path") + .join("lib") + .join("rustlib") + .join(target) + .join("bin") + .join(if config.host.contains("windows") { "rust-lld.exe" } else { "rust-lld" }) + .exists(), + + llvm_zstd: llvm_has_libzstd(&config), + dlltool: find_dlltool(&config), + symlinks: has_symlinks(), + } + } +} + +fn find_dlltool(config: &Config) -> bool { + let path = std::env::var_os("PATH").expect("missing PATH environment variable"); + let path = std::env::split_paths(&path).collect::>(); + + // dlltool is used ony by GNU based `*-*-windows-gnu` + if !(config.matches_os("windows") && config.matches_env("gnu") && config.matches_abi("")) { + return false; + } + + // On Windows, dlltool.exe is used for all architectures. + // For non-Windows, there are architecture specific dlltool binaries. + let dlltool_found = if cfg!(windows) { + path.iter().any(|dir| dir.join("dlltool.exe").is_file()) + } else if config.matches_arch("i686") { + path.iter().any(|dir| dir.join("i686-w64-mingw32-dlltool").is_file()) + } else if config.matches_arch("x86_64") { + path.iter().any(|dir| dir.join("x86_64-w64-mingw32-dlltool").is_file()) + } else { + false + }; + dlltool_found +} + +// FIXME(#135928): this is actually not quite right because this detection is run on the **host**. +// This however still helps the case of windows -> windows local development in case symlinks are +// not available. +#[cfg(windows)] +fn has_symlinks() -> bool { + if std::env::var_os("CI").is_some() { + return true; + } + let link = std::env::temp_dir().join("RUST_COMPILETEST_SYMLINK_CHECK"); + if std::os::windows::fs::symlink_file("DOES NOT EXIST", &link).is_ok() { + std::fs::remove_file(&link).unwrap(); + true + } else { + false + } +} + +#[cfg(not(windows))] +fn has_symlinks() -> bool { + true +} diff --git a/src/tools/compiletest/src/directives/test-auxillary/error_annotation.rs b/src/tools/compiletest/src/directives/test-auxillary/error_annotation.rs new file mode 100644 index 00000000000..fea66a5e07b --- /dev/null +++ b/src/tools/compiletest/src/directives/test-auxillary/error_annotation.rs @@ -0,0 +1,6 @@ +//@ check-pass + +//~ HELP +fn main() {} //~ERROR +//~^ ERROR +//~| ERROR diff --git a/src/tools/compiletest/src/directives/test-auxillary/known_directive.rs b/src/tools/compiletest/src/directives/test-auxillary/known_directive.rs new file mode 100644 index 00000000000..99834b14c1e --- /dev/null +++ b/src/tools/compiletest/src/directives/test-auxillary/known_directive.rs @@ -0,0 +1,4 @@ +//! ignore-wasm +//@ ignore-wasm +//@ check-pass +// regular comment diff --git a/src/tools/compiletest/src/directives/test-auxillary/not_rs.Makefile b/src/tools/compiletest/src/directives/test-auxillary/not_rs.Makefile new file mode 100644 index 00000000000..4b565e0e6df --- /dev/null +++ b/src/tools/compiletest/src/directives/test-auxillary/not_rs.Makefile @@ -0,0 +1 @@ +# ignore-owo diff --git a/src/tools/compiletest/src/directives/test-auxillary/unknown_directive.rs b/src/tools/compiletest/src/directives/test-auxillary/unknown_directive.rs new file mode 100644 index 00000000000..d4406031043 --- /dev/null +++ b/src/tools/compiletest/src/directives/test-auxillary/unknown_directive.rs @@ -0,0 +1 @@ +//@ needs-headpat diff --git a/src/tools/compiletest/src/directives/tests.rs b/src/tools/compiletest/src/directives/tests.rs new file mode 100644 index 00000000000..8068a4b5282 --- /dev/null +++ b/src/tools/compiletest/src/directives/tests.rs @@ -0,0 +1,958 @@ +use std::io::Read; + +use camino::Utf8Path; +use semver::Version; + +use super::{ + EarlyProps, HeadersCache, extract_llvm_version, extract_version_range, iter_header, + parse_normalize_rule, +}; +use crate::common::{Config, Debugger, Mode}; +use crate::executor::{CollectedTestDesc, ShouldPanic}; + +fn make_test_description( + config: &Config, + name: String, + path: &Utf8Path, + src: R, + revision: Option<&str>, +) -> CollectedTestDesc { + let cache = HeadersCache::load(config); + let mut poisoned = false; + let test = crate::directives::make_test_description( + config, + &cache, + name, + path, + src, + revision, + &mut poisoned, + ); + if poisoned { + panic!("poisoned!"); + } + test +} + +#[test] +fn test_parse_normalize_rule() { + let good_data = &[ + ( + r#""something (32 bits)" -> "something ($WORD bits)""#, + "something (32 bits)", + "something ($WORD bits)", + ), + (r#" " with whitespace" -> " replacement""#, " with whitespace", " replacement"), + ]; + + for &(input, expected_regex, expected_replacement) in good_data { + let parsed = parse_normalize_rule(input); + let parsed = + parsed.as_ref().map(|(regex, replacement)| (regex.as_str(), replacement.as_str())); + assert_eq!(parsed, Some((expected_regex, expected_replacement))); + } + + let bad_data = &[ + r#"something (11 bits) -> something ($WORD bits)"#, + r#"something (12 bits) -> something ($WORD bits)"#, + r#""something (13 bits) -> something ($WORD bits)"#, + r#""something (14 bits)" -> "something ($WORD bits)"#, + r#""something (15 bits)" -> "something ($WORD bits)"."#, + ]; + + for &input in bad_data { + println!("- {input:?}"); + let parsed = parse_normalize_rule(input); + assert_eq!(parsed, None); + } +} + +#[derive(Default)] +struct ConfigBuilder { + mode: Option, + channel: Option, + host: Option, + target: Option, + stage: Option, + stage_id: Option, + llvm_version: Option, + git_hash: bool, + system_llvm: bool, + profiler_runtime: bool, + rustc_debug_assertions: bool, + std_debug_assertions: bool, +} + +impl ConfigBuilder { + fn mode(&mut self, s: &str) -> &mut Self { + self.mode = Some(s.to_owned()); + self + } + + fn channel(&mut self, s: &str) -> &mut Self { + self.channel = Some(s.to_owned()); + self + } + + fn host(&mut self, s: &str) -> &mut Self { + self.host = Some(s.to_owned()); + self + } + + fn target(&mut self, s: &str) -> &mut Self { + self.target = Some(s.to_owned()); + self + } + + fn stage(&mut self, n: u32) -> &mut Self { + self.stage = Some(n); + self + } + + fn stage_id(&mut self, s: &str) -> &mut Self { + self.stage_id = Some(s.to_owned()); + self + } + + fn llvm_version(&mut self, s: &str) -> &mut Self { + self.llvm_version = Some(s.to_owned()); + self + } + + fn git_hash(&mut self, b: bool) -> &mut Self { + self.git_hash = b; + self + } + + fn system_llvm(&mut self, s: bool) -> &mut Self { + self.system_llvm = s; + self + } + + fn profiler_runtime(&mut self, is_available: bool) -> &mut Self { + self.profiler_runtime = is_available; + self + } + + fn rustc_debug_assertions(&mut self, is_enabled: bool) -> &mut Self { + self.rustc_debug_assertions = is_enabled; + self + } + + fn std_debug_assertions(&mut self, is_enabled: bool) -> &mut Self { + self.std_debug_assertions = is_enabled; + self + } + + fn build(&mut self) -> Config { + let args = &[ + "compiletest", + "--mode", + self.mode.as_deref().unwrap_or("ui"), + "--suite=ui", + "--compile-lib-path=", + "--run-lib-path=", + "--python=", + "--jsondocck-path=", + "--src-root=", + "--src-test-suite-root=", + "--build-root=", + "--build-test-suite-root=", + "--sysroot-base=", + "--cc=c", + "--cxx=c++", + "--cflags=", + "--cxxflags=", + "--llvm-components=", + "--android-cross-path=", + "--stage", + &self.stage.unwrap_or(2).to_string(), + "--stage-id", + self.stage_id.as_deref().unwrap_or("stage2-x86_64-unknown-linux-gnu"), + "--channel", + self.channel.as_deref().unwrap_or("nightly"), + "--host", + self.host.as_deref().unwrap_or("x86_64-unknown-linux-gnu"), + "--target", + self.target.as_deref().unwrap_or("x86_64-unknown-linux-gnu"), + "--nightly-branch=", + "--git-merge-commit-email=", + "--minicore-path=", + ]; + let mut args: Vec = args.iter().map(ToString::to_string).collect(); + + if let Some(ref llvm_version) = self.llvm_version { + args.push("--llvm-version".to_owned()); + args.push(llvm_version.clone()); + } + + if self.git_hash { + args.push("--git-hash".to_owned()); + } + if self.system_llvm { + args.push("--system-llvm".to_owned()); + } + if self.profiler_runtime { + args.push("--profiler-runtime".to_owned()); + } + if self.rustc_debug_assertions { + args.push("--with-rustc-debug-assertions".to_owned()); + } + if self.std_debug_assertions { + args.push("--with-std-debug-assertions".to_owned()); + } + + args.push("--rustc-path".to_string()); + // This is a subtle/fragile thing. On rust-lang CI, there is no global + // `rustc`, and Cargo doesn't offer a convenient way to get the path to + // `rustc`. Fortunately bootstrap sets `RUSTC` for us, which is pointing + // to the stage0 compiler. + // + // Otherwise, if you are running compiletests's tests manually, you + // probably don't have `RUSTC` set, in which case this falls back to the + // global rustc. If your global rustc is too far out of sync with stage0, + // then this may cause confusing errors. Or if for some reason you don't + // have rustc in PATH, that would also fail. + args.push(std::env::var("RUSTC").unwrap_or_else(|_| { + eprintln!( + "warning: RUSTC not set, using global rustc (are you not running via bootstrap?)" + ); + "rustc".to_string() + })); + crate::parse_config(args) + } +} + +fn cfg() -> ConfigBuilder { + ConfigBuilder::default() +} + +fn parse_rs(config: &Config, contents: &str) -> EarlyProps { + let bytes = contents.as_bytes(); + EarlyProps::from_reader(config, Utf8Path::new("a.rs"), bytes) +} + +fn check_ignore(config: &Config, contents: &str) -> bool { + let tn = String::new(); + let p = Utf8Path::new("a.rs"); + let d = make_test_description(&config, tn, p, std::io::Cursor::new(contents), None); + d.ignore +} + +#[test] +fn should_fail() { + let config: Config = cfg().build(); + let tn = String::new(); + let p = Utf8Path::new("a.rs"); + + let d = make_test_description(&config, tn.clone(), p, std::io::Cursor::new(""), None); + assert_eq!(d.should_panic, ShouldPanic::No); + let d = make_test_description(&config, tn, p, std::io::Cursor::new("//@ should-fail"), None); + assert_eq!(d.should_panic, ShouldPanic::Yes); +} + +#[test] +fn revisions() { + let config: Config = cfg().build(); + + assert_eq!(parse_rs(&config, "//@ revisions: a b c").revisions, vec!["a", "b", "c"],); +} + +#[test] +fn aux_build() { + let config: Config = cfg().build(); + + assert_eq!( + parse_rs( + &config, + r" + //@ aux-build: a.rs + //@ aux-build: b.rs + " + ) + .aux + .builds, + vec!["a.rs", "b.rs"], + ); +} + +#[test] +fn llvm_version() { + let config: Config = cfg().llvm_version("8.1.2").build(); + assert!(check_ignore(&config, "//@ min-llvm-version: 9.0")); + + let config: Config = cfg().llvm_version("9.0.1").build(); + assert!(check_ignore(&config, "//@ min-llvm-version: 9.2")); + + let config: Config = cfg().llvm_version("9.3.1").build(); + assert!(!check_ignore(&config, "//@ min-llvm-version: 9.2")); + + let config: Config = cfg().llvm_version("10.0.0").build(); + assert!(!check_ignore(&config, "//@ min-llvm-version: 9.0")); + + let config: Config = cfg().llvm_version("10.0.0").build(); + assert!(check_ignore(&config, "//@ exact-llvm-major-version: 9.0")); + + let config: Config = cfg().llvm_version("9.0.0").build(); + assert!(check_ignore(&config, "//@ exact-llvm-major-version: 10.0")); + + let config: Config = cfg().llvm_version("10.0.0").build(); + assert!(!check_ignore(&config, "//@ exact-llvm-major-version: 10.0")); + + let config: Config = cfg().llvm_version("10.0.0").build(); + assert!(!check_ignore(&config, "//@ exact-llvm-major-version: 10")); + + let config: Config = cfg().llvm_version("10.6.2").build(); + assert!(!check_ignore(&config, "//@ exact-llvm-major-version: 10")); + + let config: Config = cfg().llvm_version("19.0.0").build(); + assert!(!check_ignore(&config, "//@ max-llvm-major-version: 19")); + + let config: Config = cfg().llvm_version("19.1.2").build(); + assert!(!check_ignore(&config, "//@ max-llvm-major-version: 19")); + + let config: Config = cfg().llvm_version("20.0.0").build(); + assert!(check_ignore(&config, "//@ max-llvm-major-version: 19")); +} + +#[test] +fn system_llvm_version() { + let config: Config = cfg().system_llvm(true).llvm_version("17.0.0").build(); + assert!(check_ignore(&config, "//@ min-system-llvm-version: 18.0")); + + let config: Config = cfg().system_llvm(true).llvm_version("18.0.0").build(); + assert!(!check_ignore(&config, "//@ min-system-llvm-version: 18.0")); + + let config: Config = cfg().llvm_version("17.0.0").build(); + assert!(!check_ignore(&config, "//@ min-system-llvm-version: 18.0")); +} + +#[test] +fn ignore_target() { + let config: Config = cfg().target("x86_64-unknown-linux-gnu").build(); + + assert!(check_ignore(&config, "//@ ignore-x86_64-unknown-linux-gnu")); + assert!(check_ignore(&config, "//@ ignore-x86_64")); + assert!(check_ignore(&config, "//@ ignore-linux")); + assert!(check_ignore(&config, "//@ ignore-unix")); + assert!(check_ignore(&config, "//@ ignore-gnu")); + assert!(check_ignore(&config, "//@ ignore-64bit")); + + assert!(!check_ignore(&config, "//@ ignore-x86")); + assert!(!check_ignore(&config, "//@ ignore-windows")); + assert!(!check_ignore(&config, "//@ ignore-msvc")); + assert!(!check_ignore(&config, "//@ ignore-32bit")); +} + +#[test] +fn only_target() { + let config: Config = cfg().target("x86_64-pc-windows-gnu").build(); + + assert!(check_ignore(&config, "//@ only-x86")); + assert!(check_ignore(&config, "//@ only-linux")); + assert!(check_ignore(&config, "//@ only-unix")); + assert!(check_ignore(&config, "//@ only-msvc")); + assert!(check_ignore(&config, "//@ only-32bit")); + + assert!(!check_ignore(&config, "//@ only-x86_64-pc-windows-gnu")); + assert!(!check_ignore(&config, "//@ only-x86_64")); + assert!(!check_ignore(&config, "//@ only-windows")); + assert!(!check_ignore(&config, "//@ only-gnu")); + assert!(!check_ignore(&config, "//@ only-64bit")); +} + +#[test] +fn rustc_debug_assertions() { + let config: Config = cfg().rustc_debug_assertions(false).build(); + + assert!(check_ignore(&config, "//@ needs-rustc-debug-assertions")); + assert!(!check_ignore(&config, "//@ ignore-rustc-debug-assertions")); + + let config: Config = cfg().rustc_debug_assertions(true).build(); + + assert!(!check_ignore(&config, "//@ needs-rustc-debug-assertions")); + assert!(check_ignore(&config, "//@ ignore-rustc-debug-assertions")); +} + +#[test] +fn std_debug_assertions() { + let config: Config = cfg().std_debug_assertions(false).build(); + + assert!(check_ignore(&config, "//@ needs-std-debug-assertions")); + assert!(!check_ignore(&config, "//@ ignore-std-debug-assertions")); + + let config: Config = cfg().std_debug_assertions(true).build(); + + assert!(!check_ignore(&config, "//@ needs-std-debug-assertions")); + assert!(check_ignore(&config, "//@ ignore-std-debug-assertions")); +} + +#[test] +fn stage() { + let config: Config = cfg().stage(1).stage_id("stage1-x86_64-unknown-linux-gnu").build(); + + assert!(check_ignore(&config, "//@ ignore-stage1")); + assert!(!check_ignore(&config, "//@ ignore-stage2")); +} + +#[test] +fn cross_compile() { + let config: Config = cfg().host("x86_64-apple-darwin").target("wasm32-unknown-unknown").build(); + assert!(check_ignore(&config, "//@ ignore-cross-compile")); + + let config: Config = cfg().host("x86_64-apple-darwin").target("x86_64-apple-darwin").build(); + assert!(!check_ignore(&config, "//@ ignore-cross-compile")); +} + +#[test] +fn debugger() { + let mut config = cfg().build(); + config.debugger = None; + assert!(!check_ignore(&config, "//@ ignore-cdb")); + + config.debugger = Some(Debugger::Cdb); + assert!(check_ignore(&config, "//@ ignore-cdb")); + + config.debugger = Some(Debugger::Gdb); + assert!(check_ignore(&config, "//@ ignore-gdb")); + + config.debugger = Some(Debugger::Lldb); + assert!(check_ignore(&config, "//@ ignore-lldb")); +} + +#[test] +fn git_hash() { + let config: Config = cfg().git_hash(false).build(); + assert!(check_ignore(&config, "//@ needs-git-hash")); + + let config: Config = cfg().git_hash(true).build(); + assert!(!check_ignore(&config, "//@ needs-git-hash")); +} + +#[test] +fn sanitizers() { + // Target that supports all sanitizers: + let config: Config = cfg().target("x86_64-unknown-linux-gnu").build(); + assert!(!check_ignore(&config, "//@ needs-sanitizer-address")); + assert!(!check_ignore(&config, "//@ needs-sanitizer-leak")); + assert!(!check_ignore(&config, "//@ needs-sanitizer-memory")); + assert!(!check_ignore(&config, "//@ needs-sanitizer-thread")); + + // Target that doesn't support sanitizers: + let config: Config = cfg().target("wasm32-unknown-emscripten").build(); + assert!(check_ignore(&config, "//@ needs-sanitizer-address")); + assert!(check_ignore(&config, "//@ needs-sanitizer-leak")); + assert!(check_ignore(&config, "//@ needs-sanitizer-memory")); + assert!(check_ignore(&config, "//@ needs-sanitizer-thread")); +} + +#[test] +fn profiler_runtime() { + let config: Config = cfg().profiler_runtime(false).build(); + assert!(check_ignore(&config, "//@ needs-profiler-runtime")); + + let config: Config = cfg().profiler_runtime(true).build(); + assert!(!check_ignore(&config, "//@ needs-profiler-runtime")); +} + +#[test] +fn asm_support() { + let asms = [ + ("avr-none", false), + ("i686-unknown-netbsd", true), + ("riscv32gc-unknown-linux-gnu", true), + ("riscv64imac-unknown-none-elf", true), + ("x86_64-unknown-linux-gnu", true), + ("i686-unknown-netbsd", true), + ]; + for (target, has_asm) in asms { + let config = cfg().target(target).build(); + assert_eq!(config.has_asm_support(), has_asm); + assert_eq!(check_ignore(&config, "//@ needs-asm-support"), !has_asm) + } +} + +#[test] +fn channel() { + let config: Config = cfg().channel("beta").build(); + + assert!(check_ignore(&config, "//@ ignore-beta")); + assert!(check_ignore(&config, "//@ only-nightly")); + assert!(check_ignore(&config, "//@ only-stable")); + + assert!(!check_ignore(&config, "//@ only-beta")); + assert!(!check_ignore(&config, "//@ ignore-nightly")); + assert!(!check_ignore(&config, "//@ ignore-stable")); +} + +#[test] +fn test_extract_llvm_version() { + // Note: officially, semver *requires* that versions at the minimum have all three + // `major.minor.patch` numbers, though for test-writer's convenience we allow omitting the minor + // and patch numbers (which will be stubbed out as 0). + assert_eq!(extract_llvm_version("0"), Version::new(0, 0, 0)); + assert_eq!(extract_llvm_version("0.0"), Version::new(0, 0, 0)); + assert_eq!(extract_llvm_version("0.0.0"), Version::new(0, 0, 0)); + assert_eq!(extract_llvm_version("1"), Version::new(1, 0, 0)); + assert_eq!(extract_llvm_version("1.2"), Version::new(1, 2, 0)); + assert_eq!(extract_llvm_version("1.2.3"), Version::new(1, 2, 3)); + assert_eq!(extract_llvm_version("4.5.6git"), Version::new(4, 5, 6)); + assert_eq!(extract_llvm_version("4.5.6-rc1"), Version::new(4, 5, 6)); + assert_eq!(extract_llvm_version("123.456.789-rc1"), Version::new(123, 456, 789)); + assert_eq!(extract_llvm_version("8.1.2-rust"), Version::new(8, 1, 2)); + assert_eq!(extract_llvm_version("9.0.1-rust-1.43.0-dev"), Version::new(9, 0, 1)); + assert_eq!(extract_llvm_version("9.3.1-rust-1.43.0-dev"), Version::new(9, 3, 1)); + assert_eq!(extract_llvm_version("10.0.0-rust"), Version::new(10, 0, 0)); + assert_eq!(extract_llvm_version("11.1.0"), Version::new(11, 1, 0)); + assert_eq!(extract_llvm_version("12.0.0libcxx"), Version::new(12, 0, 0)); + assert_eq!(extract_llvm_version("12.0.0-rc3"), Version::new(12, 0, 0)); + assert_eq!(extract_llvm_version("13.0.0git"), Version::new(13, 0, 0)); +} + +#[test] +#[should_panic] +fn test_llvm_version_invalid_components() { + extract_llvm_version("4.x.6"); +} + +#[test] +#[should_panic] +fn test_llvm_version_invalid_prefix() { + extract_llvm_version("meow4.5.6"); +} + +#[test] +#[should_panic] +fn test_llvm_version_too_many_components() { + extract_llvm_version("4.5.6.7"); +} + +#[test] +fn test_extract_version_range() { + let wrapped_extract = |s: &str| Some(extract_llvm_version(s)); + + assert_eq!( + extract_version_range("1.2.3 - 4.5.6", wrapped_extract), + Some((Version::new(1, 2, 3), Version::new(4, 5, 6))) + ); + assert_eq!( + extract_version_range("0 - 4.5.6", wrapped_extract), + Some((Version::new(0, 0, 0), Version::new(4, 5, 6))) + ); + assert_eq!(extract_version_range("1.2.3 -", wrapped_extract), None); + assert_eq!(extract_version_range("1.2.3 - ", wrapped_extract), None); + assert_eq!(extract_version_range("- 4.5.6", wrapped_extract), None); + assert_eq!(extract_version_range("-", wrapped_extract), None); + assert_eq!(extract_version_range(" - 4.5.6", wrapped_extract), None); + assert_eq!(extract_version_range(" - 4.5.6", wrapped_extract), None); + assert_eq!(extract_version_range("0 -", wrapped_extract), None); +} + +#[test] +#[should_panic(expected = "duplicate revision: `rpass1` in line ` rpass1 rpass1`")] +fn test_duplicate_revisions() { + let config: Config = cfg().build(); + parse_rs(&config, "//@ revisions: rpass1 rpass1"); +} + +#[test] +#[should_panic( + expected = "revision name `CHECK` is not permitted in a test suite that uses `FileCheck` annotations" +)] +fn test_assembly_mode_forbidden_revisions() { + let config = cfg().mode("assembly").build(); + parse_rs(&config, "//@ revisions: CHECK"); +} + +#[test] +#[should_panic(expected = "revision name `true` is not permitted")] +fn test_forbidden_revisions() { + let config = cfg().mode("ui").build(); + parse_rs(&config, "//@ revisions: true"); +} + +#[test] +#[should_panic( + expected = "revision name `CHECK` is not permitted in a test suite that uses `FileCheck` annotations" +)] +fn test_codegen_mode_forbidden_revisions() { + let config = cfg().mode("codegen").build(); + parse_rs(&config, "//@ revisions: CHECK"); +} + +#[test] +#[should_panic( + expected = "revision name `CHECK` is not permitted in a test suite that uses `FileCheck` annotations" +)] +fn test_miropt_mode_forbidden_revisions() { + let config = cfg().mode("mir-opt").build(); + parse_rs(&config, "//@ revisions: CHECK"); +} + +#[test] +fn test_forbidden_revisions_allowed_in_non_filecheck_dir() { + let revisions = ["CHECK", "COM", "NEXT", "SAME", "EMPTY", "NOT", "COUNT", "DAG", "LABEL"]; + let modes = [ + "pretty", + "debuginfo", + "rustdoc", + "rustdoc-json", + "codegen-units", + "incremental", + "ui", + "rustdoc-js", + "coverage-map", + "coverage-run", + "crashes", + ]; + + for rev in revisions { + let content = format!("//@ revisions: {rev}"); + for mode in modes { + let config = cfg().mode(mode).build(); + parse_rs(&config, &content); + } + } +} + +#[test] +fn ignore_arch() { + let archs = [ + ("x86_64-unknown-linux-gnu", "x86_64"), + ("i686-unknown-linux-gnu", "x86"), + ("nvptx64-nvidia-cuda", "nvptx64"), + ("thumbv7m-none-eabi", "thumb"), + ]; + for (target, arch) in archs { + let config: Config = cfg().target(target).build(); + assert!(config.matches_arch(arch), "{target} {arch}"); + assert!(check_ignore(&config, &format!("//@ ignore-{arch}"))); + } +} + +#[test] +fn matches_os() { + let oss = [ + ("x86_64-unknown-linux-gnu", "linux"), + ("x86_64-fortanix-unknown-sgx", "unknown"), + ("wasm32-unknown-unknown", "unknown"), + ("x86_64-unknown-none", "none"), + ]; + for (target, os) in oss { + let config = cfg().target(target).build(); + assert!(config.matches_os(os), "{target} {os}"); + assert!(check_ignore(&config, &format!("//@ ignore-{os}"))); + } +} + +#[test] +fn matches_env() { + let envs = [ + ("x86_64-unknown-linux-gnu", "gnu"), + ("x86_64-fortanix-unknown-sgx", "sgx"), + ("arm-unknown-linux-musleabi", "musl"), + ]; + for (target, env) in envs { + let config: Config = cfg().target(target).build(); + assert!(config.matches_env(env), "{target} {env}"); + assert!(check_ignore(&config, &format!("//@ ignore-{env}"))); + } +} + +#[test] +fn matches_abi() { + let abis = [ + ("aarch64-apple-ios-macabi", "macabi"), + ("x86_64-unknown-linux-gnux32", "x32"), + ("arm-unknown-linux-gnueabi", "eabi"), + ]; + for (target, abi) in abis { + let config: Config = cfg().target(target).build(); + assert!(config.matches_abi(abi), "{target} {abi}"); + assert!(check_ignore(&config, &format!("//@ ignore-{abi}"))); + } +} + +#[test] +fn is_big_endian() { + let endians = [ + ("x86_64-unknown-linux-gnu", false), + ("bpfeb-unknown-none", true), + ("m68k-unknown-linux-gnu", true), + ("aarch64_be-unknown-linux-gnu", true), + ("powerpc64-unknown-linux-gnu", true), + ]; + for (target, is_big) in endians { + let config = cfg().target(target).build(); + assert_eq!(config.is_big_endian(), is_big, "{target} {is_big}"); + assert_eq!(check_ignore(&config, "//@ ignore-endian-big"), is_big); + } +} + +#[test] +fn pointer_width() { + let widths = [ + ("x86_64-unknown-linux-gnu", 64), + ("i686-unknown-linux-gnu", 32), + ("arm64_32-apple-watchos", 32), + ("msp430-none-elf", 16), + ]; + for (target, width) in widths { + let config: Config = cfg().target(target).build(); + assert_eq!(config.get_pointer_width(), width, "{target} {width}"); + assert_eq!(check_ignore(&config, "//@ ignore-16bit"), width == 16); + assert_eq!(check_ignore(&config, "//@ ignore-32bit"), width == 32); + assert_eq!(check_ignore(&config, "//@ ignore-64bit"), width == 64); + } +} + +#[test] +fn wasm_special() { + let ignores = [ + ("wasm32-unknown-unknown", "emscripten", true), + ("wasm32-unknown-unknown", "wasm32", true), + ("wasm32-unknown-unknown", "wasm32-bare", true), + ("wasm32-unknown-unknown", "wasm64", false), + ("wasm32-unknown-emscripten", "emscripten", true), + ("wasm32-unknown-emscripten", "wasm32", true), + ("wasm32-unknown-emscripten", "wasm32-bare", false), + ("wasm32-wasip1", "emscripten", false), + ("wasm32-wasip1", "wasm32", true), + ("wasm32-wasip1", "wasm32-bare", false), + ("wasm32-wasip1", "wasi", true), + ("wasm64-unknown-unknown", "emscripten", false), + ("wasm64-unknown-unknown", "wasm32", false), + ("wasm64-unknown-unknown", "wasm32-bare", false), + ("wasm64-unknown-unknown", "wasm64", true), + ]; + for (target, pattern, ignore) in ignores { + let config: Config = cfg().target(target).build(); + assert_eq!( + check_ignore(&config, &format!("//@ ignore-{pattern}")), + ignore, + "{target} {pattern}" + ); + } +} + +#[test] +fn families() { + let families = [ + ("x86_64-unknown-linux-gnu", "unix"), + ("x86_64-pc-windows-gnu", "windows"), + ("wasm32-unknown-unknown", "wasm"), + ("wasm32-unknown-emscripten", "wasm"), + ("wasm32-unknown-emscripten", "unix"), + ]; + for (target, family) in families { + let config: Config = cfg().target(target).build(); + assert!(config.matches_family(family)); + let other = if family == "windows" { "unix" } else { "windows" }; + assert!(!config.matches_family(other)); + assert!(check_ignore(&config, &format!("//@ ignore-{family}"))); + assert!(!check_ignore(&config, &format!("//@ ignore-{other}"))); + } +} + +#[test] +fn ignore_coverage() { + // Indicate profiler runtime availability so that "coverage-run" tests aren't skipped. + let config = cfg().mode("coverage-map").profiler_runtime(true).build(); + assert!(check_ignore(&config, "//@ ignore-coverage-map")); + assert!(!check_ignore(&config, "//@ ignore-coverage-run")); + + let config = cfg().mode("coverage-run").profiler_runtime(true).build(); + assert!(!check_ignore(&config, "//@ ignore-coverage-map")); + assert!(check_ignore(&config, "//@ ignore-coverage-run")); +} + +#[test] +fn threads_support() { + let threads = [ + ("x86_64-unknown-linux-gnu", true), + ("aarch64-apple-darwin", true), + ("wasm32-unknown-unknown", false), + ("wasm64-unknown-unknown", false), + ("wasm32-wasip1", false), + ("wasm32-wasip1-threads", true), + ]; + for (target, has_threads) in threads { + let config = cfg().target(target).build(); + assert_eq!(config.has_threads(), has_threads); + assert_eq!(check_ignore(&config, "//@ needs-threads"), !has_threads) + } +} + +fn run_path(poisoned: &mut bool, path: &Utf8Path, buf: &[u8]) { + let rdr = std::io::Cursor::new(&buf); + iter_header(Mode::Ui, "ui", poisoned, path, rdr, &mut |_| {}); +} + +#[test] +fn test_unknown_directive_check() { + let mut poisoned = false; + run_path( + &mut poisoned, + Utf8Path::new("a.rs"), + include_bytes!("./test-auxillary/unknown_directive.rs"), + ); + assert!(poisoned); +} + +#[test] +fn test_known_directive_check_no_error() { + let mut poisoned = false; + run_path( + &mut poisoned, + Utf8Path::new("a.rs"), + include_bytes!("./test-auxillary/known_directive.rs"), + ); + assert!(!poisoned); +} + +#[test] +fn test_error_annotation_no_error() { + let mut poisoned = false; + run_path( + &mut poisoned, + Utf8Path::new("a.rs"), + include_bytes!("./test-auxillary/error_annotation.rs"), + ); + assert!(!poisoned); +} + +#[test] +fn test_non_rs_unknown_directive_not_checked() { + let mut poisoned = false; + run_path( + &mut poisoned, + Utf8Path::new("a.Makefile"), + include_bytes!("./test-auxillary/not_rs.Makefile"), + ); + assert!(!poisoned); +} + +#[test] +fn test_trailing_directive() { + let mut poisoned = false; + run_path(&mut poisoned, Utf8Path::new("a.rs"), b"//@ only-x86 only-arm"); + assert!(poisoned); +} + +#[test] +fn test_trailing_directive_with_comment() { + let mut poisoned = false; + run_path(&mut poisoned, Utf8Path::new("a.rs"), b"//@ only-x86 only-arm with comment"); + assert!(poisoned); +} + +#[test] +fn test_not_trailing_directive() { + let mut poisoned = false; + run_path(&mut poisoned, Utf8Path::new("a.rs"), b"//@ revisions: incremental"); + assert!(!poisoned); +} + +#[test] +fn test_needs_target_has_atomic() { + use std::collections::BTreeSet; + + // `x86_64-unknown-linux-gnu` supports `["8", "16", "32", "64", "ptr"]` but not `128`. + + let config = cfg().target("x86_64-unknown-linux-gnu").build(); + // Expectation sanity check. + assert_eq!( + config.target_cfg().target_has_atomic, + BTreeSet::from([ + "8".to_string(), + "16".to_string(), + "32".to_string(), + "64".to_string(), + "ptr".to_string() + ]), + "expected `x86_64-unknown-linux-gnu` to not have 128-bit atomic support" + ); + + assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 8")); + assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 16")); + assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 32")); + assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 64")); + assert!(!check_ignore(&config, "//@ needs-target-has-atomic: ptr")); + + assert!(check_ignore(&config, "//@ needs-target-has-atomic: 128")); + + assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 8,16,32,64,ptr")); + + assert!(check_ignore(&config, "//@ needs-target-has-atomic: 8,16,32,64,ptr,128")); + + // Check whitespace between widths is permitted. + assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 8, ptr")); + assert!(check_ignore(&config, "//@ needs-target-has-atomic: 8, ptr, 128")); +} + +#[test] +fn test_rustc_abi() { + let config = cfg().target("i686-unknown-linux-gnu").build(); + assert_eq!(config.target_cfg().rustc_abi, Some("x86-sse2".to_string())); + assert!(check_ignore(&config, "//@ ignore-rustc_abi-x86-sse2")); + assert!(!check_ignore(&config, "//@ only-rustc_abi-x86-sse2")); + let config = cfg().target("x86_64-unknown-linux-gnu").build(); + assert_eq!(config.target_cfg().rustc_abi, None); + assert!(!check_ignore(&config, "//@ ignore-rustc_abi-x86-sse2")); + assert!(check_ignore(&config, "//@ only-rustc_abi-x86-sse2")); +} + +#[test] +fn test_supported_crate_types() { + // Basic assumptions check on under-test compiler's `--print=supported-crate-types` output based + // on knowledge about the cherry-picked `x86_64-unknown-linux-gnu` and `wasm32-unknown-unknown` + // targets. Also smoke tests the `needs-crate-type` directive itself. + + use std::collections::HashSet; + + let config = cfg().target("x86_64-unknown-linux-gnu").build(); + assert_eq!( + config.supported_crate_types().iter().map(String::as_str).collect::>(), + HashSet::from(["bin", "cdylib", "dylib", "lib", "proc-macro", "rlib", "staticlib"]), + ); + assert!(!check_ignore(&config, "//@ needs-crate-type: rlib")); + assert!(!check_ignore(&config, "//@ needs-crate-type: dylib")); + assert!(!check_ignore( + &config, + "//@ needs-crate-type: bin, cdylib, dylib, lib, proc-macro, rlib, staticlib" + )); + + let config = cfg().target("wasm32-unknown-unknown").build(); + assert_eq!( + config.supported_crate_types().iter().map(String::as_str).collect::>(), + HashSet::from(["bin", "cdylib", "lib", "rlib", "staticlib"]), + ); + + // rlib is supported + assert!(!check_ignore(&config, "//@ needs-crate-type: rlib")); + // dylib is not + assert!(check_ignore(&config, "//@ needs-crate-type: dylib")); + // If multiple crate types are specified, then all specified crate types need to be supported. + assert!(check_ignore(&config, "//@ needs-crate-type: cdylib, dylib")); + assert!(check_ignore( + &config, + "//@ needs-crate-type: bin, cdylib, dylib, lib, proc-macro, rlib, staticlib" + )); +} + +#[test] +fn test_ignore_auxiliary() { + let config = cfg().build(); + assert!(check_ignore(&config, "//@ ignore-auxiliary")); +} + +#[test] +fn test_needs_target_std() { + // Cherry-picks two targets: + // 1. `x86_64-unknown-none`: Tier 2, intentionally never supports std. + // 2. `x86_64-unknown-linux-gnu`: Tier 1, always supports std. + let config = cfg().target("x86_64-unknown-none").build(); + assert!(check_ignore(&config, "//@ needs-target-std")); + let config = cfg().target("x86_64-unknown-linux-gnu").build(); + assert!(!check_ignore(&config, "//@ needs-target-std")); +} diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs deleted file mode 100644 index 5636a146b0f..00000000000 --- a/src/tools/compiletest/src/header.rs +++ /dev/null @@ -1,1656 +0,0 @@ -use std::collections::HashSet; -use std::env; -use std::fs::File; -use std::io::BufReader; -use std::io::prelude::*; -use std::process::Command; - -use camino::{Utf8Path, Utf8PathBuf}; -use semver::Version; -use tracing::*; - -use crate::common::{Config, Debugger, FailMode, Mode, PassMode}; -use crate::debuggers::{extract_cdb_version, extract_gdb_version}; -use crate::errors::ErrorKind; -use crate::executor::{CollectedTestDesc, ShouldPanic}; -use crate::header::auxiliary::{AuxProps, parse_and_update_aux}; -use crate::header::needs::CachedNeedsConditions; -use crate::help; -use crate::util::static_regex; - -pub(crate) mod auxiliary; -mod cfg; -mod needs; -#[cfg(test)] -mod tests; - -pub struct HeadersCache { - needs: CachedNeedsConditions, -} - -impl HeadersCache { - pub fn load(config: &Config) -> Self { - Self { needs: CachedNeedsConditions::load(config) } - } -} - -/// Properties which must be known very early, before actually running -/// the test. -#[derive(Default)] -pub struct EarlyProps { - /// Auxiliary crates that should be built and made available to this test. - /// Included in [`EarlyProps`] so that the indicated files can participate - /// in up-to-date checking. Building happens via [`TestProps::aux`] instead. - pub(crate) aux: AuxProps, - pub revisions: Vec, -} - -impl EarlyProps { - pub fn from_file(config: &Config, testfile: &Utf8Path) -> Self { - let file = File::open(testfile.as_std_path()).expect("open test file to parse earlyprops"); - Self::from_reader(config, testfile, file) - } - - pub fn from_reader(config: &Config, testfile: &Utf8Path, rdr: R) -> Self { - let mut props = EarlyProps::default(); - let mut poisoned = false; - iter_header( - config.mode, - &config.suite, - &mut poisoned, - testfile, - rdr, - &mut |DirectiveLine { raw_directive: ln, .. }| { - parse_and_update_aux(config, ln, &mut props.aux); - config.parse_and_update_revisions(testfile, ln, &mut props.revisions); - }, - ); - - if poisoned { - eprintln!("errors encountered during EarlyProps parsing: {}", testfile); - panic!("errors encountered during EarlyProps parsing"); - } - - props - } -} - -#[derive(Clone, Debug)] -pub struct TestProps { - // Lines that should be expected, in order, on standard out - pub error_patterns: Vec, - // Regexes that should be expected, in order, on standard out - pub regex_error_patterns: Vec, - // Extra flags to pass to the compiler - pub compile_flags: Vec, - // Extra flags to pass when the compiled code is run (such as --bench) - pub run_flags: Vec, - /// Extra flags to pass to rustdoc but not the compiler. - pub doc_flags: Vec, - // If present, the name of a file that this test should match when - // pretty-printed - pub pp_exact: Option, - /// Auxiliary crates that should be built and made available to this test. - pub(crate) aux: AuxProps, - // Environment settings to use for compiling - pub rustc_env: Vec<(String, String)>, - // Environment variables to unset prior to compiling. - // Variables are unset before applying 'rustc_env'. - pub unset_rustc_env: Vec, - // Environment settings to use during execution - pub exec_env: Vec<(String, String)>, - // Environment variables to unset prior to execution. - // Variables are unset before applying 'exec_env' - pub unset_exec_env: Vec, - // Build documentation for all specified aux-builds as well - pub build_aux_docs: bool, - /// Build the documentation for each crate in a unique output directory. - /// Uses `/docs//doc`. - pub unique_doc_out_dir: bool, - // Flag to force a crate to be built with the host architecture - pub force_host: bool, - // Check stdout for error-pattern output as well as stderr - pub check_stdout: bool, - // Check stdout & stderr for output of run-pass test - pub check_run_results: bool, - // For UI tests, allows compiler to generate arbitrary output to stdout - pub dont_check_compiler_stdout: bool, - // For UI tests, allows compiler to generate arbitrary output to stderr - pub dont_check_compiler_stderr: bool, - // Don't force a --crate-type=dylib flag on the command line - // - // Set this for example if you have an auxiliary test file that contains - // a proc-macro and needs `#![crate_type = "proc-macro"]`. This ensures - // that the aux file is compiled as a `proc-macro` and not as a `dylib`. - pub no_prefer_dynamic: bool, - // Which pretty mode are we testing with, default to 'normal' - pub pretty_mode: String, - // Only compare pretty output and don't try compiling - pub pretty_compare_only: bool, - // Patterns which must not appear in the output of a cfail test. - pub forbid_output: Vec, - // Revisions to test for incremental compilation. - pub revisions: Vec, - // Directory (if any) to use for incremental compilation. This is - // not set by end-users; rather it is set by the incremental - // testing harness and used when generating compilation - // arguments. (In particular, it propagates to the aux-builds.) - pub incremental_dir: Option, - // If `true`, this test will use incremental compilation. - // - // This can be set manually with the `incremental` header, or implicitly - // by being a part of an incremental mode test. Using the `incremental` - // header should be avoided if possible; using an incremental mode test is - // preferred. Incremental mode tests support multiple passes, which can - // verify that the incremental cache can be loaded properly after being - // created. Just setting the header will only verify the behavior with - // creating an incremental cache, but doesn't check that it is created - // correctly. - // - // Compiletest will create the incremental directory, and ensure it is - // empty before the test starts. Incremental mode tests will reuse the - // incremental directory between passes in the same test. - pub incremental: bool, - // If `true`, this test is a known bug. - // - // When set, some requirements are relaxed. Currently, this only means no - // error annotations are needed, but this may be updated in the future to - // include other relaxations. - pub known_bug: bool, - // How far should the test proceed while still passing. - pass_mode: Option, - // Ignore `--pass` overrides from the command line for this test. - ignore_pass: bool, - // How far this test should proceed to start failing. - pub fail_mode: Option, - // rustdoc will test the output of the `--test` option - pub check_test_line_numbers_match: bool, - // customized normalization rules - pub normalize_stdout: Vec<(String, String)>, - pub normalize_stderr: Vec<(String, String)>, - pub failure_status: Option, - // For UI tests, allows compiler to exit with arbitrary failure status - pub dont_check_failure_status: bool, - // Whether or not `rustfix` should apply the `CodeSuggestion`s of this test and compile the - // resulting Rust code. - pub run_rustfix: bool, - // If true, `rustfix` will only apply `MachineApplicable` suggestions. - pub rustfix_only_machine_applicable: bool, - pub assembly_output: Option, - // If true, the test is expected to ICE - pub should_ice: bool, - // If true, the stderr is expected to be different across bit-widths. - pub stderr_per_bitwidth: bool, - // The MIR opt to unit test, if any - pub mir_unit_test: Option, - // Whether to tell `rustc` to remap the "src base" directory to a fake - // directory. - pub remap_src_base: bool, - /// Extra flags to pass to `llvm-cov` when producing coverage reports. - /// Only used by the "coverage-run" test mode. - pub llvm_cov_flags: Vec, - /// Extra flags to pass to LLVM's `filecheck` tool, in tests that use it. - pub filecheck_flags: Vec, - /// Don't automatically insert any `--check-cfg` args - pub no_auto_check_cfg: bool, - /// Run tests which require enzyme being build - pub has_enzyme: bool, - /// Build and use `minicore` as `core` stub for `no_core` tests in cross-compilation scenarios - /// that don't otherwise want/need `-Z build-std`. - pub add_core_stubs: bool, - /// Whether line annotatins are required for the given error kind. - pub dont_require_annotations: HashSet, -} - -mod directives { - pub const ERROR_PATTERN: &'static str = "error-pattern"; - pub const REGEX_ERROR_PATTERN: &'static str = "regex-error-pattern"; - pub const COMPILE_FLAGS: &'static str = "compile-flags"; - pub const RUN_FLAGS: &'static str = "run-flags"; - pub const DOC_FLAGS: &'static str = "doc-flags"; - pub const SHOULD_ICE: &'static str = "should-ice"; - pub const BUILD_AUX_DOCS: &'static str = "build-aux-docs"; - pub const UNIQUE_DOC_OUT_DIR: &'static str = "unique-doc-out-dir"; - pub const FORCE_HOST: &'static str = "force-host"; - pub const CHECK_STDOUT: &'static str = "check-stdout"; - pub const CHECK_RUN_RESULTS: &'static str = "check-run-results"; - pub const DONT_CHECK_COMPILER_STDOUT: &'static str = "dont-check-compiler-stdout"; - pub const DONT_CHECK_COMPILER_STDERR: &'static str = "dont-check-compiler-stderr"; - pub const DONT_REQUIRE_ANNOTATIONS: &'static str = "dont-require-annotations"; - pub const NO_PREFER_DYNAMIC: &'static str = "no-prefer-dynamic"; - pub const PRETTY_MODE: &'static str = "pretty-mode"; - pub const PRETTY_COMPARE_ONLY: &'static str = "pretty-compare-only"; - pub const AUX_BIN: &'static str = "aux-bin"; - pub const AUX_BUILD: &'static str = "aux-build"; - pub const AUX_CRATE: &'static str = "aux-crate"; - pub const PROC_MACRO: &'static str = "proc-macro"; - pub const AUX_CODEGEN_BACKEND: &'static str = "aux-codegen-backend"; - pub const EXEC_ENV: &'static str = "exec-env"; - pub const RUSTC_ENV: &'static str = "rustc-env"; - pub const UNSET_EXEC_ENV: &'static str = "unset-exec-env"; - pub const UNSET_RUSTC_ENV: &'static str = "unset-rustc-env"; - pub const FORBID_OUTPUT: &'static str = "forbid-output"; - pub const CHECK_TEST_LINE_NUMBERS_MATCH: &'static str = "check-test-line-numbers-match"; - pub const IGNORE_PASS: &'static str = "ignore-pass"; - pub const FAILURE_STATUS: &'static str = "failure-status"; - pub const DONT_CHECK_FAILURE_STATUS: &'static str = "dont-check-failure-status"; - pub const RUN_RUSTFIX: &'static str = "run-rustfix"; - pub const RUSTFIX_ONLY_MACHINE_APPLICABLE: &'static str = "rustfix-only-machine-applicable"; - pub const ASSEMBLY_OUTPUT: &'static str = "assembly-output"; - pub const STDERR_PER_BITWIDTH: &'static str = "stderr-per-bitwidth"; - pub const INCREMENTAL: &'static str = "incremental"; - pub const KNOWN_BUG: &'static str = "known-bug"; - pub const TEST_MIR_PASS: &'static str = "test-mir-pass"; - pub const REMAP_SRC_BASE: &'static str = "remap-src-base"; - pub const LLVM_COV_FLAGS: &'static str = "llvm-cov-flags"; - pub const FILECHECK_FLAGS: &'static str = "filecheck-flags"; - pub const NO_AUTO_CHECK_CFG: &'static str = "no-auto-check-cfg"; - pub const ADD_CORE_STUBS: &'static str = "add-core-stubs"; - // This isn't a real directive, just one that is probably mistyped often - pub const INCORRECT_COMPILER_FLAGS: &'static str = "compiler-flags"; -} - -impl TestProps { - pub fn new() -> Self { - TestProps { - error_patterns: vec![], - regex_error_patterns: vec![], - compile_flags: vec![], - run_flags: vec![], - doc_flags: vec![], - pp_exact: None, - aux: Default::default(), - revisions: vec![], - rustc_env: vec![ - ("RUSTC_ICE".to_string(), "0".to_string()), - ("RUST_BACKTRACE".to_string(), "short".to_string()), - ], - unset_rustc_env: vec![("RUSTC_LOG_COLOR".to_string())], - exec_env: vec![], - unset_exec_env: vec![], - build_aux_docs: false, - unique_doc_out_dir: false, - force_host: false, - check_stdout: false, - check_run_results: false, - dont_check_compiler_stdout: false, - dont_check_compiler_stderr: false, - no_prefer_dynamic: false, - pretty_mode: "normal".to_string(), - pretty_compare_only: false, - forbid_output: vec![], - incremental_dir: None, - incremental: false, - known_bug: false, - pass_mode: None, - fail_mode: None, - ignore_pass: false, - check_test_line_numbers_match: false, - normalize_stdout: vec![], - normalize_stderr: vec![], - failure_status: None, - dont_check_failure_status: false, - run_rustfix: false, - rustfix_only_machine_applicable: false, - assembly_output: None, - should_ice: false, - stderr_per_bitwidth: false, - mir_unit_test: None, - remap_src_base: false, - llvm_cov_flags: vec![], - filecheck_flags: vec![], - no_auto_check_cfg: false, - has_enzyme: false, - add_core_stubs: false, - dont_require_annotations: Default::default(), - } - } - - pub fn from_aux_file( - &self, - testfile: &Utf8Path, - revision: Option<&str>, - config: &Config, - ) -> Self { - let mut props = TestProps::new(); - - // copy over select properties to the aux build: - props.incremental_dir = self.incremental_dir.clone(); - props.ignore_pass = true; - props.load_from(testfile, revision, config); - - props - } - - pub fn from_file(testfile: &Utf8Path, revision: Option<&str>, config: &Config) -> Self { - let mut props = TestProps::new(); - props.load_from(testfile, revision, config); - props.exec_env.push(("RUSTC".to_string(), config.rustc_path.to_string())); - - match (props.pass_mode, props.fail_mode) { - (None, None) if config.mode == Mode::Ui => props.fail_mode = Some(FailMode::Check), - (Some(_), Some(_)) => panic!("cannot use a *-fail and *-pass mode together"), - _ => {} - } - - props - } - - /// Loads properties from `testfile` into `props`. If a property is - /// tied to a particular revision `foo` (indicated by writing - /// `//@[foo]`), then the property is ignored unless `test_revision` is - /// `Some("foo")`. - fn load_from(&mut self, testfile: &Utf8Path, test_revision: Option<&str>, config: &Config) { - let mut has_edition = false; - if !testfile.is_dir() { - let file = File::open(testfile.as_std_path()).unwrap(); - - let mut poisoned = false; - - iter_header( - config.mode, - &config.suite, - &mut poisoned, - testfile, - file, - &mut |directive @ DirectiveLine { raw_directive: ln, .. }| { - if !directive.applies_to_test_revision(test_revision) { - return; - } - - use directives::*; - - config.push_name_value_directive( - ln, - ERROR_PATTERN, - &mut self.error_patterns, - |r| r, - ); - config.push_name_value_directive( - ln, - REGEX_ERROR_PATTERN, - &mut self.regex_error_patterns, - |r| r, - ); - - config.push_name_value_directive(ln, DOC_FLAGS, &mut self.doc_flags, |r| r); - - fn split_flags(flags: &str) -> Vec { - // Individual flags can be single-quoted to preserve spaces; see - // . - flags - .split('\'') - .enumerate() - .flat_map(|(i, f)| { - if i % 2 == 1 { vec![f] } else { f.split_whitespace().collect() } - }) - .map(move |s| s.to_owned()) - .collect::>() - } - - if let Some(flags) = config.parse_name_value_directive(ln, COMPILE_FLAGS) { - let flags = split_flags(&flags); - for flag in &flags { - if flag == "--edition" || flag.starts_with("--edition=") { - panic!("you must use `//@ edition` to configure the edition"); - } - } - self.compile_flags.extend(flags); - } - if config.parse_name_value_directive(ln, INCORRECT_COMPILER_FLAGS).is_some() { - panic!("`compiler-flags` directive should be spelled `compile-flags`"); - } - - if let Some(edition) = config.parse_edition(ln) { - // The edition is added at the start, since flags from //@compile-flags must - // be passed to rustc last. - self.compile_flags.insert(0, format!("--edition={}", edition.trim())); - has_edition = true; - } - - config.parse_and_update_revisions(testfile, ln, &mut self.revisions); - - if let Some(flags) = config.parse_name_value_directive(ln, RUN_FLAGS) { - self.run_flags.extend(split_flags(&flags)); - } - - if self.pp_exact.is_none() { - self.pp_exact = config.parse_pp_exact(ln, testfile); - } - - config.set_name_directive(ln, SHOULD_ICE, &mut self.should_ice); - config.set_name_directive(ln, BUILD_AUX_DOCS, &mut self.build_aux_docs); - config.set_name_directive(ln, UNIQUE_DOC_OUT_DIR, &mut self.unique_doc_out_dir); - - config.set_name_directive(ln, FORCE_HOST, &mut self.force_host); - config.set_name_directive(ln, CHECK_STDOUT, &mut self.check_stdout); - config.set_name_directive(ln, CHECK_RUN_RESULTS, &mut self.check_run_results); - config.set_name_directive( - ln, - DONT_CHECK_COMPILER_STDOUT, - &mut self.dont_check_compiler_stdout, - ); - config.set_name_directive( - ln, - DONT_CHECK_COMPILER_STDERR, - &mut self.dont_check_compiler_stderr, - ); - config.set_name_directive(ln, NO_PREFER_DYNAMIC, &mut self.no_prefer_dynamic); - - if let Some(m) = config.parse_name_value_directive(ln, PRETTY_MODE) { - self.pretty_mode = m; - } - - config.set_name_directive( - ln, - PRETTY_COMPARE_ONLY, - &mut self.pretty_compare_only, - ); - - // Call a helper method to deal with aux-related directives. - parse_and_update_aux(config, ln, &mut self.aux); - - config.push_name_value_directive( - ln, - EXEC_ENV, - &mut self.exec_env, - Config::parse_env, - ); - config.push_name_value_directive( - ln, - UNSET_EXEC_ENV, - &mut self.unset_exec_env, - |r| r.trim().to_owned(), - ); - config.push_name_value_directive( - ln, - RUSTC_ENV, - &mut self.rustc_env, - Config::parse_env, - ); - config.push_name_value_directive( - ln, - UNSET_RUSTC_ENV, - &mut self.unset_rustc_env, - |r| r.trim().to_owned(), - ); - config.push_name_value_directive( - ln, - FORBID_OUTPUT, - &mut self.forbid_output, - |r| r, - ); - config.set_name_directive( - ln, - CHECK_TEST_LINE_NUMBERS_MATCH, - &mut self.check_test_line_numbers_match, - ); - - self.update_pass_mode(ln, test_revision, config); - self.update_fail_mode(ln, config); - - config.set_name_directive(ln, IGNORE_PASS, &mut self.ignore_pass); - - if let Some(NormalizeRule { kind, regex, replacement }) = - config.parse_custom_normalization(ln) - { - let rule_tuple = (regex, replacement); - match kind { - NormalizeKind::Stdout => self.normalize_stdout.push(rule_tuple), - NormalizeKind::Stderr => self.normalize_stderr.push(rule_tuple), - NormalizeKind::Stderr32bit => { - if config.target_cfg().pointer_width == 32 { - self.normalize_stderr.push(rule_tuple); - } - } - NormalizeKind::Stderr64bit => { - if config.target_cfg().pointer_width == 64 { - self.normalize_stderr.push(rule_tuple); - } - } - } - } - - if let Some(code) = config - .parse_name_value_directive(ln, FAILURE_STATUS) - .and_then(|code| code.trim().parse::().ok()) - { - self.failure_status = Some(code); - } - - config.set_name_directive( - ln, - DONT_CHECK_FAILURE_STATUS, - &mut self.dont_check_failure_status, - ); - - config.set_name_directive(ln, RUN_RUSTFIX, &mut self.run_rustfix); - config.set_name_directive( - ln, - RUSTFIX_ONLY_MACHINE_APPLICABLE, - &mut self.rustfix_only_machine_applicable, - ); - config.set_name_value_directive( - ln, - ASSEMBLY_OUTPUT, - &mut self.assembly_output, - |r| r.trim().to_string(), - ); - config.set_name_directive( - ln, - STDERR_PER_BITWIDTH, - &mut self.stderr_per_bitwidth, - ); - config.set_name_directive(ln, INCREMENTAL, &mut self.incremental); - - // Unlike the other `name_value_directive`s this needs to be handled manually, - // because it sets a `bool` flag. - if let Some(known_bug) = config.parse_name_value_directive(ln, KNOWN_BUG) { - let known_bug = known_bug.trim(); - if known_bug == "unknown" - || known_bug.split(',').all(|issue_ref| { - issue_ref - .trim() - .split_once('#') - .filter(|(_, number)| { - number.chars().all(|digit| digit.is_numeric()) - }) - .is_some() - }) - { - self.known_bug = true; - } else { - panic!( - "Invalid known-bug value: {known_bug}\nIt requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`." - ); - } - } else if config.parse_name_directive(ln, KNOWN_BUG) { - panic!( - "Invalid known-bug attribute, requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`." - ); - } - - config.set_name_value_directive( - ln, - TEST_MIR_PASS, - &mut self.mir_unit_test, - |s| s.trim().to_string(), - ); - config.set_name_directive(ln, REMAP_SRC_BASE, &mut self.remap_src_base); - - if let Some(flags) = config.parse_name_value_directive(ln, LLVM_COV_FLAGS) { - self.llvm_cov_flags.extend(split_flags(&flags)); - } - - if let Some(flags) = config.parse_name_value_directive(ln, FILECHECK_FLAGS) { - self.filecheck_flags.extend(split_flags(&flags)); - } - - config.set_name_directive(ln, NO_AUTO_CHECK_CFG, &mut self.no_auto_check_cfg); - - self.update_add_core_stubs(ln, config); - - if let Some(err_kind) = - config.parse_name_value_directive(ln, DONT_REQUIRE_ANNOTATIONS) - { - self.dont_require_annotations - .insert(ErrorKind::expect_from_user_str(err_kind.trim())); - } - }, - ); - - if poisoned { - eprintln!("errors encountered during TestProps parsing: {}", testfile); - panic!("errors encountered during TestProps parsing"); - } - } - - if self.should_ice { - self.failure_status = Some(101); - } - - if config.mode == Mode::Incremental { - self.incremental = true; - } - - if config.mode == Mode::Crashes { - // we don't want to pollute anything with backtrace-files - // also turn off backtraces in order to save some execution - // time on the tests; we only need to know IF it crashes - self.rustc_env = vec![ - ("RUST_BACKTRACE".to_string(), "0".to_string()), - ("RUSTC_ICE".to_string(), "0".to_string()), - ]; - } - - for key in &["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] { - if let Ok(val) = env::var(key) { - if !self.exec_env.iter().any(|&(ref x, _)| x == key) { - self.exec_env.push(((*key).to_owned(), val)) - } - } - } - - if let (Some(edition), false) = (&config.edition, has_edition) { - // The edition is added at the start, since flags from //@compile-flags must be passed - // to rustc last. - self.compile_flags.insert(0, format!("--edition={}", edition)); - } - } - - fn update_fail_mode(&mut self, ln: &str, config: &Config) { - let check_ui = |mode: &str| { - // Mode::Crashes may need build-fail in order to trigger llvm errors or stack overflows - if config.mode != Mode::Ui && config.mode != Mode::Crashes { - panic!("`{}-fail` header is only supported in UI tests", mode); - } - }; - if config.mode == Mode::Ui && config.parse_name_directive(ln, "compile-fail") { - panic!("`compile-fail` header is useless in UI tests"); - } - let fail_mode = if config.parse_name_directive(ln, "check-fail") { - check_ui("check"); - Some(FailMode::Check) - } else if config.parse_name_directive(ln, "build-fail") { - check_ui("build"); - Some(FailMode::Build) - } else if config.parse_name_directive(ln, "run-fail") { - check_ui("run"); - Some(FailMode::Run) - } else { - None - }; - match (self.fail_mode, fail_mode) { - (None, Some(_)) => self.fail_mode = fail_mode, - (Some(_), Some(_)) => panic!("multiple `*-fail` headers in a single test"), - (_, None) => {} - } - } - - fn update_pass_mode(&mut self, ln: &str, revision: Option<&str>, config: &Config) { - let check_no_run = |s| match (config.mode, s) { - (Mode::Ui, _) => (), - (Mode::Crashes, _) => (), - (Mode::Codegen, "build-pass") => (), - (Mode::Incremental, _) => { - if revision.is_some() && !self.revisions.iter().all(|r| r.starts_with("cfail")) { - panic!("`{s}` header is only supported in `cfail` incremental tests") - } - } - (mode, _) => panic!("`{s}` header is not supported in `{mode}` tests"), - }; - let pass_mode = if config.parse_name_directive(ln, "check-pass") { - check_no_run("check-pass"); - Some(PassMode::Check) - } else if config.parse_name_directive(ln, "build-pass") { - check_no_run("build-pass"); - Some(PassMode::Build) - } else if config.parse_name_directive(ln, "run-pass") { - check_no_run("run-pass"); - Some(PassMode::Run) - } else { - None - }; - match (self.pass_mode, pass_mode) { - (None, Some(_)) => self.pass_mode = pass_mode, - (Some(_), Some(_)) => panic!("multiple `*-pass` headers in a single test"), - (_, None) => {} - } - } - - pub fn pass_mode(&self, config: &Config) -> Option { - if !self.ignore_pass && self.fail_mode.is_none() { - if let mode @ Some(_) = config.force_pass_mode { - return mode; - } - } - self.pass_mode - } - - // does not consider CLI override for pass mode - pub fn local_pass_mode(&self) -> Option { - self.pass_mode - } - - pub fn update_add_core_stubs(&mut self, ln: &str, config: &Config) { - let add_core_stubs = config.parse_name_directive(ln, directives::ADD_CORE_STUBS); - if add_core_stubs { - if !matches!(config.mode, Mode::Ui | Mode::Codegen | Mode::Assembly) { - panic!( - "`add-core-stubs` is currently only supported for ui, codegen and assembly test modes" - ); - } - - // FIXME(jieyouxu): this check is currently order-dependent, but we should probably - // collect all directives in one go then perform a validation pass after that. - if self.local_pass_mode().is_some_and(|pm| pm == PassMode::Run) { - // `minicore` can only be used with non-run modes, because it's `core` prelude stubs - // and can't run. - panic!("`add-core-stubs` cannot be used to run the test binary"); - } - - self.add_core_stubs = add_core_stubs; - } - } -} - -/// If the given line begins with the appropriate comment prefix for a directive, -/// returns a struct containing various parts of the directive. -fn line_directive<'line>( - line_number: usize, - original_line: &'line str, -) -> Option> { - // Ignore lines that don't start with the comment prefix. - let after_comment = - original_line.trim_start().strip_prefix(COMPILETEST_DIRECTIVE_PREFIX)?.trim_start(); - - let revision; - let raw_directive; - - if let Some(after_open_bracket) = after_comment.strip_prefix('[') { - // A comment like `//@[foo]` only applies to revision `foo`. - let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else { - panic!( - "malformed condition directive: expected `{COMPILETEST_DIRECTIVE_PREFIX}[foo]`, found `{original_line}`" - ) - }; - - revision = Some(line_revision); - raw_directive = after_close_bracket.trim_start(); - } else { - revision = None; - raw_directive = after_comment; - }; - - Some(DirectiveLine { line_number, revision, raw_directive }) -} - -// To prevent duplicating the list of directives between `compiletest`,`htmldocck` and `jsondocck`, -// we put it into a common file which is included in rust code and parsed here. -// FIXME: This setup is temporary until we figure out how to improve this situation. -// See . -include!("directive-list.rs"); - -const KNOWN_HTMLDOCCK_DIRECTIVE_NAMES: &[&str] = &[ - "count", - "!count", - "files", - "!files", - "has", - "!has", - "has-dir", - "!has-dir", - "hasraw", - "!hasraw", - "matches", - "!matches", - "matchesraw", - "!matchesraw", - "snapshot", - "!snapshot", -]; - -const KNOWN_JSONDOCCK_DIRECTIVE_NAMES: &[&str] = - &["count", "!count", "has", "!has", "is", "!is", "ismany", "!ismany", "set", "!set"]; - -/// The (partly) broken-down contents of a line containing a test directive, -/// which [`iter_header`] passes to its callback function. -/// -/// For example: -/// -/// ```text -/// //@ compile-flags: -O -/// ^^^^^^^^^^^^^^^^^ raw_directive -/// -/// //@ [foo] compile-flags: -O -/// ^^^ revision -/// ^^^^^^^^^^^^^^^^^ raw_directive -/// ``` -struct DirectiveLine<'ln> { - line_number: usize, - /// Some test directives start with a revision name in square brackets - /// (e.g. `[foo]`), and only apply to that revision of the test. - /// If present, this field contains the revision name (e.g. `foo`). - revision: Option<&'ln str>, - /// The main part of the directive, after removing the comment prefix - /// and the optional revision specifier. - /// - /// This is "raw" because the directive's name and colon-separated value - /// (if present) have not yet been extracted or checked. - raw_directive: &'ln str, -} - -impl<'ln> DirectiveLine<'ln> { - fn applies_to_test_revision(&self, test_revision: Option<&str>) -> bool { - self.revision.is_none() || self.revision == test_revision - } -} - -pub(crate) struct CheckDirectiveResult<'ln> { - is_known_directive: bool, - trailing_directive: Option<&'ln str>, -} - -pub(crate) fn check_directive<'a>( - directive_ln: &'a str, - mode: Mode, - original_line: &str, -) -> CheckDirectiveResult<'a> { - let (directive_name, post) = directive_ln.split_once([':', ' ']).unwrap_or((directive_ln, "")); - - let trailing = post.trim().split_once(' ').map(|(pre, _)| pre).unwrap_or(post); - let is_known = |s: &str| { - KNOWN_DIRECTIVE_NAMES.contains(&s) - || match mode { - Mode::Rustdoc | Mode::RustdocJson => { - original_line.starts_with("//@") - && match mode { - Mode::Rustdoc => KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, - Mode::RustdocJson => KNOWN_JSONDOCCK_DIRECTIVE_NAMES, - _ => unreachable!(), - } - .contains(&s) - } - _ => false, - } - }; - let trailing_directive = { - // 1. is the directive name followed by a space? (to exclude `:`) - matches!(directive_ln.get(directive_name.len()..), Some(s) if s.starts_with(' ')) - // 2. is what is after that directive also a directive (ex: "only-x86 only-arm") - && is_known(trailing) - } - .then_some(trailing); - - CheckDirectiveResult { is_known_directive: is_known(&directive_name), trailing_directive } -} - -const COMPILETEST_DIRECTIVE_PREFIX: &str = "//@"; - -fn iter_header( - mode: Mode, - _suite: &str, - poisoned: &mut bool, - testfile: &Utf8Path, - rdr: impl Read, - it: &mut dyn FnMut(DirectiveLine<'_>), -) { - if testfile.is_dir() { - return; - } - - // Coverage tests in coverage-run mode always have these extra directives, without needing to - // specify them manually in every test file. - // - // FIXME(jieyouxu): I feel like there's a better way to do this, leaving for later. - if mode == Mode::CoverageRun { - let extra_directives: &[&str] = &[ - "needs-profiler-runtime", - // FIXME(pietroalbini): this test currently does not work on cross-compiled targets - // because remote-test is not capable of sending back the *.profraw files generated by - // the LLVM instrumentation. - "ignore-cross-compile", - ]; - // Process the extra implied directives, with a dummy line number of 0. - for raw_directive in extra_directives { - it(DirectiveLine { line_number: 0, revision: None, raw_directive }); - } - } - - let mut rdr = BufReader::with_capacity(1024, rdr); - let mut ln = String::new(); - let mut line_number = 0; - - loop { - line_number += 1; - ln.clear(); - if rdr.read_line(&mut ln).unwrap() == 0 { - break; - } - let ln = ln.trim(); - - let Some(directive_line) = line_directive(line_number, ln) else { - continue; - }; - - // Perform unknown directive check on Rust files. - if testfile.extension().map(|e| e == "rs").unwrap_or(false) { - let CheckDirectiveResult { is_known_directive, trailing_directive } = - check_directive(directive_line.raw_directive, mode, ln); - - if !is_known_directive { - *poisoned = true; - - error!( - "{testfile}:{line_number}: detected unknown compiletest test directive `{}`", - directive_line.raw_directive, - ); - - return; - } - - if let Some(trailing_directive) = &trailing_directive { - *poisoned = true; - - error!( - "{testfile}:{line_number}: detected trailing compiletest test directive `{}`", - trailing_directive, - ); - help!("put the trailing directive in it's own line: `//@ {}`", trailing_directive); - - return; - } - } - - it(directive_line); - } -} - -impl Config { - fn parse_and_update_revisions( - &self, - testfile: &Utf8Path, - line: &str, - existing: &mut Vec, - ) { - const FORBIDDEN_REVISION_NAMES: [&str; 2] = [ - // `//@ revisions: true false` Implying `--cfg=true` and `--cfg=false` makes it very - // weird for the test, since if the test writer wants a cfg of the same revision name - // they'd have to use `cfg(r#true)` and `cfg(r#false)`. - "true", "false", - ]; - - const FILECHECK_FORBIDDEN_REVISION_NAMES: [&str; 9] = - ["CHECK", "COM", "NEXT", "SAME", "EMPTY", "NOT", "COUNT", "DAG", "LABEL"]; - - if let Some(raw) = self.parse_name_value_directive(line, "revisions") { - if self.mode == Mode::RunMake { - panic!("`run-make` tests do not support revisions: {}", testfile); - } - - let mut duplicates: HashSet<_> = existing.iter().cloned().collect(); - for revision in raw.split_whitespace() { - if !duplicates.insert(revision.to_string()) { - panic!("duplicate revision: `{}` in line `{}`: {}", revision, raw, testfile); - } - - if FORBIDDEN_REVISION_NAMES.contains(&revision) { - panic!( - "revision name `{revision}` is not permitted: `{}` in line `{}`: {}", - revision, raw, testfile - ); - } - - if matches!(self.mode, Mode::Assembly | Mode::Codegen | Mode::MirOpt) - && FILECHECK_FORBIDDEN_REVISION_NAMES.contains(&revision) - { - panic!( - "revision name `{revision}` is not permitted in a test suite that uses \ - `FileCheck` annotations as it is confusing when used as custom `FileCheck` \ - prefix: `{revision}` in line `{}`: {}", - raw, testfile - ); - } - - existing.push(revision.to_string()); - } - } - } - - fn parse_env(nv: String) -> (String, String) { - // nv is either FOO or FOO=BAR - // FIXME(Zalathar): The form without `=` seems to be unused; should - // we drop support for it? - let (name, value) = nv.split_once('=').unwrap_or((&nv, "")); - // Trim whitespace from the name, so that `//@ exec-env: FOO=BAR` - // sees the name as `FOO` and not ` FOO`. - let name = name.trim(); - (name.to_owned(), value.to_owned()) - } - - fn parse_pp_exact(&self, line: &str, testfile: &Utf8Path) -> Option { - if let Some(s) = self.parse_name_value_directive(line, "pp-exact") { - Some(Utf8PathBuf::from(&s)) - } else if self.parse_name_directive(line, "pp-exact") { - testfile.file_name().map(Utf8PathBuf::from) - } else { - None - } - } - - fn parse_custom_normalization(&self, raw_directive: &str) -> Option { - // FIXME(Zalathar): Integrate name/value splitting into `DirectiveLine` - // instead of doing it here. - let (directive_name, raw_value) = raw_directive.split_once(':')?; - - let kind = match directive_name { - "normalize-stdout" => NormalizeKind::Stdout, - "normalize-stderr" => NormalizeKind::Stderr, - "normalize-stderr-32bit" => NormalizeKind::Stderr32bit, - "normalize-stderr-64bit" => NormalizeKind::Stderr64bit, - _ => return None, - }; - - let Some((regex, replacement)) = parse_normalize_rule(raw_value) else { - error!("couldn't parse custom normalization rule: `{raw_directive}`"); - help!("expected syntax is: `{directive_name}: \"REGEX\" -> \"REPLACEMENT\"`"); - panic!("invalid normalization rule detected"); - }; - Some(NormalizeRule { kind, regex, replacement }) - } - - fn parse_name_directive(&self, line: &str, directive: &str) -> bool { - // Ensure the directive is a whole word. Do not match "ignore-x86" when - // the line says "ignore-x86_64". - line.starts_with(directive) - && matches!(line.as_bytes().get(directive.len()), None | Some(&b' ') | Some(&b':')) - } - - fn parse_negative_name_directive(&self, line: &str, directive: &str) -> bool { - line.starts_with("no-") && self.parse_name_directive(&line[3..], directive) - } - - pub fn parse_name_value_directive(&self, line: &str, directive: &str) -> Option { - let colon = directive.len(); - if line.starts_with(directive) && line.as_bytes().get(colon) == Some(&b':') { - let value = line[(colon + 1)..].to_owned(); - debug!("{}: {}", directive, value); - Some(expand_variables(value, self)) - } else { - None - } - } - - fn parse_edition(&self, line: &str) -> Option { - self.parse_name_value_directive(line, "edition") - } - - fn set_name_directive(&self, line: &str, directive: &str, value: &mut bool) { - match value { - true => { - if self.parse_negative_name_directive(line, directive) { - *value = false; - } - } - false => { - if self.parse_name_directive(line, directive) { - *value = true; - } - } - } - } - - fn set_name_value_directive( - &self, - line: &str, - directive: &str, - value: &mut Option, - parse: impl FnOnce(String) -> T, - ) { - if value.is_none() { - *value = self.parse_name_value_directive(line, directive).map(parse); - } - } - - fn push_name_value_directive( - &self, - line: &str, - directive: &str, - values: &mut Vec, - parse: impl FnOnce(String) -> T, - ) { - if let Some(value) = self.parse_name_value_directive(line, directive).map(parse) { - values.push(value); - } - } -} - -// FIXME(jieyouxu): fix some of these variable names to more accurately reflect what they do. -fn expand_variables(mut value: String, config: &Config) -> String { - const CWD: &str = "{{cwd}}"; - const SRC_BASE: &str = "{{src-base}}"; - const TEST_SUITE_BUILD_BASE: &str = "{{build-base}}"; - const RUST_SRC_BASE: &str = "{{rust-src-base}}"; - const SYSROOT_BASE: &str = "{{sysroot-base}}"; - const TARGET_LINKER: &str = "{{target-linker}}"; - const TARGET: &str = "{{target}}"; - - if value.contains(CWD) { - let cwd = env::current_dir().unwrap(); - value = value.replace(CWD, &cwd.to_str().unwrap()); - } - - if value.contains(SRC_BASE) { - value = value.replace(SRC_BASE, &config.src_test_suite_root.as_str()); - } - - if value.contains(TEST_SUITE_BUILD_BASE) { - value = value.replace(TEST_SUITE_BUILD_BASE, &config.build_test_suite_root.as_str()); - } - - if value.contains(SYSROOT_BASE) { - value = value.replace(SYSROOT_BASE, &config.sysroot_base.as_str()); - } - - if value.contains(TARGET_LINKER) { - value = value.replace(TARGET_LINKER, config.target_linker.as_deref().unwrap_or("")); - } - - if value.contains(TARGET) { - value = value.replace(TARGET, &config.target); - } - - if value.contains(RUST_SRC_BASE) { - let src_base = config.sysroot_base.join("lib/rustlib/src/rust"); - src_base.try_exists().expect(&*format!("{} should exists", src_base)); - let src_base = src_base.read_link_utf8().unwrap_or(src_base); - value = value.replace(RUST_SRC_BASE, &src_base.as_str()); - } - - value -} - -struct NormalizeRule { - kind: NormalizeKind, - regex: String, - replacement: String, -} - -enum NormalizeKind { - Stdout, - Stderr, - Stderr32bit, - Stderr64bit, -} - -/// Parses the regex and replacement values of a `//@ normalize-*` header, -/// in the format: -/// ```text -/// "REGEX" -> "REPLACEMENT" -/// ``` -fn parse_normalize_rule(raw_value: &str) -> Option<(String, String)> { - // FIXME: Support escaped double-quotes in strings. - let captures = static_regex!( - r#"(?x) # (verbose mode regex) - ^ - \s* # (leading whitespace) - "(?[^"]*)" # "REGEX" - \s+->\s+ # -> - "(?[^"]*)" # "REPLACEMENT" - $ - "# - ) - .captures(raw_value)?; - let regex = captures["regex"].to_owned(); - let replacement = captures["replacement"].to_owned(); - // A `\n` sequence in the replacement becomes an actual newline. - // FIXME: Do unescaping in a less ad-hoc way, and perhaps support escaped - // backslashes and double-quotes. - let replacement = replacement.replace("\\n", "\n"); - Some((regex, replacement)) -} - -/// Given an llvm version string that looks like `1.2.3-rc1`, extract as semver. Note that this -/// accepts more than just strict `semver` syntax (as in `major.minor.patch`); this permits omitting -/// minor and patch version components so users can write e.g. `//@ min-llvm-version: 19` instead of -/// having to write `//@ min-llvm-version: 19.0.0`. -/// -/// Currently panics if the input string is malformed, though we really should not use panic as an -/// error handling strategy. -/// -/// FIXME(jieyouxu): improve error handling -pub fn extract_llvm_version(version: &str) -> Version { - // The version substring we're interested in usually looks like the `1.2.3`, without any of the - // fancy suffix like `-rc1` or `meow`. - let version = version.trim(); - let uninterested = |c: char| !c.is_ascii_digit() && c != '.'; - let version_without_suffix = match version.split_once(uninterested) { - Some((prefix, _suffix)) => prefix, - None => version, - }; - - let components: Vec = version_without_suffix - .split('.') - .map(|s| s.parse().expect("llvm version component should consist of only digits")) - .collect(); - - match &components[..] { - [major] => Version::new(*major, 0, 0), - [major, minor] => Version::new(*major, *minor, 0), - [major, minor, patch] => Version::new(*major, *minor, *patch), - _ => panic!("malformed llvm version string, expected only 1-3 components: {version}"), - } -} - -pub fn extract_llvm_version_from_binary(binary_path: &str) -> Option { - let output = Command::new(binary_path).arg("--version").output().ok()?; - if !output.status.success() { - return None; - } - let version = String::from_utf8(output.stdout).ok()?; - for line in version.lines() { - if let Some(version) = line.split("LLVM version ").nth(1) { - return Some(extract_llvm_version(version)); - } - } - None -} - -/// For tests using the `needs-llvm-zstd` directive: -/// - for local LLVM builds, try to find the static zstd library in the llvm-config system libs. -/// - for `download-ci-llvm`, see if `lld` was built with zstd support. -pub fn llvm_has_libzstd(config: &Config) -> bool { - // Strategy 1: works for local builds but not with `download-ci-llvm`. - // - // We check whether `llvm-config` returns the zstd library. Bootstrap's `llvm.libzstd` will only - // ask to statically link it when building LLVM, so we only check if the list of system libs - // contains a path to that static lib, and that it exists. - // - // See compiler/rustc_llvm/build.rs for more details and similar expectations. - fn is_zstd_in_config(llvm_bin_dir: &Utf8Path) -> Option<()> { - let llvm_config_path = llvm_bin_dir.join("llvm-config"); - let output = Command::new(llvm_config_path).arg("--system-libs").output().ok()?; - assert!(output.status.success(), "running llvm-config --system-libs failed"); - - let libs = String::from_utf8(output.stdout).ok()?; - for lib in libs.split_whitespace() { - if lib.ends_with("libzstd.a") && Utf8Path::new(lib).exists() { - return Some(()); - } - } - - None - } - - // Strategy 2: `download-ci-llvm`'s `llvm-config --system-libs` will not return any libs to - // use. - // - // The CI artifacts also don't contain the bootstrap config used to build them: otherwise we - // could have looked at the `llvm.libzstd` config. - // - // We infer whether `LLVM_ENABLE_ZSTD` was used to build LLVM as a byproduct of testing whether - // `lld` supports it. If not, an error will be emitted: "LLVM was not built with - // LLVM_ENABLE_ZSTD or did not find zstd at build time". - #[cfg(unix)] - fn is_lld_built_with_zstd(llvm_bin_dir: &Utf8Path) -> Option<()> { - let lld_path = llvm_bin_dir.join("lld"); - if lld_path.exists() { - // We can't call `lld` as-is, it expects to be invoked by a compiler driver using a - // different name. Prepare a temporary symlink to do that. - let lld_symlink_path = llvm_bin_dir.join("ld.lld"); - if !lld_symlink_path.exists() { - std::os::unix::fs::symlink(lld_path, &lld_symlink_path).ok()?; - } - - // Run `lld` with a zstd flag. We expect this command to always error here, we don't - // want to link actual files and don't pass any. - let output = Command::new(&lld_symlink_path) - .arg("--compress-debug-sections=zstd") - .output() - .ok()?; - assert!(!output.status.success()); - - // Look for a specific error caused by LLVM not being built with zstd support. We could - // also look for the "no input files" message, indicating the zstd flag was accepted. - let stderr = String::from_utf8(output.stderr).ok()?; - let zstd_available = !stderr.contains("LLVM was not built with LLVM_ENABLE_ZSTD"); - - // We don't particularly need to clean the link up (so the previous commands could fail - // in theory but won't in practice), but we can try. - std::fs::remove_file(lld_symlink_path).ok()?; - - if zstd_available { - return Some(()); - } - } - - None - } - - #[cfg(not(unix))] - fn is_lld_built_with_zstd(_llvm_bin_dir: &Utf8Path) -> Option<()> { - None - } - - if let Some(llvm_bin_dir) = &config.llvm_bin_dir { - // Strategy 1: for local LLVM builds. - if is_zstd_in_config(llvm_bin_dir).is_some() { - return true; - } - - // Strategy 2: for LLVM artifacts built on CI via `download-ci-llvm`. - // - // It doesn't work for cases where the artifacts don't contain the linker, but it's - // best-effort: CI has `llvm.libzstd` and `lld` enabled on the x64 linux artifacts, so it - // will at least work there. - // - // If this can be improved and expanded to less common cases in the future, it should. - if config.target == "x86_64-unknown-linux-gnu" - && config.host == config.target - && is_lld_built_with_zstd(llvm_bin_dir).is_some() - { - return true; - } - } - - // Otherwise, all hope is lost. - false -} - -/// Takes a directive of the form `" [- ]"`, returns the numeric representation -/// of `` and `` as tuple: `(, )`. -/// -/// If the `` part is omitted, the second component of the tuple is the same as -/// ``. -fn extract_version_range<'a, F, VersionTy: Clone>( - line: &'a str, - parse: F, -) -> Option<(VersionTy, VersionTy)> -where - F: Fn(&'a str) -> Option, -{ - let mut splits = line.splitn(2, "- ").map(str::trim); - let min = splits.next().unwrap(); - if min.ends_with('-') { - return None; - } - - let max = splits.next(); - - if min.is_empty() { - return None; - } - - let min = parse(min)?; - let max = match max { - Some("") => return None, - Some(max) => parse(max)?, - _ => min.clone(), - }; - - Some((min, max)) -} - -pub(crate) fn make_test_description( - config: &Config, - cache: &HeadersCache, - name: String, - path: &Utf8Path, - src: R, - test_revision: Option<&str>, - poisoned: &mut bool, -) -> CollectedTestDesc { - let mut ignore = false; - let mut ignore_message = None; - let mut should_fail = false; - - let mut local_poisoned = false; - - // Scan through the test file to handle `ignore-*`, `only-*`, and `needs-*` directives. - iter_header( - config.mode, - &config.suite, - &mut local_poisoned, - path, - src, - &mut |directive @ DirectiveLine { line_number, raw_directive: ln, .. }| { - if !directive.applies_to_test_revision(test_revision) { - return; - } - - macro_rules! decision { - ($e:expr) => { - match $e { - IgnoreDecision::Ignore { reason } => { - ignore = true; - ignore_message = Some(reason.into()); - } - IgnoreDecision::Error { message } => { - error!("{path}:{line_number}: {message}"); - *poisoned = true; - return; - } - IgnoreDecision::Continue => {} - } - }; - } - - decision!(cfg::handle_ignore(config, ln)); - decision!(cfg::handle_only(config, ln)); - decision!(needs::handle_needs(&cache.needs, config, ln)); - decision!(ignore_llvm(config, path, ln)); - decision!(ignore_cdb(config, ln)); - decision!(ignore_gdb(config, ln)); - decision!(ignore_lldb(config, ln)); - - if config.target == "wasm32-unknown-unknown" - && config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS) - { - decision!(IgnoreDecision::Ignore { - reason: "ignored on WASM as the run results cannot be checked there".into(), - }); - } - - should_fail |= config.parse_name_directive(ln, "should-fail"); - }, - ); - - if local_poisoned { - eprintln!("errors encountered when trying to make test description: {}", path); - panic!("errors encountered when trying to make test description"); - } - - // The `should-fail` annotation doesn't apply to pretty tests, - // since we run the pretty printer across all tests by default. - // If desired, we could add a `should-fail-pretty` annotation. - let should_panic = match config.mode { - crate::common::Pretty => ShouldPanic::No, - _ if should_fail => ShouldPanic::Yes, - _ => ShouldPanic::No, - }; - - CollectedTestDesc { name, ignore, ignore_message, should_panic } -} - -fn ignore_cdb(config: &Config, line: &str) -> IgnoreDecision { - if config.debugger != Some(Debugger::Cdb) { - return IgnoreDecision::Continue; - } - - if let Some(actual_version) = config.cdb_version { - if let Some(rest) = line.strip_prefix("min-cdb-version:").map(str::trim) { - let min_version = extract_cdb_version(rest).unwrap_or_else(|| { - panic!("couldn't parse version range: {:?}", rest); - }); - - // Ignore if actual version is smaller than the minimum - // required version - if actual_version < min_version { - return IgnoreDecision::Ignore { - reason: format!("ignored when the CDB version is lower than {rest}"), - }; - } - } - } - IgnoreDecision::Continue -} - -fn ignore_gdb(config: &Config, line: &str) -> IgnoreDecision { - if config.debugger != Some(Debugger::Gdb) { - return IgnoreDecision::Continue; - } - - if let Some(actual_version) = config.gdb_version { - if let Some(rest) = line.strip_prefix("min-gdb-version:").map(str::trim) { - let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version) - .unwrap_or_else(|| { - panic!("couldn't parse version range: {:?}", rest); - }); - - if start_ver != end_ver { - panic!("Expected single GDB version") - } - // Ignore if actual version is smaller than the minimum - // required version - if actual_version < start_ver { - return IgnoreDecision::Ignore { - reason: format!("ignored when the GDB version is lower than {rest}"), - }; - } - } else if let Some(rest) = line.strip_prefix("ignore-gdb-version:").map(str::trim) { - let (min_version, max_version) = extract_version_range(rest, extract_gdb_version) - .unwrap_or_else(|| { - panic!("couldn't parse version range: {:?}", rest); - }); - - if max_version < min_version { - panic!("Malformed GDB version range: max < min") - } - - if actual_version >= min_version && actual_version <= max_version { - if min_version == max_version { - return IgnoreDecision::Ignore { - reason: format!("ignored when the GDB version is {rest}"), - }; - } else { - return IgnoreDecision::Ignore { - reason: format!("ignored when the GDB version is between {rest}"), - }; - } - } - } - } - IgnoreDecision::Continue -} - -fn ignore_lldb(config: &Config, line: &str) -> IgnoreDecision { - if config.debugger != Some(Debugger::Lldb) { - return IgnoreDecision::Continue; - } - - if let Some(actual_version) = config.lldb_version { - if let Some(rest) = line.strip_prefix("min-lldb-version:").map(str::trim) { - let min_version = rest.parse().unwrap_or_else(|e| { - panic!("Unexpected format of LLDB version string: {}\n{:?}", rest, e); - }); - // Ignore if actual version is smaller the minimum required - // version - if actual_version < min_version { - return IgnoreDecision::Ignore { - reason: format!("ignored when the LLDB version is {rest}"), - }; - } - } - } - IgnoreDecision::Continue -} - -fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision { - if let Some(needed_components) = - config.parse_name_value_directive(line, "needs-llvm-components") - { - let components: HashSet<_> = config.llvm_components.split_whitespace().collect(); - if let Some(missing_component) = needed_components - .split_whitespace() - .find(|needed_component| !components.contains(needed_component)) - { - if env::var_os("COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS").is_some() { - panic!( - "missing LLVM component {}, and COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS is set: {}", - missing_component, path - ); - } - return IgnoreDecision::Ignore { - reason: format!("ignored when the {missing_component} LLVM component is missing"), - }; - } - } - if let Some(actual_version) = &config.llvm_version { - // Note that these `min` versions will check for not just major versions. - - if let Some(version_string) = config.parse_name_value_directive(line, "min-llvm-version") { - let min_version = extract_llvm_version(&version_string); - // Ignore if actual version is smaller than the minimum required version. - if *actual_version < min_version { - return IgnoreDecision::Ignore { - reason: format!( - "ignored when the LLVM version {actual_version} is older than {min_version}" - ), - }; - } - } else if let Some(version_string) = - config.parse_name_value_directive(line, "max-llvm-major-version") - { - let max_version = extract_llvm_version(&version_string); - // Ignore if actual major version is larger than the maximum required major version. - if actual_version.major > max_version.major { - return IgnoreDecision::Ignore { - reason: format!( - "ignored when the LLVM version ({actual_version}) is newer than major\ - version {}", - max_version.major - ), - }; - } - } else if let Some(version_string) = - config.parse_name_value_directive(line, "min-system-llvm-version") - { - let min_version = extract_llvm_version(&version_string); - // Ignore if using system LLVM and actual version - // is smaller the minimum required version - if config.system_llvm && *actual_version < min_version { - return IgnoreDecision::Ignore { - reason: format!( - "ignored when the system LLVM version {actual_version} is older than {min_version}" - ), - }; - } - } else if let Some(version_range) = - config.parse_name_value_directive(line, "ignore-llvm-version") - { - // Syntax is: "ignore-llvm-version: [- ]" - let (v_min, v_max) = - extract_version_range(&version_range, |s| Some(extract_llvm_version(s))) - .unwrap_or_else(|| { - panic!("couldn't parse version range: \"{version_range}\""); - }); - if v_max < v_min { - panic!("malformed LLVM version range where {v_max} < {v_min}") - } - // Ignore if version lies inside of range. - if *actual_version >= v_min && *actual_version <= v_max { - if v_min == v_max { - return IgnoreDecision::Ignore { - reason: format!("ignored when the LLVM version is {actual_version}"), - }; - } else { - return IgnoreDecision::Ignore { - reason: format!( - "ignored when the LLVM version is between {v_min} and {v_max}" - ), - }; - } - } - } else if let Some(version_string) = - config.parse_name_value_directive(line, "exact-llvm-major-version") - { - // Syntax is "exact-llvm-major-version: " - let version = extract_llvm_version(&version_string); - if actual_version.major != version.major { - return IgnoreDecision::Ignore { - reason: format!( - "ignored when the actual LLVM major version is {}, but the test only targets major version {}", - actual_version.major, version.major - ), - }; - } - } - } - IgnoreDecision::Continue -} - -enum IgnoreDecision { - Ignore { reason: String }, - Continue, - Error { message: String }, -} diff --git a/src/tools/compiletest/src/header/auxiliary.rs b/src/tools/compiletest/src/header/auxiliary.rs deleted file mode 100644 index 0e1f3a785f8..00000000000 --- a/src/tools/compiletest/src/header/auxiliary.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! Code for dealing with test directives that request an "auxiliary" crate to -//! be built and made available to the test in some way. - -use std::iter; - -use crate::common::Config; -use crate::header::directives::{AUX_BIN, AUX_BUILD, AUX_CODEGEN_BACKEND, AUX_CRATE, PROC_MACRO}; - -/// Properties parsed from `aux-*` test directives. -#[derive(Clone, Debug, Default)] -pub(crate) struct AuxProps { - /// Other crates that should be built and made available to this test. - /// These are filenames relative to `./auxiliary/` in the test's directory. - pub(crate) builds: Vec, - /// Auxiliary crates that should be compiled as `#![crate_type = "bin"]`. - pub(crate) bins: Vec, - /// Similar to `builds`, but a list of NAME=somelib.rs of dependencies - /// to build and pass with the `--extern` flag. - pub(crate) crates: Vec<(String, String)>, - /// Same as `builds`, but for proc-macros. - pub(crate) proc_macros: Vec, - /// Similar to `builds`, but also uses the resulting dylib as a - /// `-Zcodegen-backend` when compiling the test file. - pub(crate) codegen_backend: Option, -} - -impl AuxProps { - /// Yields all of the paths (relative to `./auxiliary/`) that have been - /// specified in `aux-*` directives for this test. - pub(crate) fn all_aux_path_strings(&self) -> impl Iterator { - let Self { builds, bins, crates, proc_macros, codegen_backend } = self; - - iter::empty() - .chain(builds.iter().map(String::as_str)) - .chain(bins.iter().map(String::as_str)) - .chain(crates.iter().map(|(_, path)| path.as_str())) - .chain(proc_macros.iter().map(String::as_str)) - .chain(codegen_backend.iter().map(String::as_str)) - } -} - -/// If the given test directive line contains an `aux-*` directive, parse it -/// and update [`AuxProps`] accordingly. -pub(super) fn parse_and_update_aux(config: &Config, ln: &str, aux: &mut AuxProps) { - if !(ln.starts_with("aux-") || ln.starts_with("proc-macro")) { - return; - } - - config.push_name_value_directive(ln, AUX_BUILD, &mut aux.builds, |r| r.trim().to_string()); - config.push_name_value_directive(ln, AUX_BIN, &mut aux.bins, |r| r.trim().to_string()); - config.push_name_value_directive(ln, AUX_CRATE, &mut aux.crates, parse_aux_crate); - config - .push_name_value_directive(ln, PROC_MACRO, &mut aux.proc_macros, |r| r.trim().to_string()); - if let Some(r) = config.parse_name_value_directive(ln, AUX_CODEGEN_BACKEND) { - aux.codegen_backend = Some(r.trim().to_owned()); - } -} - -fn parse_aux_crate(r: String) -> (String, String) { - let mut parts = r.trim().splitn(2, '='); - ( - parts.next().expect("missing aux-crate name (e.g. log=log.rs)").to_string(), - parts.next().expect("missing aux-crate value (e.g. log=log.rs)").to_string(), - ) -} diff --git a/src/tools/compiletest/src/header/cfg.rs b/src/tools/compiletest/src/header/cfg.rs deleted file mode 100644 index f1f1384afb9..00000000000 --- a/src/tools/compiletest/src/header/cfg.rs +++ /dev/null @@ -1,414 +0,0 @@ -use std::collections::HashSet; - -use crate::common::{CompareMode, Config, Debugger}; -use crate::header::IgnoreDecision; - -const EXTRA_ARCHS: &[&str] = &["spirv"]; - -pub(super) fn handle_ignore(config: &Config, line: &str) -> IgnoreDecision { - let parsed = parse_cfg_name_directive(config, line, "ignore"); - match parsed.outcome { - MatchOutcome::NoMatch => IgnoreDecision::Continue, - MatchOutcome::Match => IgnoreDecision::Ignore { - reason: match parsed.comment { - Some(comment) => format!("ignored {} ({comment})", parsed.pretty_reason.unwrap()), - None => format!("ignored {}", parsed.pretty_reason.unwrap()), - }, - }, - MatchOutcome::Invalid => IgnoreDecision::Error { message: format!("invalid line: {line}") }, - MatchOutcome::External => IgnoreDecision::Continue, - MatchOutcome::NotADirective => IgnoreDecision::Continue, - } -} - -pub(super) fn handle_only(config: &Config, line: &str) -> IgnoreDecision { - let parsed = parse_cfg_name_directive(config, line, "only"); - match parsed.outcome { - MatchOutcome::Match => IgnoreDecision::Continue, - MatchOutcome::NoMatch => IgnoreDecision::Ignore { - reason: match parsed.comment { - Some(comment) => { - format!("only executed {} ({comment})", parsed.pretty_reason.unwrap()) - } - None => format!("only executed {}", parsed.pretty_reason.unwrap()), - }, - }, - MatchOutcome::Invalid => IgnoreDecision::Error { message: format!("invalid line: {line}") }, - MatchOutcome::External => IgnoreDecision::Continue, - MatchOutcome::NotADirective => IgnoreDecision::Continue, - } -} - -/// Parses a name-value directive which contains config-specific information, e.g., `ignore-x86` -/// or `only-windows`. -fn parse_cfg_name_directive<'a>( - config: &Config, - line: &'a str, - prefix: &str, -) -> ParsedNameDirective<'a> { - if !line.as_bytes().starts_with(prefix.as_bytes()) { - return ParsedNameDirective::not_a_directive(); - } - if line.as_bytes().get(prefix.len()) != Some(&b'-') { - return ParsedNameDirective::not_a_directive(); - } - let line = &line[prefix.len() + 1..]; - - let (name, comment) = - line.split_once(&[':', ' ']).map(|(l, c)| (l, Some(c))).unwrap_or((line, None)); - - // Some of the matchers might be "" depending on what the target information is. To avoid - // problems we outright reject empty directives. - if name.is_empty() { - return ParsedNameDirective::not_a_directive(); - } - - let mut outcome = MatchOutcome::Invalid; - let mut message = None; - - macro_rules! condition { - ( - name: $name:expr, - $(allowed_names: $allowed_names:expr,)? - $(condition: $condition:expr,)? - message: $($message:tt)* - ) => {{ - // This is not inlined to avoid problems with macro repetitions. - let format_message = || format!($($message)*); - - if outcome != MatchOutcome::Invalid { - // Ignore all other matches if we already found one - } else if $name.custom_matches(name) { - message = Some(format_message()); - if true $(&& $condition)? { - outcome = MatchOutcome::Match; - } else { - outcome = MatchOutcome::NoMatch; - } - } - $(else if $allowed_names.custom_contains(name) { - message = Some(format_message()); - outcome = MatchOutcome::NoMatch; - })? - }}; - } - - let target_cfgs = config.target_cfgs(); - let target_cfg = config.target_cfg(); - - condition! { - name: "test", - message: "always" - } - condition! { - name: "auxiliary", - message: "used by another main test file" - } - condition! { - name: &config.target, - allowed_names: &target_cfgs.all_targets, - message: "when the target is {name}" - } - condition! { - name: &[ - Some(&*target_cfg.os), - // If something is ignored for emscripten, it likely also needs to be - // ignored for wasm32-unknown-unknown. - (config.target == "wasm32-unknown-unknown").then_some("emscripten"), - ], - allowed_names: &target_cfgs.all_oses, - message: "when the operating system is {name}" - } - condition! { - name: &target_cfg.env, - allowed_names: &target_cfgs.all_envs, - message: "when the target environment is {name}" - } - condition! { - name: &target_cfg.os_and_env(), - allowed_names: &target_cfgs.all_oses_and_envs, - message: "when the operating system and target environment are {name}" - } - condition! { - name: &target_cfg.abi, - allowed_names: &target_cfgs.all_abis, - message: "when the ABI is {name}" - } - condition! { - name: &target_cfg.arch, - allowed_names: ContainsEither { a: &target_cfgs.all_archs, b: &EXTRA_ARCHS }, - message: "when the architecture is {name}" - } - condition! { - name: format!("{}bit", target_cfg.pointer_width), - allowed_names: &target_cfgs.all_pointer_widths, - message: "when the pointer width is {name}" - } - condition! { - name: &*target_cfg.families, - allowed_names: &target_cfgs.all_families, - message: "when the target family is {name}" - } - - // `wasm32-bare` is an alias to refer to just wasm32-unknown-unknown - // (in contrast to `wasm32` which also matches non-bare targets) - condition! { - name: "wasm32-bare", - condition: config.target == "wasm32-unknown-unknown", - message: "when the target is WASM" - } - - condition! { - name: "thumb", - condition: config.target.starts_with("thumb"), - message: "when the architecture is part of the Thumb family" - } - - condition! { - name: "apple", - condition: config.target.contains("apple"), - message: "when the target vendor is Apple" - } - - condition! { - name: "elf", - condition: !config.target.contains("windows") - && !config.target.contains("wasm") - && !config.target.contains("apple") - && !config.target.contains("aix") - && !config.target.contains("uefi"), - message: "when the target binary format is ELF" - } - - condition! { - name: "enzyme", - condition: config.has_enzyme, - message: "when rustc is built with LLVM Enzyme" - } - - // Technically the locally built compiler uses the "dev" channel rather than the "nightly" - // channel, even though most people don't know or won't care about it. To avoid confusion, we - // treat the "dev" channel as the "nightly" channel when processing the directive. - condition! { - name: if config.channel == "dev" { "nightly" } else { &config.channel }, - allowed_names: &["stable", "beta", "nightly"], - message: "when the release channel is {name}", - } - - condition! { - name: "cross-compile", - condition: config.target != config.host, - message: "when cross-compiling" - } - condition! { - name: "endian-big", - condition: config.is_big_endian(), - message: "on big-endian targets", - } - condition! { - name: format!("stage{}", config.stage).as_str(), - allowed_names: &["stage0", "stage1", "stage2"], - message: "when the bootstrapping stage is {name}", - } - condition! { - name: "remote", - condition: config.remote_test_client.is_some(), - message: "when running tests remotely", - } - condition! { - name: "rustc-debug-assertions", - condition: config.with_rustc_debug_assertions, - message: "when rustc is built with debug assertions", - } - condition! { - name: "std-debug-assertions", - condition: config.with_std_debug_assertions, - message: "when std is built with debug assertions", - } - condition! { - name: config.debugger.as_ref().map(|d| d.to_str()), - allowed_names: &Debugger::STR_VARIANTS, - message: "when the debugger is {name}", - } - condition! { - name: config.compare_mode - .as_ref() - .map(|d| format!("compare-mode-{}", d.to_str())), - allowed_names: ContainsPrefixed { - prefix: "compare-mode-", - inner: CompareMode::STR_VARIANTS, - }, - message: "when comparing with {name}", - } - // Coverage tests run the same test file in multiple modes. - // If a particular test should not be run in one of the modes, ignore it - // with "ignore-coverage-map" or "ignore-coverage-run". - condition! { - name: config.mode.to_str(), - allowed_names: ["coverage-map", "coverage-run"], - message: "when the test mode is {name}", - } - condition! { - name: target_cfg.rustc_abi.as_ref().map(|abi| format!("rustc_abi-{abi}")).unwrap_or_default(), - allowed_names: ContainsPrefixed { - prefix: "rustc_abi-", - inner: target_cfgs.all_rustc_abis.clone(), - }, - message: "when the target `rustc_abi` is {name}", - } - - condition! { - name: "dist", - condition: std::env::var("COMPILETEST_ENABLE_DIST_TESTS") == Ok("1".to_string()), - message: "when performing tests on dist toolchain" - } - - if prefix == "ignore" && outcome == MatchOutcome::Invalid { - // Don't error out for ignore-tidy-* diretives, as those are not handled by compiletest. - if name.starts_with("tidy-") { - outcome = MatchOutcome::External; - } - - // Don't error out for ignore-pass, as that is handled elsewhere. - if name == "pass" { - outcome = MatchOutcome::External; - } - - // Don't error out for ignore-llvm-version, that has a custom syntax and is handled - // elsewhere. - if name == "llvm-version" { - outcome = MatchOutcome::External; - } - - // Don't error out for ignore-llvm-version, that has a custom syntax and is handled - // elsewhere. - if name == "gdb-version" { - outcome = MatchOutcome::External; - } - } - - ParsedNameDirective { - name: Some(name), - comment: comment.map(|c| c.trim().trim_start_matches('-').trim()), - outcome, - pretty_reason: message, - } -} - -/// The result of parse_cfg_name_directive. -#[derive(Clone, PartialEq, Debug)] -pub(super) struct ParsedNameDirective<'a> { - pub(super) name: Option<&'a str>, - pub(super) pretty_reason: Option, - pub(super) comment: Option<&'a str>, - pub(super) outcome: MatchOutcome, -} - -impl ParsedNameDirective<'_> { - fn not_a_directive() -> Self { - Self { - name: None, - pretty_reason: None, - comment: None, - outcome: MatchOutcome::NotADirective, - } - } -} - -#[derive(Clone, Copy, PartialEq, Debug)] -pub(super) enum MatchOutcome { - /// No match. - NoMatch, - /// Match. - Match, - /// The directive was invalid. - Invalid, - /// The directive is handled by other parts of our tooling. - External, - /// The line is not actually a directive. - NotADirective, -} - -trait CustomContains { - fn custom_contains(&self, item: &str) -> bool; -} - -impl CustomContains for HashSet { - fn custom_contains(&self, item: &str) -> bool { - self.contains(item) - } -} - -impl CustomContains for &[&str] { - fn custom_contains(&self, item: &str) -> bool { - self.contains(&item) - } -} - -impl CustomContains for [&str; N] { - fn custom_contains(&self, item: &str) -> bool { - self.contains(&item) - } -} - -struct ContainsPrefixed { - prefix: &'static str, - inner: T, -} - -impl CustomContains for ContainsPrefixed { - fn custom_contains(&self, item: &str) -> bool { - match item.strip_prefix(self.prefix) { - Some(stripped) => self.inner.custom_contains(stripped), - None => false, - } - } -} - -struct ContainsEither<'a, A: CustomContains, B: CustomContains> { - a: &'a A, - b: &'a B, -} - -impl CustomContains for ContainsEither<'_, A, B> { - fn custom_contains(&self, item: &str) -> bool { - self.a.custom_contains(item) || self.b.custom_contains(item) - } -} - -trait CustomMatches { - fn custom_matches(&self, name: &str) -> bool; -} - -impl CustomMatches for &str { - fn custom_matches(&self, name: &str) -> bool { - name == *self - } -} - -impl CustomMatches for String { - fn custom_matches(&self, name: &str) -> bool { - name == self - } -} - -impl CustomMatches for &[T] { - fn custom_matches(&self, name: &str) -> bool { - self.iter().any(|m| m.custom_matches(name)) - } -} - -impl CustomMatches for [T; N] { - fn custom_matches(&self, name: &str) -> bool { - self.iter().any(|m| m.custom_matches(name)) - } -} - -impl CustomMatches for Option { - fn custom_matches(&self, name: &str) -> bool { - match self { - Some(inner) => inner.custom_matches(name), - None => false, - } - } -} diff --git a/src/tools/compiletest/src/header/needs.rs b/src/tools/compiletest/src/header/needs.rs deleted file mode 100644 index b1165f4bb18..00000000000 --- a/src/tools/compiletest/src/header/needs.rs +++ /dev/null @@ -1,428 +0,0 @@ -use crate::common::{Config, KNOWN_CRATE_TYPES, KNOWN_TARGET_HAS_ATOMIC_WIDTHS, Sanitizer}; -use crate::header::{IgnoreDecision, llvm_has_libzstd}; - -pub(super) fn handle_needs( - cache: &CachedNeedsConditions, - config: &Config, - ln: &str, -) -> IgnoreDecision { - // Note that we intentionally still put the needs- prefix here to make the file show up when - // grepping for a directive name, even though we could technically strip that. - let needs = &[ - Need { - name: "needs-asm-support", - condition: config.has_asm_support(), - ignore_reason: "ignored on targets without inline assembly support", - }, - Need { - name: "needs-sanitizer-support", - condition: cache.sanitizer_support, - ignore_reason: "ignored on targets without sanitizers support", - }, - Need { - name: "needs-sanitizer-address", - condition: cache.sanitizer_address, - ignore_reason: "ignored on targets without address sanitizer", - }, - Need { - name: "needs-sanitizer-cfi", - condition: cache.sanitizer_cfi, - ignore_reason: "ignored on targets without CFI sanitizer", - }, - Need { - name: "needs-sanitizer-dataflow", - condition: cache.sanitizer_dataflow, - ignore_reason: "ignored on targets without dataflow sanitizer", - }, - Need { - name: "needs-sanitizer-kcfi", - condition: cache.sanitizer_kcfi, - ignore_reason: "ignored on targets without kernel CFI sanitizer", - }, - Need { - name: "needs-sanitizer-kasan", - condition: cache.sanitizer_kasan, - ignore_reason: "ignored on targets without kernel address sanitizer", - }, - Need { - name: "needs-sanitizer-leak", - condition: cache.sanitizer_leak, - ignore_reason: "ignored on targets without leak sanitizer", - }, - Need { - name: "needs-sanitizer-memory", - condition: cache.sanitizer_memory, - ignore_reason: "ignored on targets without memory sanitizer", - }, - Need { - name: "needs-sanitizer-thread", - condition: cache.sanitizer_thread, - ignore_reason: "ignored on targets without thread sanitizer", - }, - Need { - name: "needs-sanitizer-hwaddress", - condition: cache.sanitizer_hwaddress, - ignore_reason: "ignored on targets without hardware-assisted address sanitizer", - }, - Need { - name: "needs-sanitizer-memtag", - condition: cache.sanitizer_memtag, - ignore_reason: "ignored on targets without memory tagging sanitizer", - }, - Need { - name: "needs-sanitizer-shadow-call-stack", - condition: cache.sanitizer_shadow_call_stack, - ignore_reason: "ignored on targets without shadow call stacks", - }, - Need { - name: "needs-sanitizer-safestack", - condition: cache.sanitizer_safestack, - ignore_reason: "ignored on targets without SafeStack support", - }, - Need { - name: "needs-enzyme", - condition: config.has_enzyme, - ignore_reason: "ignored when LLVM Enzyme is disabled", - }, - Need { - name: "needs-run-enabled", - condition: config.run_enabled(), - ignore_reason: "ignored when running the resulting test binaries is disabled", - }, - Need { - name: "needs-threads", - condition: config.has_threads(), - ignore_reason: "ignored on targets without threading support", - }, - Need { - name: "needs-subprocess", - condition: config.has_subprocess_support(), - ignore_reason: "ignored on targets without subprocess support", - }, - Need { - name: "needs-unwind", - condition: config.can_unwind(), - ignore_reason: "ignored on targets without unwinding support", - }, - Need { - name: "needs-profiler-runtime", - condition: config.profiler_runtime, - ignore_reason: "ignored when the profiler runtime is not available", - }, - Need { - name: "needs-force-clang-based-tests", - condition: config.run_clang_based_tests_with.is_some(), - ignore_reason: "ignored when RUSTBUILD_FORCE_CLANG_BASED_TESTS is not set", - }, - Need { - name: "needs-xray", - condition: cache.xray, - ignore_reason: "ignored on targets without xray tracing", - }, - Need { - name: "needs-rust-lld", - condition: cache.rust_lld, - ignore_reason: "ignored on targets without Rust's LLD", - }, - Need { - name: "needs-dlltool", - condition: cache.dlltool, - ignore_reason: "ignored when dlltool for the current architecture is not present", - }, - Need { - name: "needs-git-hash", - condition: config.git_hash, - ignore_reason: "ignored when git hashes have been omitted for building", - }, - Need { - name: "needs-dynamic-linking", - condition: config.target_cfg().dynamic_linking, - ignore_reason: "ignored on targets without dynamic linking", - }, - Need { - name: "needs-relocation-model-pic", - condition: config.target_cfg().relocation_model == "pic", - ignore_reason: "ignored on targets without PIC relocation model", - }, - Need { - name: "needs-deterministic-layouts", - condition: !config.rust_randomized_layout, - ignore_reason: "ignored when randomizing layouts", - }, - Need { - name: "needs-wasmtime", - condition: config.runner.as_ref().is_some_and(|r| r.contains("wasmtime")), - ignore_reason: "ignored when wasmtime runner is not available", - }, - Need { - name: "needs-symlink", - condition: cache.symlinks, - ignore_reason: "ignored if symlinks are unavailable", - }, - Need { - name: "needs-llvm-zstd", - condition: cache.llvm_zstd, - ignore_reason: "ignored if LLVM wasn't build with zstd for ELF section compression", - }, - Need { - name: "needs-rustc-debug-assertions", - condition: config.with_rustc_debug_assertions, - ignore_reason: "ignored if rustc wasn't built with debug assertions", - }, - Need { - name: "needs-std-debug-assertions", - condition: config.with_std_debug_assertions, - ignore_reason: "ignored if std wasn't built with debug assertions", - }, - Need { - name: "needs-target-std", - condition: build_helper::targets::target_supports_std(&config.target), - ignore_reason: "ignored if target does not support std", - }, - ]; - - let (name, rest) = match ln.split_once([':', ' ']) { - Some((name, rest)) => (name, Some(rest)), - None => (ln, None), - }; - - // FIXME(jieyouxu): tighten up this parsing to reject using both `:` and ` ` as means to - // delineate value. - if name == "needs-target-has-atomic" { - let Some(rest) = rest else { - return IgnoreDecision::Error { - message: "expected `needs-target-has-atomic` to have a comma-separated list of atomic widths".to_string(), - }; - }; - - // Expect directive value to be a list of comma-separated atomic widths. - let specified_widths = rest - .split(',') - .map(|width| width.trim()) - .map(ToString::to_string) - .collect::>(); - - for width in &specified_widths { - if !KNOWN_TARGET_HAS_ATOMIC_WIDTHS.contains(&width.as_str()) { - return IgnoreDecision::Error { - message: format!( - "unknown width specified in `needs-target-has-atomic`: `{width}` is not a \ - known `target_has_atomic_width`, known values are `{:?}`", - KNOWN_TARGET_HAS_ATOMIC_WIDTHS - ), - }; - } - } - - let satisfies_all_specified_widths = specified_widths - .iter() - .all(|specified| config.target_cfg().target_has_atomic.contains(specified)); - if satisfies_all_specified_widths { - return IgnoreDecision::Continue; - } else { - return IgnoreDecision::Ignore { - reason: format!( - "skipping test as target does not support all of the required `target_has_atomic` widths `{:?}`", - specified_widths - ), - }; - } - } - - // FIXME(jieyouxu): share multi-value directive logic with `needs-target-has-atomic` above. - if name == "needs-crate-type" { - let Some(rest) = rest else { - return IgnoreDecision::Error { - message: - "expected `needs-crate-type` to have a comma-separated list of crate types" - .to_string(), - }; - }; - - // Expect directive value to be a list of comma-separated crate-types. - let specified_crate_types = rest - .split(',') - .map(|crate_type| crate_type.trim()) - .map(ToString::to_string) - .collect::>(); - - for crate_type in &specified_crate_types { - if !KNOWN_CRATE_TYPES.contains(&crate_type.as_str()) { - return IgnoreDecision::Error { - message: format!( - "unknown crate type specified in `needs-crate-type`: `{crate_type}` is not \ - a known crate type, known values are `{:?}`", - KNOWN_CRATE_TYPES - ), - }; - } - } - - let satisfies_all_crate_types = specified_crate_types - .iter() - .all(|specified| config.supported_crate_types().contains(specified)); - if satisfies_all_crate_types { - return IgnoreDecision::Continue; - } else { - return IgnoreDecision::Ignore { - reason: format!( - "skipping test as target does not support all of the crate types `{:?}`", - specified_crate_types - ), - }; - } - } - - if !name.starts_with("needs-") { - return IgnoreDecision::Continue; - } - - // Handled elsewhere. - if name == "needs-llvm-components" { - return IgnoreDecision::Continue; - } - - let mut found_valid = false; - for need in needs { - if need.name == name { - if need.condition { - found_valid = true; - break; - } else { - return IgnoreDecision::Ignore { - reason: if let Some(comment) = rest { - format!("{} ({})", need.ignore_reason, comment.trim()) - } else { - need.ignore_reason.into() - }, - }; - } - } - } - - if found_valid { - IgnoreDecision::Continue - } else { - IgnoreDecision::Error { message: format!("invalid needs directive: {name}") } - } -} - -struct Need { - name: &'static str, - condition: bool, - ignore_reason: &'static str, -} - -pub(super) struct CachedNeedsConditions { - sanitizer_support: bool, - sanitizer_address: bool, - sanitizer_cfi: bool, - sanitizer_dataflow: bool, - sanitizer_kcfi: bool, - sanitizer_kasan: bool, - sanitizer_leak: bool, - sanitizer_memory: bool, - sanitizer_thread: bool, - sanitizer_hwaddress: bool, - sanitizer_memtag: bool, - sanitizer_shadow_call_stack: bool, - sanitizer_safestack: bool, - xray: bool, - rust_lld: bool, - dlltool: bool, - symlinks: bool, - /// Whether LLVM built with zstd, for the `needs-llvm-zstd` directive. - llvm_zstd: bool, -} - -impl CachedNeedsConditions { - pub(super) fn load(config: &Config) -> Self { - let target = &&*config.target; - let sanitizers = &config.target_cfg().sanitizers; - Self { - sanitizer_support: std::env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(), - sanitizer_address: sanitizers.contains(&Sanitizer::Address), - sanitizer_cfi: sanitizers.contains(&Sanitizer::Cfi), - sanitizer_dataflow: sanitizers.contains(&Sanitizer::Dataflow), - sanitizer_kcfi: sanitizers.contains(&Sanitizer::Kcfi), - sanitizer_kasan: sanitizers.contains(&Sanitizer::KernelAddress), - sanitizer_leak: sanitizers.contains(&Sanitizer::Leak), - sanitizer_memory: sanitizers.contains(&Sanitizer::Memory), - sanitizer_thread: sanitizers.contains(&Sanitizer::Thread), - sanitizer_hwaddress: sanitizers.contains(&Sanitizer::Hwaddress), - sanitizer_memtag: sanitizers.contains(&Sanitizer::Memtag), - sanitizer_shadow_call_stack: sanitizers.contains(&Sanitizer::ShadowCallStack), - sanitizer_safestack: sanitizers.contains(&Sanitizer::Safestack), - xray: config.target_cfg().xray, - - // For tests using the `needs-rust-lld` directive (e.g. for `-Clink-self-contained=+linker`), - // we need to find whether `rust-lld` is present in the compiler under test. - // - // The --compile-lib-path is the path to host shared libraries, but depends on the OS. For - // example: - // - on linux, it can be /lib - // - on windows, it can be /bin - // - // However, `rust-lld` is only located under the lib path, so we look for it there. - rust_lld: config - .compile_lib_path - .parent() - .expect("couldn't traverse to the parent of the specified --compile-lib-path") - .join("lib") - .join("rustlib") - .join(target) - .join("bin") - .join(if config.host.contains("windows") { "rust-lld.exe" } else { "rust-lld" }) - .exists(), - - llvm_zstd: llvm_has_libzstd(&config), - dlltool: find_dlltool(&config), - symlinks: has_symlinks(), - } - } -} - -fn find_dlltool(config: &Config) -> bool { - let path = std::env::var_os("PATH").expect("missing PATH environment variable"); - let path = std::env::split_paths(&path).collect::>(); - - // dlltool is used ony by GNU based `*-*-windows-gnu` - if !(config.matches_os("windows") && config.matches_env("gnu") && config.matches_abi("")) { - return false; - } - - // On Windows, dlltool.exe is used for all architectures. - // For non-Windows, there are architecture specific dlltool binaries. - let dlltool_found = if cfg!(windows) { - path.iter().any(|dir| dir.join("dlltool.exe").is_file()) - } else if config.matches_arch("i686") { - path.iter().any(|dir| dir.join("i686-w64-mingw32-dlltool").is_file()) - } else if config.matches_arch("x86_64") { - path.iter().any(|dir| dir.join("x86_64-w64-mingw32-dlltool").is_file()) - } else { - false - }; - dlltool_found -} - -// FIXME(#135928): this is actually not quite right because this detection is run on the **host**. -// This however still helps the case of windows -> windows local development in case symlinks are -// not available. -#[cfg(windows)] -fn has_symlinks() -> bool { - if std::env::var_os("CI").is_some() { - return true; - } - let link = std::env::temp_dir().join("RUST_COMPILETEST_SYMLINK_CHECK"); - if std::os::windows::fs::symlink_file("DOES NOT EXIST", &link).is_ok() { - std::fs::remove_file(&link).unwrap(); - true - } else { - false - } -} - -#[cfg(not(windows))] -fn has_symlinks() -> bool { - true -} diff --git a/src/tools/compiletest/src/header/test-auxillary/error_annotation.rs b/src/tools/compiletest/src/header/test-auxillary/error_annotation.rs deleted file mode 100644 index fea66a5e07b..00000000000 --- a/src/tools/compiletest/src/header/test-auxillary/error_annotation.rs +++ /dev/null @@ -1,6 +0,0 @@ -//@ check-pass - -//~ HELP -fn main() {} //~ERROR -//~^ ERROR -//~| ERROR diff --git a/src/tools/compiletest/src/header/test-auxillary/known_directive.rs b/src/tools/compiletest/src/header/test-auxillary/known_directive.rs deleted file mode 100644 index 99834b14c1e..00000000000 --- a/src/tools/compiletest/src/header/test-auxillary/known_directive.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! ignore-wasm -//@ ignore-wasm -//@ check-pass -// regular comment diff --git a/src/tools/compiletest/src/header/test-auxillary/not_rs.Makefile b/src/tools/compiletest/src/header/test-auxillary/not_rs.Makefile deleted file mode 100644 index 4b565e0e6df..00000000000 --- a/src/tools/compiletest/src/header/test-auxillary/not_rs.Makefile +++ /dev/null @@ -1 +0,0 @@ -# ignore-owo diff --git a/src/tools/compiletest/src/header/test-auxillary/unknown_directive.rs b/src/tools/compiletest/src/header/test-auxillary/unknown_directive.rs deleted file mode 100644 index d4406031043..00000000000 --- a/src/tools/compiletest/src/header/test-auxillary/unknown_directive.rs +++ /dev/null @@ -1 +0,0 @@ -//@ needs-headpat diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs deleted file mode 100644 index 31b49b09bcd..00000000000 --- a/src/tools/compiletest/src/header/tests.rs +++ /dev/null @@ -1,958 +0,0 @@ -use std::io::Read; - -use camino::Utf8Path; -use semver::Version; - -use super::{ - EarlyProps, HeadersCache, extract_llvm_version, extract_version_range, iter_header, - parse_normalize_rule, -}; -use crate::common::{Config, Debugger, Mode}; -use crate::executor::{CollectedTestDesc, ShouldPanic}; - -fn make_test_description( - config: &Config, - name: String, - path: &Utf8Path, - src: R, - revision: Option<&str>, -) -> CollectedTestDesc { - let cache = HeadersCache::load(config); - let mut poisoned = false; - let test = crate::header::make_test_description( - config, - &cache, - name, - path, - src, - revision, - &mut poisoned, - ); - if poisoned { - panic!("poisoned!"); - } - test -} - -#[test] -fn test_parse_normalize_rule() { - let good_data = &[ - ( - r#""something (32 bits)" -> "something ($WORD bits)""#, - "something (32 bits)", - "something ($WORD bits)", - ), - (r#" " with whitespace" -> " replacement""#, " with whitespace", " replacement"), - ]; - - for &(input, expected_regex, expected_replacement) in good_data { - let parsed = parse_normalize_rule(input); - let parsed = - parsed.as_ref().map(|(regex, replacement)| (regex.as_str(), replacement.as_str())); - assert_eq!(parsed, Some((expected_regex, expected_replacement))); - } - - let bad_data = &[ - r#"something (11 bits) -> something ($WORD bits)"#, - r#"something (12 bits) -> something ($WORD bits)"#, - r#""something (13 bits) -> something ($WORD bits)"#, - r#""something (14 bits)" -> "something ($WORD bits)"#, - r#""something (15 bits)" -> "something ($WORD bits)"."#, - ]; - - for &input in bad_data { - println!("- {input:?}"); - let parsed = parse_normalize_rule(input); - assert_eq!(parsed, None); - } -} - -#[derive(Default)] -struct ConfigBuilder { - mode: Option, - channel: Option, - host: Option, - target: Option, - stage: Option, - stage_id: Option, - llvm_version: Option, - git_hash: bool, - system_llvm: bool, - profiler_runtime: bool, - rustc_debug_assertions: bool, - std_debug_assertions: bool, -} - -impl ConfigBuilder { - fn mode(&mut self, s: &str) -> &mut Self { - self.mode = Some(s.to_owned()); - self - } - - fn channel(&mut self, s: &str) -> &mut Self { - self.channel = Some(s.to_owned()); - self - } - - fn host(&mut self, s: &str) -> &mut Self { - self.host = Some(s.to_owned()); - self - } - - fn target(&mut self, s: &str) -> &mut Self { - self.target = Some(s.to_owned()); - self - } - - fn stage(&mut self, n: u32) -> &mut Self { - self.stage = Some(n); - self - } - - fn stage_id(&mut self, s: &str) -> &mut Self { - self.stage_id = Some(s.to_owned()); - self - } - - fn llvm_version(&mut self, s: &str) -> &mut Self { - self.llvm_version = Some(s.to_owned()); - self - } - - fn git_hash(&mut self, b: bool) -> &mut Self { - self.git_hash = b; - self - } - - fn system_llvm(&mut self, s: bool) -> &mut Self { - self.system_llvm = s; - self - } - - fn profiler_runtime(&mut self, is_available: bool) -> &mut Self { - self.profiler_runtime = is_available; - self - } - - fn rustc_debug_assertions(&mut self, is_enabled: bool) -> &mut Self { - self.rustc_debug_assertions = is_enabled; - self - } - - fn std_debug_assertions(&mut self, is_enabled: bool) -> &mut Self { - self.std_debug_assertions = is_enabled; - self - } - - fn build(&mut self) -> Config { - let args = &[ - "compiletest", - "--mode", - self.mode.as_deref().unwrap_or("ui"), - "--suite=ui", - "--compile-lib-path=", - "--run-lib-path=", - "--python=", - "--jsondocck-path=", - "--src-root=", - "--src-test-suite-root=", - "--build-root=", - "--build-test-suite-root=", - "--sysroot-base=", - "--cc=c", - "--cxx=c++", - "--cflags=", - "--cxxflags=", - "--llvm-components=", - "--android-cross-path=", - "--stage", - &self.stage.unwrap_or(2).to_string(), - "--stage-id", - self.stage_id.as_deref().unwrap_or("stage2-x86_64-unknown-linux-gnu"), - "--channel", - self.channel.as_deref().unwrap_or("nightly"), - "--host", - self.host.as_deref().unwrap_or("x86_64-unknown-linux-gnu"), - "--target", - self.target.as_deref().unwrap_or("x86_64-unknown-linux-gnu"), - "--nightly-branch=", - "--git-merge-commit-email=", - "--minicore-path=", - ]; - let mut args: Vec = args.iter().map(ToString::to_string).collect(); - - if let Some(ref llvm_version) = self.llvm_version { - args.push("--llvm-version".to_owned()); - args.push(llvm_version.clone()); - } - - if self.git_hash { - args.push("--git-hash".to_owned()); - } - if self.system_llvm { - args.push("--system-llvm".to_owned()); - } - if self.profiler_runtime { - args.push("--profiler-runtime".to_owned()); - } - if self.rustc_debug_assertions { - args.push("--with-rustc-debug-assertions".to_owned()); - } - if self.std_debug_assertions { - args.push("--with-std-debug-assertions".to_owned()); - } - - args.push("--rustc-path".to_string()); - // This is a subtle/fragile thing. On rust-lang CI, there is no global - // `rustc`, and Cargo doesn't offer a convenient way to get the path to - // `rustc`. Fortunately bootstrap sets `RUSTC` for us, which is pointing - // to the stage0 compiler. - // - // Otherwise, if you are running compiletests's tests manually, you - // probably don't have `RUSTC` set, in which case this falls back to the - // global rustc. If your global rustc is too far out of sync with stage0, - // then this may cause confusing errors. Or if for some reason you don't - // have rustc in PATH, that would also fail. - args.push(std::env::var("RUSTC").unwrap_or_else(|_| { - eprintln!( - "warning: RUSTC not set, using global rustc (are you not running via bootstrap?)" - ); - "rustc".to_string() - })); - crate::parse_config(args) - } -} - -fn cfg() -> ConfigBuilder { - ConfigBuilder::default() -} - -fn parse_rs(config: &Config, contents: &str) -> EarlyProps { - let bytes = contents.as_bytes(); - EarlyProps::from_reader(config, Utf8Path::new("a.rs"), bytes) -} - -fn check_ignore(config: &Config, contents: &str) -> bool { - let tn = String::new(); - let p = Utf8Path::new("a.rs"); - let d = make_test_description(&config, tn, p, std::io::Cursor::new(contents), None); - d.ignore -} - -#[test] -fn should_fail() { - let config: Config = cfg().build(); - let tn = String::new(); - let p = Utf8Path::new("a.rs"); - - let d = make_test_description(&config, tn.clone(), p, std::io::Cursor::new(""), None); - assert_eq!(d.should_panic, ShouldPanic::No); - let d = make_test_description(&config, tn, p, std::io::Cursor::new("//@ should-fail"), None); - assert_eq!(d.should_panic, ShouldPanic::Yes); -} - -#[test] -fn revisions() { - let config: Config = cfg().build(); - - assert_eq!(parse_rs(&config, "//@ revisions: a b c").revisions, vec!["a", "b", "c"],); -} - -#[test] -fn aux_build() { - let config: Config = cfg().build(); - - assert_eq!( - parse_rs( - &config, - r" - //@ aux-build: a.rs - //@ aux-build: b.rs - " - ) - .aux - .builds, - vec!["a.rs", "b.rs"], - ); -} - -#[test] -fn llvm_version() { - let config: Config = cfg().llvm_version("8.1.2").build(); - assert!(check_ignore(&config, "//@ min-llvm-version: 9.0")); - - let config: Config = cfg().llvm_version("9.0.1").build(); - assert!(check_ignore(&config, "//@ min-llvm-version: 9.2")); - - let config: Config = cfg().llvm_version("9.3.1").build(); - assert!(!check_ignore(&config, "//@ min-llvm-version: 9.2")); - - let config: Config = cfg().llvm_version("10.0.0").build(); - assert!(!check_ignore(&config, "//@ min-llvm-version: 9.0")); - - let config: Config = cfg().llvm_version("10.0.0").build(); - assert!(check_ignore(&config, "//@ exact-llvm-major-version: 9.0")); - - let config: Config = cfg().llvm_version("9.0.0").build(); - assert!(check_ignore(&config, "//@ exact-llvm-major-version: 10.0")); - - let config: Config = cfg().llvm_version("10.0.0").build(); - assert!(!check_ignore(&config, "//@ exact-llvm-major-version: 10.0")); - - let config: Config = cfg().llvm_version("10.0.0").build(); - assert!(!check_ignore(&config, "//@ exact-llvm-major-version: 10")); - - let config: Config = cfg().llvm_version("10.6.2").build(); - assert!(!check_ignore(&config, "//@ exact-llvm-major-version: 10")); - - let config: Config = cfg().llvm_version("19.0.0").build(); - assert!(!check_ignore(&config, "//@ max-llvm-major-version: 19")); - - let config: Config = cfg().llvm_version("19.1.2").build(); - assert!(!check_ignore(&config, "//@ max-llvm-major-version: 19")); - - let config: Config = cfg().llvm_version("20.0.0").build(); - assert!(check_ignore(&config, "//@ max-llvm-major-version: 19")); -} - -#[test] -fn system_llvm_version() { - let config: Config = cfg().system_llvm(true).llvm_version("17.0.0").build(); - assert!(check_ignore(&config, "//@ min-system-llvm-version: 18.0")); - - let config: Config = cfg().system_llvm(true).llvm_version("18.0.0").build(); - assert!(!check_ignore(&config, "//@ min-system-llvm-version: 18.0")); - - let config: Config = cfg().llvm_version("17.0.0").build(); - assert!(!check_ignore(&config, "//@ min-system-llvm-version: 18.0")); -} - -#[test] -fn ignore_target() { - let config: Config = cfg().target("x86_64-unknown-linux-gnu").build(); - - assert!(check_ignore(&config, "//@ ignore-x86_64-unknown-linux-gnu")); - assert!(check_ignore(&config, "//@ ignore-x86_64")); - assert!(check_ignore(&config, "//@ ignore-linux")); - assert!(check_ignore(&config, "//@ ignore-unix")); - assert!(check_ignore(&config, "//@ ignore-gnu")); - assert!(check_ignore(&config, "//@ ignore-64bit")); - - assert!(!check_ignore(&config, "//@ ignore-x86")); - assert!(!check_ignore(&config, "//@ ignore-windows")); - assert!(!check_ignore(&config, "//@ ignore-msvc")); - assert!(!check_ignore(&config, "//@ ignore-32bit")); -} - -#[test] -fn only_target() { - let config: Config = cfg().target("x86_64-pc-windows-gnu").build(); - - assert!(check_ignore(&config, "//@ only-x86")); - assert!(check_ignore(&config, "//@ only-linux")); - assert!(check_ignore(&config, "//@ only-unix")); - assert!(check_ignore(&config, "//@ only-msvc")); - assert!(check_ignore(&config, "//@ only-32bit")); - - assert!(!check_ignore(&config, "//@ only-x86_64-pc-windows-gnu")); - assert!(!check_ignore(&config, "//@ only-x86_64")); - assert!(!check_ignore(&config, "//@ only-windows")); - assert!(!check_ignore(&config, "//@ only-gnu")); - assert!(!check_ignore(&config, "//@ only-64bit")); -} - -#[test] -fn rustc_debug_assertions() { - let config: Config = cfg().rustc_debug_assertions(false).build(); - - assert!(check_ignore(&config, "//@ needs-rustc-debug-assertions")); - assert!(!check_ignore(&config, "//@ ignore-rustc-debug-assertions")); - - let config: Config = cfg().rustc_debug_assertions(true).build(); - - assert!(!check_ignore(&config, "//@ needs-rustc-debug-assertions")); - assert!(check_ignore(&config, "//@ ignore-rustc-debug-assertions")); -} - -#[test] -fn std_debug_assertions() { - let config: Config = cfg().std_debug_assertions(false).build(); - - assert!(check_ignore(&config, "//@ needs-std-debug-assertions")); - assert!(!check_ignore(&config, "//@ ignore-std-debug-assertions")); - - let config: Config = cfg().std_debug_assertions(true).build(); - - assert!(!check_ignore(&config, "//@ needs-std-debug-assertions")); - assert!(check_ignore(&config, "//@ ignore-std-debug-assertions")); -} - -#[test] -fn stage() { - let config: Config = cfg().stage(1).stage_id("stage1-x86_64-unknown-linux-gnu").build(); - - assert!(check_ignore(&config, "//@ ignore-stage1")); - assert!(!check_ignore(&config, "//@ ignore-stage2")); -} - -#[test] -fn cross_compile() { - let config: Config = cfg().host("x86_64-apple-darwin").target("wasm32-unknown-unknown").build(); - assert!(check_ignore(&config, "//@ ignore-cross-compile")); - - let config: Config = cfg().host("x86_64-apple-darwin").target("x86_64-apple-darwin").build(); - assert!(!check_ignore(&config, "//@ ignore-cross-compile")); -} - -#[test] -fn debugger() { - let mut config = cfg().build(); - config.debugger = None; - assert!(!check_ignore(&config, "//@ ignore-cdb")); - - config.debugger = Some(Debugger::Cdb); - assert!(check_ignore(&config, "//@ ignore-cdb")); - - config.debugger = Some(Debugger::Gdb); - assert!(check_ignore(&config, "//@ ignore-gdb")); - - config.debugger = Some(Debugger::Lldb); - assert!(check_ignore(&config, "//@ ignore-lldb")); -} - -#[test] -fn git_hash() { - let config: Config = cfg().git_hash(false).build(); - assert!(check_ignore(&config, "//@ needs-git-hash")); - - let config: Config = cfg().git_hash(true).build(); - assert!(!check_ignore(&config, "//@ needs-git-hash")); -} - -#[test] -fn sanitizers() { - // Target that supports all sanitizers: - let config: Config = cfg().target("x86_64-unknown-linux-gnu").build(); - assert!(!check_ignore(&config, "//@ needs-sanitizer-address")); - assert!(!check_ignore(&config, "//@ needs-sanitizer-leak")); - assert!(!check_ignore(&config, "//@ needs-sanitizer-memory")); - assert!(!check_ignore(&config, "//@ needs-sanitizer-thread")); - - // Target that doesn't support sanitizers: - let config: Config = cfg().target("wasm32-unknown-emscripten").build(); - assert!(check_ignore(&config, "//@ needs-sanitizer-address")); - assert!(check_ignore(&config, "//@ needs-sanitizer-leak")); - assert!(check_ignore(&config, "//@ needs-sanitizer-memory")); - assert!(check_ignore(&config, "//@ needs-sanitizer-thread")); -} - -#[test] -fn profiler_runtime() { - let config: Config = cfg().profiler_runtime(false).build(); - assert!(check_ignore(&config, "//@ needs-profiler-runtime")); - - let config: Config = cfg().profiler_runtime(true).build(); - assert!(!check_ignore(&config, "//@ needs-profiler-runtime")); -} - -#[test] -fn asm_support() { - let asms = [ - ("avr-none", false), - ("i686-unknown-netbsd", true), - ("riscv32gc-unknown-linux-gnu", true), - ("riscv64imac-unknown-none-elf", true), - ("x86_64-unknown-linux-gnu", true), - ("i686-unknown-netbsd", true), - ]; - for (target, has_asm) in asms { - let config = cfg().target(target).build(); - assert_eq!(config.has_asm_support(), has_asm); - assert_eq!(check_ignore(&config, "//@ needs-asm-support"), !has_asm) - } -} - -#[test] -fn channel() { - let config: Config = cfg().channel("beta").build(); - - assert!(check_ignore(&config, "//@ ignore-beta")); - assert!(check_ignore(&config, "//@ only-nightly")); - assert!(check_ignore(&config, "//@ only-stable")); - - assert!(!check_ignore(&config, "//@ only-beta")); - assert!(!check_ignore(&config, "//@ ignore-nightly")); - assert!(!check_ignore(&config, "//@ ignore-stable")); -} - -#[test] -fn test_extract_llvm_version() { - // Note: officially, semver *requires* that versions at the minimum have all three - // `major.minor.patch` numbers, though for test-writer's convenience we allow omitting the minor - // and patch numbers (which will be stubbed out as 0). - assert_eq!(extract_llvm_version("0"), Version::new(0, 0, 0)); - assert_eq!(extract_llvm_version("0.0"), Version::new(0, 0, 0)); - assert_eq!(extract_llvm_version("0.0.0"), Version::new(0, 0, 0)); - assert_eq!(extract_llvm_version("1"), Version::new(1, 0, 0)); - assert_eq!(extract_llvm_version("1.2"), Version::new(1, 2, 0)); - assert_eq!(extract_llvm_version("1.2.3"), Version::new(1, 2, 3)); - assert_eq!(extract_llvm_version("4.5.6git"), Version::new(4, 5, 6)); - assert_eq!(extract_llvm_version("4.5.6-rc1"), Version::new(4, 5, 6)); - assert_eq!(extract_llvm_version("123.456.789-rc1"), Version::new(123, 456, 789)); - assert_eq!(extract_llvm_version("8.1.2-rust"), Version::new(8, 1, 2)); - assert_eq!(extract_llvm_version("9.0.1-rust-1.43.0-dev"), Version::new(9, 0, 1)); - assert_eq!(extract_llvm_version("9.3.1-rust-1.43.0-dev"), Version::new(9, 3, 1)); - assert_eq!(extract_llvm_version("10.0.0-rust"), Version::new(10, 0, 0)); - assert_eq!(extract_llvm_version("11.1.0"), Version::new(11, 1, 0)); - assert_eq!(extract_llvm_version("12.0.0libcxx"), Version::new(12, 0, 0)); - assert_eq!(extract_llvm_version("12.0.0-rc3"), Version::new(12, 0, 0)); - assert_eq!(extract_llvm_version("13.0.0git"), Version::new(13, 0, 0)); -} - -#[test] -#[should_panic] -fn test_llvm_version_invalid_components() { - extract_llvm_version("4.x.6"); -} - -#[test] -#[should_panic] -fn test_llvm_version_invalid_prefix() { - extract_llvm_version("meow4.5.6"); -} - -#[test] -#[should_panic] -fn test_llvm_version_too_many_components() { - extract_llvm_version("4.5.6.7"); -} - -#[test] -fn test_extract_version_range() { - let wrapped_extract = |s: &str| Some(extract_llvm_version(s)); - - assert_eq!( - extract_version_range("1.2.3 - 4.5.6", wrapped_extract), - Some((Version::new(1, 2, 3), Version::new(4, 5, 6))) - ); - assert_eq!( - extract_version_range("0 - 4.5.6", wrapped_extract), - Some((Version::new(0, 0, 0), Version::new(4, 5, 6))) - ); - assert_eq!(extract_version_range("1.2.3 -", wrapped_extract), None); - assert_eq!(extract_version_range("1.2.3 - ", wrapped_extract), None); - assert_eq!(extract_version_range("- 4.5.6", wrapped_extract), None); - assert_eq!(extract_version_range("-", wrapped_extract), None); - assert_eq!(extract_version_range(" - 4.5.6", wrapped_extract), None); - assert_eq!(extract_version_range(" - 4.5.6", wrapped_extract), None); - assert_eq!(extract_version_range("0 -", wrapped_extract), None); -} - -#[test] -#[should_panic(expected = "duplicate revision: `rpass1` in line ` rpass1 rpass1`")] -fn test_duplicate_revisions() { - let config: Config = cfg().build(); - parse_rs(&config, "//@ revisions: rpass1 rpass1"); -} - -#[test] -#[should_panic( - expected = "revision name `CHECK` is not permitted in a test suite that uses `FileCheck` annotations" -)] -fn test_assembly_mode_forbidden_revisions() { - let config = cfg().mode("assembly").build(); - parse_rs(&config, "//@ revisions: CHECK"); -} - -#[test] -#[should_panic(expected = "revision name `true` is not permitted")] -fn test_forbidden_revisions() { - let config = cfg().mode("ui").build(); - parse_rs(&config, "//@ revisions: true"); -} - -#[test] -#[should_panic( - expected = "revision name `CHECK` is not permitted in a test suite that uses `FileCheck` annotations" -)] -fn test_codegen_mode_forbidden_revisions() { - let config = cfg().mode("codegen").build(); - parse_rs(&config, "//@ revisions: CHECK"); -} - -#[test] -#[should_panic( - expected = "revision name `CHECK` is not permitted in a test suite that uses `FileCheck` annotations" -)] -fn test_miropt_mode_forbidden_revisions() { - let config = cfg().mode("mir-opt").build(); - parse_rs(&config, "//@ revisions: CHECK"); -} - -#[test] -fn test_forbidden_revisions_allowed_in_non_filecheck_dir() { - let revisions = ["CHECK", "COM", "NEXT", "SAME", "EMPTY", "NOT", "COUNT", "DAG", "LABEL"]; - let modes = [ - "pretty", - "debuginfo", - "rustdoc", - "rustdoc-json", - "codegen-units", - "incremental", - "ui", - "rustdoc-js", - "coverage-map", - "coverage-run", - "crashes", - ]; - - for rev in revisions { - let content = format!("//@ revisions: {rev}"); - for mode in modes { - let config = cfg().mode(mode).build(); - parse_rs(&config, &content); - } - } -} - -#[test] -fn ignore_arch() { - let archs = [ - ("x86_64-unknown-linux-gnu", "x86_64"), - ("i686-unknown-linux-gnu", "x86"), - ("nvptx64-nvidia-cuda", "nvptx64"), - ("thumbv7m-none-eabi", "thumb"), - ]; - for (target, arch) in archs { - let config: Config = cfg().target(target).build(); - assert!(config.matches_arch(arch), "{target} {arch}"); - assert!(check_ignore(&config, &format!("//@ ignore-{arch}"))); - } -} - -#[test] -fn matches_os() { - let oss = [ - ("x86_64-unknown-linux-gnu", "linux"), - ("x86_64-fortanix-unknown-sgx", "unknown"), - ("wasm32-unknown-unknown", "unknown"), - ("x86_64-unknown-none", "none"), - ]; - for (target, os) in oss { - let config = cfg().target(target).build(); - assert!(config.matches_os(os), "{target} {os}"); - assert!(check_ignore(&config, &format!("//@ ignore-{os}"))); - } -} - -#[test] -fn matches_env() { - let envs = [ - ("x86_64-unknown-linux-gnu", "gnu"), - ("x86_64-fortanix-unknown-sgx", "sgx"), - ("arm-unknown-linux-musleabi", "musl"), - ]; - for (target, env) in envs { - let config: Config = cfg().target(target).build(); - assert!(config.matches_env(env), "{target} {env}"); - assert!(check_ignore(&config, &format!("//@ ignore-{env}"))); - } -} - -#[test] -fn matches_abi() { - let abis = [ - ("aarch64-apple-ios-macabi", "macabi"), - ("x86_64-unknown-linux-gnux32", "x32"), - ("arm-unknown-linux-gnueabi", "eabi"), - ]; - for (target, abi) in abis { - let config: Config = cfg().target(target).build(); - assert!(config.matches_abi(abi), "{target} {abi}"); - assert!(check_ignore(&config, &format!("//@ ignore-{abi}"))); - } -} - -#[test] -fn is_big_endian() { - let endians = [ - ("x86_64-unknown-linux-gnu", false), - ("bpfeb-unknown-none", true), - ("m68k-unknown-linux-gnu", true), - ("aarch64_be-unknown-linux-gnu", true), - ("powerpc64-unknown-linux-gnu", true), - ]; - for (target, is_big) in endians { - let config = cfg().target(target).build(); - assert_eq!(config.is_big_endian(), is_big, "{target} {is_big}"); - assert_eq!(check_ignore(&config, "//@ ignore-endian-big"), is_big); - } -} - -#[test] -fn pointer_width() { - let widths = [ - ("x86_64-unknown-linux-gnu", 64), - ("i686-unknown-linux-gnu", 32), - ("arm64_32-apple-watchos", 32), - ("msp430-none-elf", 16), - ]; - for (target, width) in widths { - let config: Config = cfg().target(target).build(); - assert_eq!(config.get_pointer_width(), width, "{target} {width}"); - assert_eq!(check_ignore(&config, "//@ ignore-16bit"), width == 16); - assert_eq!(check_ignore(&config, "//@ ignore-32bit"), width == 32); - assert_eq!(check_ignore(&config, "//@ ignore-64bit"), width == 64); - } -} - -#[test] -fn wasm_special() { - let ignores = [ - ("wasm32-unknown-unknown", "emscripten", true), - ("wasm32-unknown-unknown", "wasm32", true), - ("wasm32-unknown-unknown", "wasm32-bare", true), - ("wasm32-unknown-unknown", "wasm64", false), - ("wasm32-unknown-emscripten", "emscripten", true), - ("wasm32-unknown-emscripten", "wasm32", true), - ("wasm32-unknown-emscripten", "wasm32-bare", false), - ("wasm32-wasip1", "emscripten", false), - ("wasm32-wasip1", "wasm32", true), - ("wasm32-wasip1", "wasm32-bare", false), - ("wasm32-wasip1", "wasi", true), - ("wasm64-unknown-unknown", "emscripten", false), - ("wasm64-unknown-unknown", "wasm32", false), - ("wasm64-unknown-unknown", "wasm32-bare", false), - ("wasm64-unknown-unknown", "wasm64", true), - ]; - for (target, pattern, ignore) in ignores { - let config: Config = cfg().target(target).build(); - assert_eq!( - check_ignore(&config, &format!("//@ ignore-{pattern}")), - ignore, - "{target} {pattern}" - ); - } -} - -#[test] -fn families() { - let families = [ - ("x86_64-unknown-linux-gnu", "unix"), - ("x86_64-pc-windows-gnu", "windows"), - ("wasm32-unknown-unknown", "wasm"), - ("wasm32-unknown-emscripten", "wasm"), - ("wasm32-unknown-emscripten", "unix"), - ]; - for (target, family) in families { - let config: Config = cfg().target(target).build(); - assert!(config.matches_family(family)); - let other = if family == "windows" { "unix" } else { "windows" }; - assert!(!config.matches_family(other)); - assert!(check_ignore(&config, &format!("//@ ignore-{family}"))); - assert!(!check_ignore(&config, &format!("//@ ignore-{other}"))); - } -} - -#[test] -fn ignore_coverage() { - // Indicate profiler runtime availability so that "coverage-run" tests aren't skipped. - let config = cfg().mode("coverage-map").profiler_runtime(true).build(); - assert!(check_ignore(&config, "//@ ignore-coverage-map")); - assert!(!check_ignore(&config, "//@ ignore-coverage-run")); - - let config = cfg().mode("coverage-run").profiler_runtime(true).build(); - assert!(!check_ignore(&config, "//@ ignore-coverage-map")); - assert!(check_ignore(&config, "//@ ignore-coverage-run")); -} - -#[test] -fn threads_support() { - let threads = [ - ("x86_64-unknown-linux-gnu", true), - ("aarch64-apple-darwin", true), - ("wasm32-unknown-unknown", false), - ("wasm64-unknown-unknown", false), - ("wasm32-wasip1", false), - ("wasm32-wasip1-threads", true), - ]; - for (target, has_threads) in threads { - let config = cfg().target(target).build(); - assert_eq!(config.has_threads(), has_threads); - assert_eq!(check_ignore(&config, "//@ needs-threads"), !has_threads) - } -} - -fn run_path(poisoned: &mut bool, path: &Utf8Path, buf: &[u8]) { - let rdr = std::io::Cursor::new(&buf); - iter_header(Mode::Ui, "ui", poisoned, path, rdr, &mut |_| {}); -} - -#[test] -fn test_unknown_directive_check() { - let mut poisoned = false; - run_path( - &mut poisoned, - Utf8Path::new("a.rs"), - include_bytes!("./test-auxillary/unknown_directive.rs"), - ); - assert!(poisoned); -} - -#[test] -fn test_known_directive_check_no_error() { - let mut poisoned = false; - run_path( - &mut poisoned, - Utf8Path::new("a.rs"), - include_bytes!("./test-auxillary/known_directive.rs"), - ); - assert!(!poisoned); -} - -#[test] -fn test_error_annotation_no_error() { - let mut poisoned = false; - run_path( - &mut poisoned, - Utf8Path::new("a.rs"), - include_bytes!("./test-auxillary/error_annotation.rs"), - ); - assert!(!poisoned); -} - -#[test] -fn test_non_rs_unknown_directive_not_checked() { - let mut poisoned = false; - run_path( - &mut poisoned, - Utf8Path::new("a.Makefile"), - include_bytes!("./test-auxillary/not_rs.Makefile"), - ); - assert!(!poisoned); -} - -#[test] -fn test_trailing_directive() { - let mut poisoned = false; - run_path(&mut poisoned, Utf8Path::new("a.rs"), b"//@ only-x86 only-arm"); - assert!(poisoned); -} - -#[test] -fn test_trailing_directive_with_comment() { - let mut poisoned = false; - run_path(&mut poisoned, Utf8Path::new("a.rs"), b"//@ only-x86 only-arm with comment"); - assert!(poisoned); -} - -#[test] -fn test_not_trailing_directive() { - let mut poisoned = false; - run_path(&mut poisoned, Utf8Path::new("a.rs"), b"//@ revisions: incremental"); - assert!(!poisoned); -} - -#[test] -fn test_needs_target_has_atomic() { - use std::collections::BTreeSet; - - // `x86_64-unknown-linux-gnu` supports `["8", "16", "32", "64", "ptr"]` but not `128`. - - let config = cfg().target("x86_64-unknown-linux-gnu").build(); - // Expectation sanity check. - assert_eq!( - config.target_cfg().target_has_atomic, - BTreeSet::from([ - "8".to_string(), - "16".to_string(), - "32".to_string(), - "64".to_string(), - "ptr".to_string() - ]), - "expected `x86_64-unknown-linux-gnu` to not have 128-bit atomic support" - ); - - assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 8")); - assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 16")); - assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 32")); - assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 64")); - assert!(!check_ignore(&config, "//@ needs-target-has-atomic: ptr")); - - assert!(check_ignore(&config, "//@ needs-target-has-atomic: 128")); - - assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 8,16,32,64,ptr")); - - assert!(check_ignore(&config, "//@ needs-target-has-atomic: 8,16,32,64,ptr,128")); - - // Check whitespace between widths is permitted. - assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 8, ptr")); - assert!(check_ignore(&config, "//@ needs-target-has-atomic: 8, ptr, 128")); -} - -#[test] -fn test_rustc_abi() { - let config = cfg().target("i686-unknown-linux-gnu").build(); - assert_eq!(config.target_cfg().rustc_abi, Some("x86-sse2".to_string())); - assert!(check_ignore(&config, "//@ ignore-rustc_abi-x86-sse2")); - assert!(!check_ignore(&config, "//@ only-rustc_abi-x86-sse2")); - let config = cfg().target("x86_64-unknown-linux-gnu").build(); - assert_eq!(config.target_cfg().rustc_abi, None); - assert!(!check_ignore(&config, "//@ ignore-rustc_abi-x86-sse2")); - assert!(check_ignore(&config, "//@ only-rustc_abi-x86-sse2")); -} - -#[test] -fn test_supported_crate_types() { - // Basic assumptions check on under-test compiler's `--print=supported-crate-types` output based - // on knowledge about the cherry-picked `x86_64-unknown-linux-gnu` and `wasm32-unknown-unknown` - // targets. Also smoke tests the `needs-crate-type` directive itself. - - use std::collections::HashSet; - - let config = cfg().target("x86_64-unknown-linux-gnu").build(); - assert_eq!( - config.supported_crate_types().iter().map(String::as_str).collect::>(), - HashSet::from(["bin", "cdylib", "dylib", "lib", "proc-macro", "rlib", "staticlib"]), - ); - assert!(!check_ignore(&config, "//@ needs-crate-type: rlib")); - assert!(!check_ignore(&config, "//@ needs-crate-type: dylib")); - assert!(!check_ignore( - &config, - "//@ needs-crate-type: bin, cdylib, dylib, lib, proc-macro, rlib, staticlib" - )); - - let config = cfg().target("wasm32-unknown-unknown").build(); - assert_eq!( - config.supported_crate_types().iter().map(String::as_str).collect::>(), - HashSet::from(["bin", "cdylib", "lib", "rlib", "staticlib"]), - ); - - // rlib is supported - assert!(!check_ignore(&config, "//@ needs-crate-type: rlib")); - // dylib is not - assert!(check_ignore(&config, "//@ needs-crate-type: dylib")); - // If multiple crate types are specified, then all specified crate types need to be supported. - assert!(check_ignore(&config, "//@ needs-crate-type: cdylib, dylib")); - assert!(check_ignore( - &config, - "//@ needs-crate-type: bin, cdylib, dylib, lib, proc-macro, rlib, staticlib" - )); -} - -#[test] -fn test_ignore_auxiliary() { - let config = cfg().build(); - assert!(check_ignore(&config, "//@ ignore-auxiliary")); -} - -#[test] -fn test_needs_target_std() { - // Cherry-picks two targets: - // 1. `x86_64-unknown-none`: Tier 2, intentionally never supports std. - // 2. `x86_64-unknown-linux-gnu`: Tier 1, always supports std. - let config = cfg().target("x86_64-unknown-none").build(); - assert!(check_ignore(&config, "//@ needs-target-std")); - let config = cfg().target("x86_64-unknown-linux-gnu").build(); - assert!(!check_ignore(&config, "//@ needs-target-std")); -} diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index 09de3eb4c70..7f2b73eb81b 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -14,7 +14,7 @@ mod debuggers; pub mod diagnostics; pub mod errors; mod executor; -pub mod header; +pub mod directives; mod json; mod raise_fd_limit; mod read2; @@ -37,13 +37,13 @@ use rayon::iter::{ParallelBridge, ParallelIterator}; use tracing::debug; use walkdir::WalkDir; -use self::header::{EarlyProps, make_test_description}; +use self::directives::{EarlyProps, make_test_description}; use crate::common::{ CompareMode, Config, Debugger, Mode, PassMode, TestPaths, UI_EXTENSIONS, expected_output_path, output_base_dir, output_relative_path, }; use crate::executor::{CollectedTest, ColorConfig, OutputFormat}; -use crate::header::HeadersCache; +use crate::directives::HeadersCache; use crate::util::logv; /// Creates the `Config` instance for this invocation of compiletest. @@ -254,8 +254,8 @@ pub fn parse_config(args: Vec) -> Config { Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x), }; let llvm_version = - matches.opt_str("llvm-version").as_deref().map(header::extract_llvm_version).or_else( - || header::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?), + matches.opt_str("llvm-version").as_deref().map(directives::extract_llvm_version).or_else( + || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?), ); let run_ignored = matches.opt_present("ignored"); diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 53b5990d3cd..acc81dda4a8 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -24,7 +24,7 @@ use crate::common::{ }; use crate::compute_diff::{DiffLine, make_diff, write_diff, write_filtered_diff}; use crate::errors::{Error, ErrorKind, load_errors}; -use crate::header::TestProps; +use crate::directives::TestProps; use crate::read2::{Truncated, read2_abbreviated}; use crate::util::{Utf8PathBufExt, add_dylib_path, logv, static_regex}; use crate::{ColorConfig, help, json, stamp_file_path, warning}; diff --git a/src/tools/rustdoc-gui-test/src/main.rs b/src/tools/rustdoc-gui-test/src/main.rs index addb0af4a54..6461f38f527 100644 --- a/src/tools/rustdoc-gui-test/src/main.rs +++ b/src/tools/rustdoc-gui-test/src/main.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use std::{env, fs}; use build_helper::util::try_run; -use compiletest::header::TestProps; +use compiletest::directives::TestProps; use config::Config; mod config; -- cgit 1.4.1-3-g733a5