diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index 735351fbf605f..02382f8d4c649 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -11,13 +11,24 @@ use tracing::*; use crate::common::{Config, Debugger, FailMode, Mode, PassMode}; use crate::header::cfg::parse_cfg_name_directive; use crate::header::cfg::MatchOutcome; -use crate::util; +use crate::header::needs::CachedNeedsConditions; use crate::{extract_cdb_version, extract_gdb_version}; 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)] @@ -35,7 +46,7 @@ impl EarlyProps { pub fn from_reader(config: &Config, testfile: &Path, rdr: R) -> Self { let mut props = EarlyProps::default(); - iter_header(testfile, rdr, &mut |_, ln| { + iter_header(testfile, rdr, &mut |_, ln, _| { config.push_name_value_directive(ln, directives::AUX_BUILD, &mut props.aux, |r| { r.trim().to_string() }); @@ -283,7 +294,7 @@ impl TestProps { if !testfile.is_dir() { let file = File::open(testfile).unwrap(); - iter_header(testfile, file, &mut |revision, ln| { + iter_header(testfile, file, &mut |revision, ln, _| { if revision.is_some() && revision != cfg { return; } @@ -577,7 +588,7 @@ pub fn line_directive<'line>( } } -fn iter_header(testfile: &Path, rdr: R, it: &mut dyn FnMut(Option<&str>, &str)) { +fn iter_header(testfile: &Path, rdr: R, it: &mut dyn FnMut(Option<&str>, &str, usize)) { if testfile.is_dir() { return; } @@ -586,8 +597,10 @@ fn iter_header(testfile: &Path, rdr: R, it: &mut dyn FnMut(Option<&str> let mut rdr = BufReader::new(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; @@ -600,7 +613,7 @@ fn iter_header(testfile: &Path, rdr: R, it: &mut dyn FnMut(Option<&str> if ln.starts_with("fn") || ln.starts_with("mod") { return; } else if let Some((lncfg, ln)) = line_directive(comment, ln) { - it(lncfg, ln); + it(lncfg, ln, line_number); } } } @@ -660,21 +673,6 @@ impl Config { } } - fn parse_needs_matching_clang(&self, line: &str) -> bool { - self.parse_name_directive(line, "needs-matching-clang") - } - - fn parse_needs_profiler_support(&self, line: &str) -> bool { - self.parse_name_directive(line, "needs-profiler-support") - } - - fn has_cfg_prefix(&self, line: &str, prefix: &str) -> bool { - // returns whether this line contains this prefix or not. For prefix - // "ignore", returns true if line says "ignore-x86_64", "ignore-arch", - // "ignore-android" etc. - line.starts_with(prefix) && line.as_bytes().get(prefix.len()) == Some(&b'-') - } - 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". @@ -862,155 +860,58 @@ where pub fn make_test_description( config: &Config, + cache: &HeadersCache, name: test::TestName, path: &Path, src: R, cfg: Option<&str>, + poisoned: &mut bool, ) -> test::TestDesc { let mut ignore = false; let mut ignore_message = None; let mut should_fail = false; - let rustc_has_profiler_support = env::var_os("RUSTC_PROFILER_SUPPORT").is_some(); - let rustc_has_sanitizer_support = env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(); - let has_asm_support = config.has_asm_support(); - let has_asan = util::ASAN_SUPPORTED_TARGETS.contains(&&*config.target); - let has_cfi = util::CFI_SUPPORTED_TARGETS.contains(&&*config.target); - let has_kcfi = util::KCFI_SUPPORTED_TARGETS.contains(&&*config.target); - let has_kasan = util::KASAN_SUPPORTED_TARGETS.contains(&&*config.target); - let has_lsan = util::LSAN_SUPPORTED_TARGETS.contains(&&*config.target); - let has_msan = util::MSAN_SUPPORTED_TARGETS.contains(&&*config.target); - let has_tsan = util::TSAN_SUPPORTED_TARGETS.contains(&&*config.target); - let has_hwasan = util::HWASAN_SUPPORTED_TARGETS.contains(&&*config.target); - let has_memtag = util::MEMTAG_SUPPORTED_TARGETS.contains(&&*config.target); - let has_shadow_call_stack = util::SHADOWCALLSTACK_SUPPORTED_TARGETS.contains(&&*config.target); - let has_xray = util::XRAY_SUPPORTED_TARGETS.contains(&&*config.target); - - // For tests using the `needs-rust-lld` directive (e.g. for `-Zgcc-ld=lld`), 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. - let has_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(&config.target) - .join("bin") - .join(if config.host.contains("windows") { "rust-lld.exe" } else { "rust-lld" }) - .exists(); - - fn is_on_path(file: &'static str) -> impl Fn() -> bool { - move || env::split_paths(&env::var_os("PATH").unwrap()).any(|dir| dir.join(file).is_file()) - } - - // On Windows, dlltool.exe is used for all architectures. - #[cfg(windows)] - let (has_i686_dlltool, has_x86_64_dlltool) = - (is_on_path("dlltool.exe"), is_on_path("dlltool.exe")); - // For non-Windows, there are architecture specific dlltool binaries. - #[cfg(not(windows))] - let (has_i686_dlltool, has_x86_64_dlltool) = - (is_on_path("i686-w64-mingw32-dlltool"), is_on_path("x86_64-w64-mingw32-dlltool")); - - iter_header(path, src, &mut |revision, ln| { + iter_header(path, src, &mut |revision, ln, line_number| { if revision.is_some() && revision != cfg { return; } - macro_rules! reason { + + macro_rules! decision { ($e:expr) => { - ignore |= match $e { - true => { - ignore_message = Some(stringify!($e)); - true + match $e { + IgnoreDecision::Ignore { reason } => { + ignore = true; + // The ignore reason must be a &'static str, so we have to leak memory to + // create it. This is fine, as the header is parsed only at the start of + // compiletest so it won't grow indefinitely. + ignore_message = Some(&*Box::leak(Box::::from(reason))); } - false => ignore, - } - }; - } - - { - let parsed = parse_cfg_name_directive(config, ln, "ignore"); - ignore = match parsed.outcome { - MatchOutcome::Match => { - let reason = parsed.pretty_reason.unwrap(); - // The ignore reason must be a &'static str, so we have to leak memory to - // create it. This is fine, as the header is parsed only at the start of - // compiletest so it won't grow indefinitely. - ignore_message = Some(Box::leak(Box::::from(match parsed.comment { - Some(comment) => format!("ignored {reason} ({comment})"), - None => format!("ignored {reason}"), - })) as &str); - true + IgnoreDecision::Error { message } => { + eprintln!("error: {}:{line_number}: {message}", path.display()); + *poisoned = true; + return; + } + IgnoreDecision::Continue => {} } - MatchOutcome::NoMatch => ignore, - MatchOutcome::External => ignore, - MatchOutcome::Invalid => panic!("invalid line in {}: {ln}", path.display()), }; } - if config.has_cfg_prefix(ln, "only") { - let parsed = parse_cfg_name_directive(config, ln, "only"); - ignore = match parsed.outcome { - MatchOutcome::Match => ignore, - MatchOutcome::NoMatch => { - let reason = parsed.pretty_reason.unwrap(); - // The ignore reason must be a &'static str, so we have to leak memory to - // create it. This is fine, as the header is parsed only at the start of - // compiletest so it won't grow indefinitely. - ignore_message = Some(Box::leak(Box::::from(match parsed.comment { - Some(comment) => format!("only executed {reason} ({comment})"), - None => format!("only executed {reason}"), - })) as &str); - true - } - MatchOutcome::External => ignore, - MatchOutcome::Invalid => panic!("invalid line in {}: {ln}", path.display()), - }; + decision!(cfg::handle_ignore(config, ln)); + decision!(cfg::handle_only(config, ln)); + decision!(needs::handle_needs(&cache.needs, config, ln)); + decision!(ignore_llvm(config, ln)); + decision!(ignore_cdb(config, ln)); + decision!(ignore_gdb(config, ln)); + decision!(ignore_lldb(config, ln)); + + if config.target == "wasm32-unknown-unknown" { + if config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS) { + decision!(IgnoreDecision::Ignore { + reason: "ignored when checking the run results on WASM".into(), + }); + } } - reason!(ignore_llvm(config, ln)); - reason!( - config.run_clang_based_tests_with.is_none() && config.parse_needs_matching_clang(ln) - ); - reason!(!has_asm_support && config.parse_name_directive(ln, "needs-asm-support")); - reason!(!rustc_has_profiler_support && config.parse_needs_profiler_support(ln)); - reason!(!config.run_enabled() && config.parse_name_directive(ln, "needs-run-enabled")); - reason!( - !rustc_has_sanitizer_support - && config.parse_name_directive(ln, "needs-sanitizer-support") - ); - reason!(!has_asan && config.parse_name_directive(ln, "needs-sanitizer-address")); - reason!(!has_cfi && config.parse_name_directive(ln, "needs-sanitizer-cfi")); - reason!(!has_kcfi && config.parse_name_directive(ln, "needs-sanitizer-kcfi")); - reason!(!has_kasan && config.parse_name_directive(ln, "needs-sanitizer-kasan")); - reason!(!has_lsan && config.parse_name_directive(ln, "needs-sanitizer-leak")); - reason!(!has_msan && config.parse_name_directive(ln, "needs-sanitizer-memory")); - reason!(!has_tsan && config.parse_name_directive(ln, "needs-sanitizer-thread")); - reason!(!has_hwasan && config.parse_name_directive(ln, "needs-sanitizer-hwaddress")); - reason!(!has_memtag && config.parse_name_directive(ln, "needs-sanitizer-memtag")); - reason!( - !has_shadow_call_stack - && config.parse_name_directive(ln, "needs-sanitizer-shadow-call-stack") - ); - reason!(!config.can_unwind() && config.parse_name_directive(ln, "needs-unwind")); - reason!(!has_xray && config.parse_name_directive(ln, "needs-xray")); - reason!( - config.target == "wasm32-unknown-unknown" - && config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS) - ); - reason!(config.debugger == Some(Debugger::Cdb) && ignore_cdb(config, ln)); - reason!(config.debugger == Some(Debugger::Gdb) && ignore_gdb(config, ln)); - reason!(config.debugger == Some(Debugger::Lldb) && ignore_lldb(config, ln)); - reason!(!has_rust_lld && config.parse_name_directive(ln, "needs-rust-lld")); - reason!(config.parse_name_directive(ln, "needs-i686-dlltool") && !has_i686_dlltool()); - reason!(config.parse_name_directive(ln, "needs-x86_64-dlltool") && !has_x86_64_dlltool()); should_fail |= config.parse_name_directive(ln, "should-fail"); }); @@ -1044,22 +945,34 @@ pub fn make_test_description( } } -fn ignore_cdb(config: &Config, line: &str) -> bool { +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(min_version) = line.strip_prefix("min-cdb-version:").map(str::trim) { - let min_version = extract_cdb_version(min_version).unwrap_or_else(|| { - panic!("couldn't parse version range: {:?}", min_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 - return actual_version < min_version; + if actual_version < min_version { + return IgnoreDecision::Ignore { + reason: format!("ignored when the CDB version is lower than {rest}"), + }; + } } } - false + IgnoreDecision::Continue } -fn ignore_gdb(config: &Config, line: &str) -> bool { +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) @@ -1072,7 +985,11 @@ fn ignore_gdb(config: &Config, line: &str) -> bool { } // Ignore if actual version is smaller than the minimum // required version - return actual_version < start_ver; + 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(|| { @@ -1083,32 +1000,47 @@ fn ignore_gdb(config: &Config, line: &str) -> bool { panic!("Malformed GDB version range: max < min") } - return actual_version >= min_version && actual_version <= max_version; + 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}"), + }; + } + } } } - false + IgnoreDecision::Continue } -fn ignore_lldb(config: &Config, line: &str) -> bool { +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(min_version) = line.strip_prefix("min-lldb-version:").map(str::trim) { - let min_version = min_version.parse().unwrap_or_else(|e| { - panic!("Unexpected format of LLDB version string: {}\n{:?}", min_version, e); + 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 - actual_version < min_version - } else { - line.starts_with("rust-lldb") && !config.lldb_native_rust + if actual_version < min_version { + return IgnoreDecision::Ignore { + reason: format!("ignored when the LLDB version is {rest}"), + }; + } } - } else { - false } + IgnoreDecision::Continue } -fn ignore_llvm(config: &Config, line: &str) -> bool { +fn ignore_llvm(config: &Config, line: &str) -> IgnoreDecision { if config.system_llvm && line.starts_with("no-system-llvm") { - return true; + return IgnoreDecision::Ignore { reason: "ignored when the system LLVM is used".into() }; } if let Some(needed_components) = config.parse_name_value_directive(line, "needs-llvm-components") @@ -1121,7 +1053,9 @@ fn ignore_llvm(config: &Config, line: &str) -> bool { if env::var_os("COMPILETEST_NEEDS_ALL_LLVM_COMPONENTS").is_some() { panic!("missing LLVM component: {}", missing_component); } - return true; + return IgnoreDecision::Ignore { + reason: format!("ignored when the {missing_component} LLVM component is missing"), + }; } } if let Some(actual_version) = config.llvm_version { @@ -1129,12 +1063,20 @@ fn ignore_llvm(config: &Config, line: &str) -> bool { let min_version = extract_llvm_version(rest).unwrap(); // Ignore if actual version is smaller the minimum required // version - actual_version < min_version + if actual_version < min_version { + return IgnoreDecision::Ignore { + reason: format!("ignored when the LLVM version is older than {rest}"), + }; + } } else if let Some(rest) = line.strip_prefix("min-system-llvm-version:").map(str::trim) { let min_version = extract_llvm_version(rest).unwrap(); // Ignore if using system LLVM and actual version // is smaller the minimum required version - config.system_llvm && actual_version < min_version + if config.system_llvm && actual_version < min_version { + return IgnoreDecision::Ignore { + reason: format!("ignored when the system LLVM version is older than {rest}"), + }; + } } else if let Some(rest) = line.strip_prefix("ignore-llvm-version:").map(str::trim) { // Syntax is: "ignore-llvm-version: [- ]" let (v_min, v_max) = @@ -1145,11 +1087,24 @@ fn ignore_llvm(config: &Config, line: &str) -> bool { panic!("Malformed LLVM version range: max < min") } // Ignore if version lies inside of range. - actual_version >= v_min && actual_version <= v_max - } else { - false + 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 {rest}"), + }; + } else { + return IgnoreDecision::Ignore { + reason: format!("ignored when the LLVM version is between {rest}"), + }; + } + } } - } else { - false } + IgnoreDecision::Continue +} + +enum IgnoreDecision { + Ignore { reason: String }, + Continue, + Error { message: String }, } diff --git a/src/tools/compiletest/src/header/cfg.rs b/src/tools/compiletest/src/header/cfg.rs index 3b9333dfe7a0b..a9694d4d52c88 100644 --- a/src/tools/compiletest/src/header/cfg.rs +++ b/src/tools/compiletest/src/header/cfg.rs @@ -1,8 +1,43 @@ use crate::common::{CompareMode, Config, Debugger}; +use crate::header::IgnoreDecision; use std::collections::HashSet; 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 `normalize-stderr-32bit`. pub(super) fn parse_cfg_name_directive<'a>( @@ -11,10 +46,10 @@ pub(super) fn parse_cfg_name_directive<'a>( prefix: &str, ) -> ParsedNameDirective<'a> { if !line.as_bytes().starts_with(prefix.as_bytes()) { - return ParsedNameDirective::invalid(); + return ParsedNameDirective::not_a_directive(); } if line.as_bytes().get(prefix.len()) != Some(&b'-') { - return ParsedNameDirective::invalid(); + return ParsedNameDirective::not_a_directive(); } let line = &line[prefix.len() + 1..]; @@ -24,7 +59,7 @@ pub(super) fn parse_cfg_name_directive<'a>( // Some of the matchers might be "" depending on what the target information is. To avoid // problems we outright reject empty directives. if name == "" { - return ParsedNameDirective::invalid(); + return ParsedNameDirective::not_a_directive(); } let mut outcome = MatchOutcome::Invalid; @@ -218,8 +253,13 @@ pub(super) struct ParsedNameDirective<'a> { } impl ParsedNameDirective<'_> { - fn invalid() -> Self { - Self { name: None, pretty_reason: None, comment: None, outcome: MatchOutcome::NoMatch } + fn not_a_directive() -> Self { + Self { + name: None, + pretty_reason: None, + comment: None, + outcome: MatchOutcome::NotADirective, + } } } @@ -233,6 +273,8 @@ pub(super) enum MatchOutcome { Invalid, /// The directive is handled by other parts of our tooling. External, + /// The line is not actually a directive. + NotADirective, } trait CustomContains { diff --git a/src/tools/compiletest/src/header/needs.rs b/src/tools/compiletest/src/header/needs.rs new file mode 100644 index 0000000000000..35d6179abaa6b --- /dev/null +++ b/src/tools/compiletest/src/header/needs.rs @@ -0,0 +1,236 @@ +use crate::common::{Config, Debugger}; +use crate::header::IgnoreDecision; +use crate::util; + +pub(super) fn handle_needs( + cache: &CachedNeedsConditions, + config: &Config, + ln: &str, +) -> IgnoreDecision { + // Note thet 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-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-run-enabled", + condition: config.run_enabled(), + ignore_reason: "ignored when running the resulting test binaries is disabled", + }, + Need { + name: "needs-unwind", + condition: config.can_unwind(), + ignore_reason: "ignored on targets without unwinding support", + }, + Need { + name: "needs-profiler-support", + condition: std::env::var_os("RUSTC_PROFILER_SUPPORT").is_some(), + ignore_reason: "ignored when profiler support is disabled", + }, + Need { + name: "needs-matching-clang", + condition: config.run_clang_based_tests_with.is_some(), + ignore_reason: "ignored when the used clang does not match the built LLVM", + }, + 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-rust-lldb", + condition: config.debugger != Some(Debugger::Lldb) || config.lldb_native_rust, + ignore_reason: "ignored on targets without Rust's LLDB", + }, + Need { + name: "needs-i686-dlltool", + condition: cache.i686_dlltool, + ignore_reason: "ignored when dlltool for i686 is not present", + }, + Need { + name: "needs-x86_64-dlltool", + condition: cache.x86_64_dlltool, + ignore_reason: "ignored when dlltool for x86_64 is not present", + }, + ]; + + let (name, comment) = match ln.split_once([':', ' ']) { + Some((name, comment)) => (name, Some(comment)), + None => (ln, None), + }; + + 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) = comment { + format!("{} ({comment})", need.ignore_reason) + } 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_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, + xray: bool, + rust_lld: bool, + i686_dlltool: bool, + x86_64_dlltool: bool, +} + +impl CachedNeedsConditions { + pub(super) fn load(config: &Config) -> Self { + let path = std::env::var_os("PATH").expect("missing PATH environment variable"); + let path = std::env::split_paths(&path).collect::>(); + + let target = &&*config.target; + Self { + sanitizer_support: std::env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(), + sanitizer_address: util::ASAN_SUPPORTED_TARGETS.contains(target), + sanitizer_cfi: util::CFI_SUPPORTED_TARGETS.contains(target), + sanitizer_kcfi: util::KCFI_SUPPORTED_TARGETS.contains(target), + sanitizer_kasan: util::KASAN_SUPPORTED_TARGETS.contains(target), + sanitizer_leak: util::LSAN_SUPPORTED_TARGETS.contains(target), + sanitizer_memory: util::MSAN_SUPPORTED_TARGETS.contains(target), + sanitizer_thread: util::TSAN_SUPPORTED_TARGETS.contains(target), + sanitizer_hwaddress: util::HWASAN_SUPPORTED_TARGETS.contains(target), + sanitizer_memtag: util::MEMTAG_SUPPORTED_TARGETS.contains(target), + sanitizer_shadow_call_stack: util::SHADOWCALLSTACK_SUPPORTED_TARGETS.contains(target), + xray: util::XRAY_SUPPORTED_TARGETS.contains(target), + + // For tests using the `needs-rust-lld` directive (e.g. for `-Zgcc-ld=lld`), 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(), + + // On Windows, dlltool.exe is used for all architectures. + #[cfg(windows)] + i686_dlltool: path.iter().any(|dir| dir.join("dlltool.exe").is_file()), + #[cfg(windows)] + x86_64_dlltool: path.iter().any(|dir| dir.join("dlltool.exe").is_file()), + + // For non-Windows, there are architecture specific dlltool binaries. + #[cfg(not(windows))] + i686_dlltool: path.iter().any(|dir| dir.join("i686-w64-mingw32-dlltool").is_file()), + #[cfg(not(windows))] + x86_64_dlltool: path.iter().any(|dir| dir.join("x86_64-w64-mingw32-dlltool").is_file()), + } + } +} diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs index acd588d7fee04..9af7bd5e20145 100644 --- a/src/tools/compiletest/src/header/tests.rs +++ b/src/tools/compiletest/src/header/tests.rs @@ -1,7 +1,25 @@ +use std::io::Read; use std::path::Path; use crate::common::{Config, Debugger}; -use crate::header::{make_test_description, parse_normalization_string, EarlyProps}; +use crate::header::{parse_normalization_string, EarlyProps, HeadersCache}; + +fn make_test_description( + config: &Config, + name: test::TestName, + path: &Path, + src: R, + cfg: Option<&str>, +) -> test::TestDesc { + let cache = HeadersCache::load(config); + let mut poisoned = false; + let test = + crate::header::make_test_description(config, &cache, name, path, src, cfg, &mut poisoned); + if poisoned { + panic!("poisoned!"); + } + test +} #[test] fn test_parse_normalization_string() { diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index cfb1ee34f679a..0e49822c1b7c3 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -25,6 +25,7 @@ use tracing::*; use walkdir::WalkDir; use self::header::{make_test_description, EarlyProps}; +use crate::header::HeadersCache; use std::sync::Arc; #[cfg(test)] @@ -555,16 +556,26 @@ pub fn make_tests( let modified_tests = modified_tests(&config, &config.src_base).unwrap_or_else(|err| { panic!("modified_tests got error from dir: {}, error: {}", config.src_base.display(), err) }); + + let cache = HeadersCache::load(&config); + let mut poisoned = false; collect_tests_from_dir( config.clone(), + &cache, &config.src_base, &PathBuf::new(), &inputs, tests, found_paths, &modified_tests, + &mut poisoned, ) .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display())); + + if poisoned { + eprintln!(); + panic!("there are errors in tests"); + } } /// Returns a stamp constructed from input files common to all test cases. @@ -628,12 +639,14 @@ fn modified_tests(config: &Config, dir: &Path) -> Result, String> { fn collect_tests_from_dir( config: Arc, + cache: &HeadersCache, dir: &Path, relative_dir_path: &Path, inputs: &Stamp, tests: &mut Vec, found_paths: &mut BTreeSet, modified_tests: &Vec, + poisoned: &mut bool, ) -> io::Result<()> { // Ignore directories that contain a file named `compiletest-ignore-dir`. if dir.join("compiletest-ignore-dir").exists() { @@ -645,7 +658,7 @@ fn collect_tests_from_dir( file: dir.to_path_buf(), relative_dir: relative_dir_path.parent().unwrap().to_path_buf(), }; - tests.extend(make_test(config, &paths, inputs)); + tests.extend(make_test(config, cache, &paths, inputs, poisoned)); return Ok(()); } @@ -671,19 +684,21 @@ fn collect_tests_from_dir( let paths = TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() }; - tests.extend(make_test(config.clone(), &paths, inputs)) + tests.extend(make_test(config.clone(), cache, &paths, inputs, poisoned)) } else if file_path.is_dir() { let relative_file_path = relative_dir_path.join(file.file_name()); if &file_name != "auxiliary" { debug!("found directory: {:?}", file_path.display()); collect_tests_from_dir( config.clone(), + cache, &file_path, &relative_file_path, inputs, tests, found_paths, modified_tests, + poisoned, )?; } } else { @@ -708,8 +723,10 @@ pub fn is_test(file_name: &OsString) -> bool { fn make_test( config: Arc, + cache: &HeadersCache, testpaths: &TestPaths, inputs: &Stamp, + poisoned: &mut bool, ) -> Vec { let test_path = if config.mode == Mode::RunMake { // Parse directives in the Makefile @@ -726,6 +743,7 @@ fn make_test( } else { early_props.revisions.iter().map(Some).collect() }; + revisions .into_iter() .map(|revision| { @@ -733,7 +751,9 @@ fn make_test( std::fs::File::open(&test_path).expect("open test file to parse ignores"); let cfg = revision.map(|v| &**v); let test_name = crate::make_test_name(&config, testpaths, revision); - let mut desc = make_test_description(&config, test_name, &test_path, src_file, cfg); + let mut desc = make_test_description( + &config, cache, test_name, &test_path, src_file, cfg, poisoned, + ); // Ignore tests that already run and are up to date with respect to inputs. if !config.force_rerun { desc.ignore |= is_up_to_date( diff --git a/tests/debuginfo/associated-types.rs b/tests/debuginfo/associated-types.rs index 0a0ce3c671f07..a1735520b1173 100644 --- a/tests/debuginfo/associated-types.rs +++ b/tests/debuginfo/associated-types.rs @@ -1,6 +1,6 @@ // Some versions of the non-rust-enabled LLDB print the wrong generic // parameter type names in this test. -// rust-lldb +// needs-rust-lldb // compile-flags:-g diff --git a/tests/debuginfo/borrowed-enum.rs b/tests/debuginfo/borrowed-enum.rs index f3e465dc652d4..37d458cb494ce 100644 --- a/tests/debuginfo/borrowed-enum.rs +++ b/tests/debuginfo/borrowed-enum.rs @@ -1,6 +1,6 @@ // Require a gdb or lldb that can read DW_TAG_variant_part. // min-gdb-version: 8.2 -// rust-lldb +// needs-rust-lldb // compile-flags:-g diff --git a/tests/debuginfo/generic-method-on-generic-struct.rs b/tests/debuginfo/generic-method-on-generic-struct.rs index 97609ef5d9341..2d54c2b07df30 100644 --- a/tests/debuginfo/generic-method-on-generic-struct.rs +++ b/tests/debuginfo/generic-method-on-generic-struct.rs @@ -2,7 +2,7 @@ // Some versions of the non-rust-enabled LLDB print the wrong generic // parameter type names in this test. -// rust-lldb +// needs-rust-lldb // === GDB TESTS =================================================================================== diff --git a/tests/debuginfo/generic-struct.rs b/tests/debuginfo/generic-struct.rs index 5fa5ce8009935..5213eebc18bd2 100644 --- a/tests/debuginfo/generic-struct.rs +++ b/tests/debuginfo/generic-struct.rs @@ -1,6 +1,6 @@ // Some versions of the non-rust-enabled LLDB print the wrong generic // parameter type names in this test. -// rust-lldb +// needs-rust-lldb // compile-flags:-g diff --git a/tests/debuginfo/generic-tuple-style-enum.rs b/tests/debuginfo/generic-tuple-style-enum.rs index 60362e54e7dbb..a55402691dc30 100644 --- a/tests/debuginfo/generic-tuple-style-enum.rs +++ b/tests/debuginfo/generic-tuple-style-enum.rs @@ -1,6 +1,6 @@ // Require a gdb or lldb that can read DW_TAG_variant_part. // min-gdb-version: 8.2 -// rust-lldb +// needs-rust-lldb // compile-flags:-g diff --git a/tests/debuginfo/method-on-generic-struct.rs b/tests/debuginfo/method-on-generic-struct.rs index bf047449164b0..138d8391d40ab 100644 --- a/tests/debuginfo/method-on-generic-struct.rs +++ b/tests/debuginfo/method-on-generic-struct.rs @@ -1,6 +1,6 @@ // Some versions of the non-rust-enabled LLDB print the wrong generic // parameter type names in this test. -// rust-lldb +// needs-rust-lldb // compile-flags:-g diff --git a/tests/debuginfo/struct-style-enum.rs b/tests/debuginfo/struct-style-enum.rs index 3d819e3689887..0152dd9ea9b11 100644 --- a/tests/debuginfo/struct-style-enum.rs +++ b/tests/debuginfo/struct-style-enum.rs @@ -1,6 +1,6 @@ // Require a gdb or lldb that can read DW_TAG_variant_part. // min-gdb-version: 8.2 -// rust-lldb +// needs-rust-lldb // compile-flags:-g diff --git a/tests/debuginfo/tuple-style-enum.rs b/tests/debuginfo/tuple-style-enum.rs index 39ead172e651c..60f3ecbd21e26 100644 --- a/tests/debuginfo/tuple-style-enum.rs +++ b/tests/debuginfo/tuple-style-enum.rs @@ -1,6 +1,6 @@ // Require a gdb or lldb that can read DW_TAG_variant_part. // min-gdb-version: 8.2 -// rust-lldb +// needs-rust-lldb // compile-flags:-g diff --git a/tests/debuginfo/unique-enum.rs b/tests/debuginfo/unique-enum.rs index d7dfaeefe2b77..1ff6f5d9cbe1e 100644 --- a/tests/debuginfo/unique-enum.rs +++ b/tests/debuginfo/unique-enum.rs @@ -1,6 +1,6 @@ // Require a gdb or lldb that can read DW_TAG_variant_part. // min-gdb-version: 8.2 -// rust-lldb +// needs-rust-lldb // compile-flags:-g