summaryrefslogtreecommitdiff
path: root/src/tools/compiletest
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-04-15 21:43:36 +0000
committerbors <bors@rust-lang.org>2023-04-15 21:43:36 +0000
commit50b816f71f9055d6f162bec69c30b7574bb8e91a (patch)
treecdf61596f5b850448d4affa5854b1bd9e7a9271b /src/tools/compiletest
parent5cdb7886a5ece816864fab177f0c266ad4dd5358 (diff)
parent34a52dfc7a8a650d940fac1a6258c7a4fb2ae19a (diff)
downloadrust-50b816f71f9055d6f162bec69c30b7574bb8e91a.tar.gz
Auto merge of #110319 - ferrocene:pa-more-ignore-reasons, r=ehuss
[compiletest] Add more test ignore reasons, `needs-` validation, and improved error messages This PR makes more improvements to the way compiletest ignoring headers are handled, following up on #108905: * Human-readable ignore reasons have been added for the remaining ignore causes (`needs-*` directives, `*llvm*` directives, and debugger version directives). All ignored tests should now have a human-readable reason. * The code handling `needs-*` directives has been refactored, and now invalid `needs-*` directive emit errors like `ignore-*` and `only-*`. * All errors are now displayed at startup (with line numbers) rather than just the first error of the first file. This PR is best reviewed commit-by-commit. r? `@ehuss`
Diffstat (limited to 'src/tools/compiletest')
-rw-r--r--src/tools/compiletest/src/header.rs311
-rw-r--r--src/tools/compiletest/src/header/cfg.rs52
-rw-r--r--src/tools/compiletest/src/header/needs.rs236
-rw-r--r--src/tools/compiletest/src/header/tests.rs20
-rw-r--r--src/tools/compiletest/src/main.rs26
5 files changed, 458 insertions, 187 deletions
diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs
index a7efe16150e..aaa70bf19b2 100644
--- a/src/tools/compiletest/src/header.rs
+++ b/src/tools/compiletest/src/header.rs
@@ -12,13 +12,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)]
@@ -36,7 +47,7 @@ impl EarlyProps {
pub fn from_reader<R: Read>(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()
});
@@ -288,7 +299,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;
}
@@ -582,7 +593,7 @@ pub fn line_directive<'line>(
}
}
-fn iter_header<R: Read>(testfile: &Path, rdr: R, it: &mut dyn FnMut(Option<&str>, &str)) {
+fn iter_header<R: Read>(testfile: &Path, rdr: R, it: &mut dyn FnMut(Option<&str>, &str, usize)) {
if testfile.is_dir() {
return;
}
@@ -591,8 +602,10 @@ fn iter_header<R: Read>(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;
@@ -605,7 +618,7 @@ fn iter_header<R: Read>(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);
}
}
}
@@ -665,21 +678,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".
@@ -867,155 +865,58 @@ where
pub fn make_test_description<R: Read>(
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 <sysroot>/lib
- // - on windows, it can be <sysroot>/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::<str>::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::<str>::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::<str>::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");
});
@@ -1049,22 +950,34 @@ pub fn make_test_description<R: Read>(
}
}
-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)
@@ -1077,7 +990,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(|| {
@@ -1088,32 +1005,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")
@@ -1126,7 +1058,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 {
@@ -1134,12 +1068,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: <version1> [- <version2>]"
let (v_min, v_max) =
@@ -1150,11 +1092,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 3b9333dfe7a..a9694d4d52c 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 00000000000..35d6179abaa
--- /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::<Vec<_>>();
+
+ 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 <sysroot>/lib
+ // - on windows, it can be <sysroot>/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 acd588d7fee..9af7bd5e201 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<R: Read>(
+ 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 6a91d25a824..4a2b9de8aee 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)]
@@ -558,16 +559,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.
@@ -631,12 +642,14 @@ fn modified_tests(config: &Config, dir: &Path) -> Result<Vec<PathBuf>, String> {
fn collect_tests_from_dir(
config: Arc<Config>,
+ cache: &HeadersCache,
dir: &Path,
relative_dir_path: &Path,
inputs: &Stamp,
tests: &mut Vec<test::TestDescAndFn>,
found_paths: &mut BTreeSet<PathBuf>,
modified_tests: &Vec<PathBuf>,
+ poisoned: &mut bool,
) -> io::Result<()> {
// Ignore directories that contain a file named `compiletest-ignore-dir`.
if dir.join("compiletest-ignore-dir").exists() {
@@ -648,7 +661,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(());
}
@@ -674,19 +687,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 {
@@ -711,8 +726,10 @@ pub fn is_test(file_name: &OsString) -> bool {
fn make_test(
config: Arc<Config>,
+ cache: &HeadersCache,
testpaths: &TestPaths,
inputs: &Stamp,
+ poisoned: &mut bool,
) -> Vec<test::TestDescAndFn> {
let test_path = if config.mode == Mode::RunMake {
// Parse directives in the Makefile
@@ -729,6 +746,7 @@ fn make_test(
} else {
early_props.revisions.iter().map(Some).collect()
};
+
revisions
.into_iter()
.map(|revision| {
@@ -736,7 +754,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(