summaryrefslogtreecommitdiffstats
path: root/src/tools/compiletest
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 02:49:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 02:49:42 +0000
commit837b550238aa671a591ccf282dddeab29cadb206 (patch)
tree914b6b8862bace72bd3245ca184d374b08d8a672 /src/tools/compiletest
parentAdding debian version 1.70.0+dfsg2-1. (diff)
downloadrustc-837b550238aa671a591ccf282dddeab29cadb206.tar.xz
rustc-837b550238aa671a591ccf282dddeab29cadb206.zip
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/compiletest')
-rw-r--r--src/tools/compiletest/Cargo.toml6
-rw-r--r--src/tools/compiletest/src/common.rs127
-rw-r--r--src/tools/compiletest/src/header.rs326
-rw-r--r--src/tools/compiletest/src/header/cfg.rs52
-rw-r--r--src/tools/compiletest/src/header/needs.rs267
-rw-r--r--src/tools/compiletest/src/header/tests.rs30
-rw-r--r--src/tools/compiletest/src/lib.rs1136
-rw-r--r--src/tools/compiletest/src/main.rs1115
-rw-r--r--src/tools/compiletest/src/runtest.rs169
9 files changed, 1871 insertions, 1357 deletions
diff --git a/src/tools/compiletest/Cargo.toml b/src/tools/compiletest/Cargo.toml
index 85fd6523c..d2f258320 100644
--- a/src/tools/compiletest/Cargo.toml
+++ b/src/tools/compiletest/Cargo.toml
@@ -3,6 +3,9 @@ name = "compiletest"
version = "0.0.0"
edition = "2021"
+[lib]
+doctest = false
+
[dependencies]
colored = "2"
diff = "0.1.10"
@@ -20,6 +23,7 @@ once_cell = "1.16.0"
walkdir = "2"
glob = "0.3.0"
lazycell = "1.3.0"
+anyhow = "1"
[target.'cfg(unix)'.dependencies]
libc = "0.2"
@@ -28,7 +32,7 @@ libc = "0.2"
miow = "0.5"
[target.'cfg(windows)'.dependencies.windows]
-version = "0.46.0"
+version = "0.48.0"
features = [
"Win32_Foundation",
"Win32_System_Diagnostics_Debug",
diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs
index d2f494942..f796c8987 100644
--- a/src/tools/compiletest/src/common.rs
+++ b/src/tools/compiletest/src/common.rs
@@ -69,6 +69,12 @@ string_enum! {
}
}
+impl Default for Mode {
+ fn default() -> Self {
+ Mode::Ui
+ }
+}
+
impl Mode {
pub fn disambiguator(self) -> &'static str {
// Pretty-printing tests could run concurrently, and if they do,
@@ -125,7 +131,7 @@ pub enum PanicStrategy {
}
/// Configuration for compiletest
-#[derive(Debug, Clone)]
+#[derive(Debug, Default, Clone)]
pub struct Config {
/// `true` to overwrite stderr/stdout files instead of complaining about changes in output.
pub bless: bool,
@@ -303,6 +309,9 @@ pub struct Config {
/// The current Rust channel
pub channel: String,
+ /// Whether adding git commit information such as the commit hash has been enabled for building
+ pub git_hash: bool,
+
/// The default Rust edition
pub edition: Option<String>,
@@ -419,19 +428,12 @@ pub struct TargetCfgs {
impl TargetCfgs {
fn new(config: &Config) -> TargetCfgs {
- let targets: HashMap<String, TargetCfg> = if config.stage_id.starts_with("stage0-") {
- // #[cfg(bootstrap)]
- // Needed only for one cycle, remove during the bootstrap bump.
- Self::collect_all_slow(config)
- } else {
- serde_json::from_str(&rustc_output(
- config,
- &["--print=all-target-specs-json", "-Zunstable-options"],
- ))
- .unwrap()
- };
-
- let mut current = None;
+ let targets: HashMap<String, TargetCfg> = serde_json::from_str(&rustc_output(
+ config,
+ &["--print=all-target-specs-json", "-Zunstable-options"],
+ ))
+ .unwrap();
+
let mut all_targets = HashSet::new();
let mut all_archs = HashSet::new();
let mut all_oses = HashSet::new();
@@ -452,14 +454,11 @@ impl TargetCfgs {
}
all_pointer_widths.insert(format!("{}bit", cfg.pointer_width));
- if target == config.target {
- current = Some(cfg);
- }
all_targets.insert(target.into());
}
Self {
- current: current.expect("current target not found"),
+ current: Self::get_current_target_config(config),
all_targets,
all_archs,
all_oses,
@@ -471,23 +470,87 @@ impl TargetCfgs {
}
}
- // #[cfg(bootstrap)]
- // Needed only for one cycle, remove during the bootstrap bump.
- fn collect_all_slow(config: &Config) -> HashMap<String, TargetCfg> {
- let mut result = HashMap::new();
- for target in rustc_output(config, &["--print=target-list"]).trim().lines() {
- let json = rustc_output(
- config,
- &["--print=target-spec-json", "-Zunstable-options", "--target", target],
- );
- match serde_json::from_str(&json) {
- Ok(res) => {
- result.insert(target.into(), res);
+ fn get_current_target_config(config: &Config) -> TargetCfg {
+ let mut arch = None;
+ let mut os = None;
+ let mut env = None;
+ let mut abi = None;
+ let mut families = Vec::new();
+ let mut pointer_width = None;
+ let mut endian = None;
+ let mut panic = None;
+
+ for config in
+ rustc_output(config, &["--print=cfg", "--target", &config.target]).trim().lines()
+ {
+ let (name, value) = config
+ .split_once("=\"")
+ .map(|(name, value)| {
+ (
+ name,
+ Some(
+ value
+ .strip_suffix("\"")
+ .expect("key-value pair should be properly quoted"),
+ ),
+ )
+ })
+ .unwrap_or_else(|| (config, None));
+
+ match name {
+ "target_arch" => {
+ arch = Some(value.expect("target_arch should be a key-value pair").to_string());
+ }
+ "target_os" => {
+ os = Some(value.expect("target_os sould be a key-value pair").to_string());
}
- Err(err) => panic!("failed to parse target spec for {target}: {err}"),
+ "target_env" => {
+ env = Some(value.expect("target_env should be a key-value pair").to_string());
+ }
+ "target_abi" => {
+ abi = Some(value.expect("target_abi should be a key-value pair").to_string());
+ }
+ "target_family" => {
+ families
+ .push(value.expect("target_family should be a key-value pair").to_string());
+ }
+ "target_pointer_width" => {
+ pointer_width = Some(
+ value
+ .expect("target_pointer_width should be a key-value pair")
+ .parse::<u32>()
+ .expect("target_pointer_width should be a valid u32"),
+ );
+ }
+ "target_endian" => {
+ endian = Some(match value.expect("target_endian should be a key-value pair") {
+ "big" => Endian::Big,
+ "little" => Endian::Little,
+ _ => panic!("target_endian should be either 'big' or 'little'"),
+ });
+ }
+ "panic" => {
+ panic = Some(match value.expect("panic should be a key-value pair") {
+ "abort" => PanicStrategy::Abort,
+ "unwind" => PanicStrategy::Unwind,
+ _ => panic!("panic should be either 'abort' or 'unwind'"),
+ });
+ }
+ _ => (),
}
}
- result
+
+ TargetCfg {
+ arch: arch.expect("target configuration should specify target_arch"),
+ os: os.expect("target configuration should specify target_os"),
+ env: env.expect("target configuration should specify target_env"),
+ abi: abi.expect("target configuration should specify target_abi"),
+ families,
+ pointer_width: pointer_width
+ .expect("target configuration should specify target_pointer_width"),
+ endian: endian.expect("target configuration should specify target_endian"),
+ panic: panic.expect("target configuration should specify panic"),
+ }
}
}
diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs
index bc65ec932..8cc935e54 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()
});
@@ -94,6 +105,9 @@ pub struct TestProps {
pub dont_check_compiler_stdout: bool,
// For UI tests, allows compiler to generate arbitrary output to stderr
pub dont_check_compiler_stderr: bool,
+ // When checking the output of stdout or stderr check
+ // that the lines of expected output are a subset of the actual output.
+ pub compare_output_lines_by_subset: 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
@@ -202,6 +216,7 @@ mod directives {
pub const KNOWN_BUG: &'static str = "known-bug";
pub const MIR_UNIT_TEST: &'static str = "unit-test";
pub const REMAP_SRC_BASE: &'static str = "remap-src-base";
+ pub const COMPARE_OUTPUT_LINES_BY_SUBSET: &'static str = "compare-output-lines-by-subset";
// This isn't a real directive, just one that is probably mistyped often
pub const INCORRECT_COMPILER_FLAGS: &'static str = "compiler-flags";
}
@@ -227,6 +242,7 @@ impl TestProps {
check_run_results: false,
dont_check_compiler_stdout: false,
dont_check_compiler_stderr: false,
+ compare_output_lines_by_subset: false,
no_prefer_dynamic: false,
pretty_expanded: false,
pretty_mode: "normal".to_string(),
@@ -293,7 +309,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;
}
@@ -467,6 +483,11 @@ impl TestProps {
s.trim().to_string()
});
config.set_name_directive(ln, REMAP_SRC_BASE, &mut self.remap_src_base);
+ config.set_name_directive(
+ ln,
+ COMPARE_OUTPUT_LINES_BY_SUBSET,
+ &mut self.compare_output_lines_by_subset,
+ );
});
}
@@ -593,7 +614,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;
}
@@ -602,8 +623,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;
@@ -616,7 +639,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);
}
}
}
@@ -676,21 +699,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".
@@ -878,155 +886,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");
});
@@ -1043,15 +954,10 @@ pub fn make_test_description<R: Read>(
name,
ignore,
ignore_message,
- #[cfg(not(bootstrap))]
source_file: "",
- #[cfg(not(bootstrap))]
start_line: 0,
- #[cfg(not(bootstrap))]
start_col: 0,
- #[cfg(not(bootstrap))]
end_line: 0,
- #[cfg(not(bootstrap))]
end_col: 0,
should_panic,
compile_fail: false,
@@ -1060,22 +966,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)
@@ -1088,7 +1006,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(|| {
@@ -1099,32 +1021,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")
@@ -1137,7 +1074,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 {
@@ -1145,12 +1084,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) =
@@ -1161,11 +1108,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 aa36fd708..86a749b93 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;
@@ -222,8 +257,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,
+ }
}
}
@@ -237,6 +277,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 000000000..4a57c6140
--- /dev/null
+++ b/src/tools/compiletest/src/header/needs.rs
@@ -0,0 +1,267 @@
+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",
+ },
+ 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",
+ },
+ ];
+
+ 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,
+ 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<_>>();
+
+ // On Windows, dlltool.exe is used for all architectures.
+ #[cfg(windows)]
+ let dlltool = path.iter().any(|dir| dir.join("dlltool.exe").is_file());
+
+ // For non-Windows, there are architecture specific dlltool binaries.
+ #[cfg(not(windows))]
+ let i686_dlltool = path.iter().any(|dir| dir.join("i686-w64-mingw32-dlltool").is_file());
+ #[cfg(not(windows))]
+ let x86_64_dlltool =
+ path.iter().any(|dir| dir.join("x86_64-w64-mingw32-dlltool").is_file());
+
+ 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(),
+
+ #[cfg(windows)]
+ i686_dlltool: dlltool,
+ #[cfg(windows)]
+ x86_64_dlltool: dlltool,
+ #[cfg(windows)]
+ dlltool,
+
+ // For non-Windows, there are architecture specific dlltool binaries.
+ #[cfg(not(windows))]
+ i686_dlltool,
+ #[cfg(not(windows))]
+ x86_64_dlltool,
+ #[cfg(not(windows))]
+ dlltool: if config.matches_arch("x86") {
+ i686_dlltool
+ } else if config.matches_arch("x86_64") {
+ x86_64_dlltool
+ } else {
+ false
+ },
+ }
+ }
+}
diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs
index acd588d7f..362fba116 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() {
@@ -234,6 +252,16 @@ fn debugger() {
}
#[test]
+fn git_hash() {
+ let mut config = config();
+ config.git_hash = false;
+ assert!(check_ignore(&config, "// needs-git-hash"));
+
+ config.git_hash = true;
+ assert!(!check_ignore(&config, "// needs-git-hash"));
+}
+
+#[test]
fn sanitizers() {
let mut config = config();
diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs
new file mode 100644
index 000000000..fc48d0159
--- /dev/null
+++ b/src/tools/compiletest/src/lib.rs
@@ -0,0 +1,1136 @@
+#![crate_name = "compiletest"]
+// The `test` crate is the only unstable feature
+// allowed here, just to share similar code.
+#![feature(test)]
+
+extern crate test;
+
+#[cfg(test)]
+mod tests;
+
+pub mod common;
+pub mod compute_diff;
+pub mod errors;
+pub mod header;
+mod json;
+mod raise_fd_limit;
+mod read2;
+pub mod runtest;
+pub mod util;
+
+use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS};
+use crate::common::{Config, Debugger, Mode, PassMode, TestPaths};
+use crate::util::logv;
+use build_helper::git::{get_git_modified_files, get_git_untracked_files};
+use core::panic;
+use getopts::Options;
+use lazycell::AtomicLazyCell;
+use std::collections::BTreeSet;
+use std::ffi::OsString;
+use std::fs;
+use std::io::{self, ErrorKind};
+use std::path::{Path, PathBuf};
+use std::process::{Command, Stdio};
+use std::time::SystemTime;
+use std::{env, vec};
+use test::ColorConfig;
+use tracing::*;
+use walkdir::WalkDir;
+
+use self::header::{make_test_description, EarlyProps};
+use crate::header::HeadersCache;
+use std::sync::Arc;
+
+pub fn parse_config(args: Vec<String>) -> Config {
+ let mut opts = Options::new();
+ opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
+ .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
+ .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
+ .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
+ .optopt("", "rust-demangler-path", "path to rust-demangler to use in tests", "PATH")
+ .reqopt("", "python", "path to python to use for doc tests", "PATH")
+ .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
+ .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
+ .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM")
+ .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind")
+ .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
+ .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
+ .reqopt("", "src-base", "directory to scan for test files", "PATH")
+ .reqopt("", "build-base", "directory to deposit test outputs", "PATH")
+ .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH")
+ .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
+ .reqopt(
+ "",
+ "mode",
+ "which sort of compile tests to run",
+ "run-pass-valgrind | pretty | debug-info | codegen | rustdoc \
+ | rustdoc-json | codegen-units | incremental | run-make | ui | js-doc-test | mir-opt | assembly",
+ )
+ .reqopt(
+ "",
+ "suite",
+ "which suite of compile tests to run. used for nicer error reporting.",
+ "SUITE",
+ )
+ .optopt(
+ "",
+ "pass",
+ "force {check,build,run}-pass tests to this mode.",
+ "check | build | run",
+ )
+ .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
+ .optflag("", "ignored", "run tests marked as ignored")
+ .optmulti("", "skip", "skip tests matching SUBSTRING. Can be passed multiple times", "SUBSTRING")
+ .optflag("", "exact", "filters match exactly")
+ .optopt(
+ "",
+ "runtool",
+ "supervisor program to run tests under \
+ (eg. emulator, valgrind)",
+ "PROGRAM",
+ )
+ .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
+ .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
+ .optflag("", "optimize-tests", "run tests with optimizations enabled")
+ .optflag("", "verbose", "run tests verbosely, showing all output")
+ .optflag(
+ "",
+ "bless",
+ "overwrite stderr/stdout files instead of complaining about a mismatch",
+ )
+ .optflag("", "quiet", "print one character per test instead of one line")
+ .optopt("", "color", "coloring: auto, always, never", "WHEN")
+ .optflag("", "json", "emit json output instead of plaintext output")
+ .optopt("", "logfile", "file to log test execution to", "FILE")
+ .optopt("", "target", "the target to build for", "TARGET")
+ .optopt("", "host", "the host to build for", "HOST")
+ .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
+ .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
+ .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
+ .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
+ .optflag("", "system-llvm", "is LLVM the system LLVM")
+ .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
+ .optopt("", "adb-path", "path to the android debugger", "PATH")
+ .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
+ .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
+ .reqopt("", "cc", "path to a C compiler", "PATH")
+ .reqopt("", "cxx", "path to a C++ compiler", "PATH")
+ .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
+ .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
+ .optopt("", "ar", "path to an archiver", "PATH")
+ .optopt("", "target-linker", "path to a linker for the target", "PATH")
+ .optopt("", "host-linker", "path to a linker for the host", "PATH")
+ .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
+ .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
+ .optopt("", "nodejs", "the name of nodejs", "PATH")
+ .optopt("", "npm", "the name of npm", "PATH")
+ .optopt("", "remote-test-client", "path to the remote test client", "PATH")
+ .optopt(
+ "",
+ "compare-mode",
+ "mode describing what file the actual ui output will be compared to",
+ "COMPARE MODE",
+ )
+ .optflag(
+ "",
+ "rustfix-coverage",
+ "enable this to generate a Rustfix coverage file, which is saved in \
+ `./<build_base>/rustfix_missing_coverage.txt`",
+ )
+ .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
+ .optflag("", "only-modified", "only run tests that result been modified")
+ .optflag("", "nocapture", "")
+ .optflag("h", "help", "show this message")
+ .reqopt("", "channel", "current Rust channel", "CHANNEL")
+ .optflag("", "git-hash", "run tests which rely on commit version being compiled into the binaries")
+ .optopt("", "edition", "default Rust edition", "EDITION");
+
+ let (argv0, args_) = args.split_first().unwrap();
+ if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
+ let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
+ println!("{}", opts.usage(&message));
+ println!();
+ panic!()
+ }
+
+ let matches = &match opts.parse(args_) {
+ Ok(m) => m,
+ Err(f) => panic!("{:?}", f),
+ };
+
+ if matches.opt_present("h") || matches.opt_present("help") {
+ let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
+ println!("{}", opts.usage(&message));
+ println!();
+ panic!()
+ }
+
+ fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
+ match m.opt_str(nm) {
+ Some(s) => PathBuf::from(&s),
+ None => panic!("no option (=path) found for {}", nm),
+ }
+ }
+
+ fn make_absolute(path: PathBuf) -> PathBuf {
+ if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
+ }
+
+ let target = opt_str2(matches.opt_str("target"));
+ let android_cross_path = opt_path(matches, "android-cross-path");
+ let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target);
+ let (gdb, gdb_version, gdb_native_rust) =
+ analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
+ let (lldb_version, lldb_native_rust) = matches
+ .opt_str("lldb-version")
+ .as_deref()
+ .and_then(extract_lldb_version)
+ .map(|(v, b)| (Some(v), b))
+ .unwrap_or((None, false));
+ let color = match matches.opt_str("color").as_deref() {
+ Some("auto") | None => ColorConfig::AutoColor,
+ Some("always") => ColorConfig::AlwaysColor,
+ Some("never") => ColorConfig::NeverColor,
+ 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().and_then(header::extract_llvm_version).or_else(
+ || header::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
+ );
+
+ let src_base = opt_path(matches, "src-base");
+ let run_ignored = matches.opt_present("ignored");
+ let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
+ let has_tidy = if mode == Mode::Rustdoc {
+ Command::new("tidy")
+ .arg("--version")
+ .stdout(Stdio::null())
+ .status()
+ .map_or(false, |status| status.success())
+ } else {
+ // Avoid spawning an external command when we know tidy won't be used.
+ false
+ };
+ Config {
+ bless: matches.opt_present("bless"),
+ compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
+ run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
+ rustc_path: opt_path(matches, "rustc-path"),
+ rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
+ rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
+ python: matches.opt_str("python").unwrap(),
+ jsondocck_path: matches.opt_str("jsondocck-path"),
+ jsondoclint_path: matches.opt_str("jsondoclint-path"),
+ valgrind_path: matches.opt_str("valgrind-path"),
+ force_valgrind: matches.opt_present("force-valgrind"),
+ run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
+ llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
+ llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
+ src_base,
+ build_base: opt_path(matches, "build-base"),
+ sysroot_base: opt_path(matches, "sysroot-base"),
+ stage_id: matches.opt_str("stage-id").unwrap(),
+ mode,
+ suite: matches.opt_str("suite").unwrap(),
+ debugger: None,
+ run_ignored,
+ filters: matches.free.clone(),
+ skip: matches.opt_strs("skip"),
+ filter_exact: matches.opt_present("exact"),
+ force_pass_mode: matches.opt_str("pass").map(|mode| {
+ mode.parse::<PassMode>()
+ .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
+ }),
+ run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
+ "auto" => None,
+ "always" => Some(true),
+ "never" => Some(false),
+ _ => panic!("unknown `--run` option `{}` given", mode),
+ }),
+ logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
+ runtool: matches.opt_str("runtool"),
+ host_rustcflags: matches.opt_strs("host-rustcflags"),
+ target_rustcflags: matches.opt_strs("target-rustcflags"),
+ optimize_tests: matches.opt_present("optimize-tests"),
+ target,
+ host: opt_str2(matches.opt_str("host")),
+ cdb,
+ cdb_version,
+ gdb,
+ gdb_version,
+ gdb_native_rust,
+ lldb_version,
+ lldb_native_rust,
+ llvm_version,
+ system_llvm: matches.opt_present("system-llvm"),
+ android_cross_path,
+ adb_path: opt_str2(matches.opt_str("adb-path")),
+ adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
+ adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
+ && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
+ && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
+ lldb_python_dir: matches.opt_str("lldb-python-dir"),
+ verbose: matches.opt_present("verbose"),
+ format: match (matches.opt_present("quiet"), matches.opt_present("json")) {
+ (true, true) => panic!("--quiet and --json are incompatible"),
+ (true, false) => test::OutputFormat::Terse,
+ (false, true) => test::OutputFormat::Json,
+ (false, false) => test::OutputFormat::Pretty,
+ },
+ only_modified: matches.opt_present("only-modified"),
+ color,
+ remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
+ compare_mode: matches
+ .opt_str("compare-mode")
+ .map(|s| s.parse().expect("invalid --compare-mode provided")),
+ rustfix_coverage: matches.opt_present("rustfix-coverage"),
+ has_tidy,
+ channel: matches.opt_str("channel").unwrap(),
+ git_hash: matches.opt_present("git-hash"),
+ edition: matches.opt_str("edition"),
+
+ cc: matches.opt_str("cc").unwrap(),
+ cxx: matches.opt_str("cxx").unwrap(),
+ cflags: matches.opt_str("cflags").unwrap(),
+ cxxflags: matches.opt_str("cxxflags").unwrap(),
+ ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
+ target_linker: matches.opt_str("target-linker"),
+ host_linker: matches.opt_str("host-linker"),
+ llvm_components: matches.opt_str("llvm-components").unwrap(),
+ nodejs: matches.opt_str("nodejs"),
+ npm: matches.opt_str("npm"),
+
+ force_rerun: matches.opt_present("force-rerun"),
+
+ target_cfgs: AtomicLazyCell::new(),
+
+ nocapture: matches.opt_present("nocapture"),
+ }
+}
+
+pub fn log_config(config: &Config) {
+ let c = config;
+ logv(c, "configuration:".to_string());
+ logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
+ logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
+ logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
+ logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
+ logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
+ logv(c, format!("src_base: {:?}", config.src_base.display()));
+ logv(c, format!("build_base: {:?}", config.build_base.display()));
+ logv(c, format!("stage_id: {}", config.stage_id));
+ logv(c, format!("mode: {}", config.mode));
+ logv(c, format!("run_ignored: {}", config.run_ignored));
+ logv(c, format!("filters: {:?}", config.filters));
+ logv(c, format!("skip: {:?}", config.skip));
+ logv(c, format!("filter_exact: {}", config.filter_exact));
+ logv(
+ c,
+ format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
+ );
+ logv(c, format!("runtool: {}", opt_str(&config.runtool)));
+ logv(c, format!("host-rustcflags: {:?}", config.host_rustcflags));
+ logv(c, format!("target-rustcflags: {:?}", config.target_rustcflags));
+ logv(c, format!("target: {}", config.target));
+ logv(c, format!("host: {}", config.host));
+ logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
+ logv(c, format!("adb_path: {:?}", config.adb_path));
+ logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
+ logv(c, format!("adb_device_status: {}", config.adb_device_status));
+ logv(c, format!("ar: {}", config.ar));
+ logv(c, format!("target-linker: {:?}", config.target_linker));
+ logv(c, format!("host-linker: {:?}", config.host_linker));
+ logv(c, format!("verbose: {}", config.verbose));
+ logv(c, format!("format: {:?}", config.format));
+ logv(c, "\n".to_string());
+}
+
+pub fn opt_str(maybestr: &Option<String>) -> &str {
+ match *maybestr {
+ None => "(none)",
+ Some(ref s) => s,
+ }
+}
+
+pub fn opt_str2(maybestr: Option<String>) -> String {
+ match maybestr {
+ None => "(none)".to_owned(),
+ Some(s) => s,
+ }
+}
+
+pub fn run_tests(config: Arc<Config>) {
+ // If we want to collect rustfix coverage information,
+ // we first make sure that the coverage file does not exist.
+ // It will be created later on.
+ if config.rustfix_coverage {
+ let mut coverage_file_path = config.build_base.clone();
+ coverage_file_path.push("rustfix_missing_coverage.txt");
+ if coverage_file_path.exists() {
+ if let Err(e) = fs::remove_file(&coverage_file_path) {
+ panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
+ }
+ }
+ }
+
+ // sadly osx needs some file descriptor limits raised for running tests in
+ // parallel (especially when we have lots and lots of child processes).
+ // For context, see #8904
+ unsafe {
+ raise_fd_limit::raise_fd_limit();
+ }
+ // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
+ // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
+ env::set_var("__COMPAT_LAYER", "RunAsInvoker");
+
+ // Let tests know which target they're running as
+ env::set_var("TARGET", &config.target);
+
+ let opts = test_opts(&config);
+
+ let mut configs = Vec::new();
+ if let Mode::DebugInfo = config.mode {
+ // Debugging emscripten code doesn't make sense today
+ if !config.target.contains("emscripten") {
+ configs.extend(configure_cdb(&config));
+ configs.extend(configure_gdb(&config));
+ configs.extend(configure_lldb(&config));
+ }
+ } else {
+ configs.push(config.clone());
+ };
+
+ let mut tests = Vec::new();
+ for c in configs {
+ let mut found_paths = BTreeSet::new();
+ make_tests(c, &mut tests, &mut found_paths);
+ check_overlapping_tests(&found_paths);
+ }
+
+ tests.sort_by(|a, b| a.desc.name.as_slice().cmp(&b.desc.name.as_slice()));
+
+ let res = test::run_tests_console(&opts, tests);
+ match res {
+ Ok(true) => {}
+ Ok(false) => {
+ // We want to report that the tests failed, but we also want to give
+ // some indication of just what tests we were running. Especially on
+ // CI, where there can be cross-compiled tests for a lot of
+ // architectures, without this critical information it can be quite
+ // easy to miss which tests failed, and as such fail to reproduce
+ // the failure locally.
+
+ println!(
+ "Some tests failed in compiletest suite={}{} mode={} host={} target={}",
+ config.suite,
+ config
+ .compare_mode
+ .as_ref()
+ .map(|c| format!(" compare_mode={:?}", c))
+ .unwrap_or_default(),
+ config.mode,
+ config.host,
+ config.target
+ );
+
+ std::process::exit(1);
+ }
+ Err(e) => {
+ // We don't know if tests passed or not, but if there was an error
+ // during testing we don't want to just succeed (we may not have
+ // tested something), so fail.
+ //
+ // This should realistically "never" happen, so don't try to make
+ // this a pretty error message.
+ panic!("I/O failure during tests: {:?}", e);
+ }
+ }
+}
+
+fn configure_cdb(config: &Config) -> Option<Arc<Config>> {
+ config.cdb.as_ref()?;
+
+ Some(Arc::new(Config { debugger: Some(Debugger::Cdb), ..config.clone() }))
+}
+
+fn configure_gdb(config: &Config) -> Option<Arc<Config>> {
+ config.gdb_version?;
+
+ if config.matches_env("msvc") {
+ return None;
+ }
+
+ if config.remote_test_client.is_some() && !config.target.contains("android") {
+ println!(
+ "WARNING: debuginfo tests are not available when \
+ testing with remote"
+ );
+ return None;
+ }
+
+ if config.target.contains("android") {
+ println!(
+ "{} debug-info test uses tcp 5039 port.\
+ please reserve it",
+ config.target
+ );
+
+ // android debug-info test uses remote debugger so, we test 1 thread
+ // at once as they're all sharing the same TCP port to communicate
+ // over.
+ //
+ // we should figure out how to lift this restriction! (run them all
+ // on different ports allocated dynamically).
+ env::set_var("RUST_TEST_THREADS", "1");
+ }
+
+ Some(Arc::new(Config { debugger: Some(Debugger::Gdb), ..config.clone() }))
+}
+
+fn configure_lldb(config: &Config) -> Option<Arc<Config>> {
+ config.lldb_python_dir.as_ref()?;
+
+ if let Some(350) = config.lldb_version {
+ println!(
+ "WARNING: The used version of LLDB (350) has a \
+ known issue that breaks debuginfo tests. See \
+ issue #32520 for more information. Skipping all \
+ LLDB-based tests!",
+ );
+ return None;
+ }
+
+ Some(Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.clone() }))
+}
+
+pub fn test_opts(config: &Config) -> test::TestOpts {
+ if env::var("RUST_TEST_NOCAPTURE").is_ok() {
+ eprintln!(
+ "WARNING: RUST_TEST_NOCAPTURE is no longer used. \
+ Use the `--nocapture` flag instead."
+ );
+ }
+
+ test::TestOpts {
+ exclude_should_panic: false,
+ filters: config.filters.clone(),
+ filter_exact: config.filter_exact,
+ run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
+ format: config.format,
+ logfile: config.logfile.clone(),
+ run_tests: true,
+ bench_benchmarks: true,
+ nocapture: config.nocapture,
+ color: config.color,
+ shuffle: false,
+ shuffle_seed: None,
+ test_threads: None,
+ skip: config.skip.clone(),
+ list: false,
+ options: test::Options::new(),
+ time_options: None,
+ force_run_in_process: false,
+ fail_fast: std::env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
+ }
+}
+
+pub fn make_tests(
+ config: Arc<Config>,
+ tests: &mut Vec<test::TestDescAndFn>,
+ found_paths: &mut BTreeSet<PathBuf>,
+) {
+ debug!("making tests from {:?}", config.src_base.display());
+ let inputs = common_inputs_stamp(&config);
+ 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.
+fn common_inputs_stamp(config: &Config) -> Stamp {
+ let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
+
+ let mut stamp = Stamp::from_path(&config.rustc_path);
+
+ // Relevant pretty printer files
+ let pretty_printer_files = [
+ "src/etc/rust_types.py",
+ "src/etc/gdb_load_rust_pretty_printers.py",
+ "src/etc/gdb_lookup.py",
+ "src/etc/gdb_providers.py",
+ "src/etc/lldb_batchmode.py",
+ "src/etc/lldb_lookup.py",
+ "src/etc/lldb_providers.py",
+ ];
+ for file in &pretty_printer_files {
+ let path = rust_src_dir.join(file);
+ stamp.add_path(&path);
+ }
+
+ stamp.add_dir(&rust_src_dir.join("src/etc/natvis"));
+
+ stamp.add_dir(&config.run_lib_path);
+
+ if let Some(ref rustdoc_path) = config.rustdoc_path {
+ stamp.add_path(&rustdoc_path);
+ stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
+ }
+
+ // Compiletest itself.
+ stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
+
+ stamp
+}
+
+fn modified_tests(config: &Config, dir: &Path) -> Result<Vec<PathBuf>, String> {
+ if !config.only_modified {
+ return Ok(vec![]);
+ }
+ let files =
+ get_git_modified_files(Some(dir), &vec!["rs", "stderr", "fixed"])?.unwrap_or(vec![]);
+ // Add new test cases to the list, it will be convenient in daily development.
+ let untracked_files = get_git_untracked_files(None)?.unwrap_or(vec![]);
+
+ let all_paths = [&files[..], &untracked_files[..]].concat();
+ let full_paths = {
+ let mut full_paths: Vec<PathBuf> = all_paths
+ .into_iter()
+ .map(|f| PathBuf::from(f).with_extension("").with_extension("rs"))
+ .filter_map(|f| if Path::new(&f).exists() { f.canonicalize().ok() } else { None })
+ .collect();
+ full_paths.dedup();
+ full_paths.sort_unstable();
+ full_paths
+ };
+ Ok(full_paths)
+}
+
+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() {
+ return Ok(());
+ }
+
+ if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
+ let paths = TestPaths {
+ file: dir.to_path_buf(),
+ relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
+ };
+ tests.extend(make_test(config, cache, &paths, inputs, poisoned));
+ return Ok(());
+ }
+
+ // If we find a test foo/bar.rs, we have to build the
+ // output directory `$build/foo` so we can write
+ // `$build/foo/bar` into it. We do this *now* in this
+ // sequential loop because otherwise, if we do it in the
+ // tests themselves, they race for the privilege of
+ // creating the directories and sometimes fail randomly.
+ let build_dir = output_relative_path(&config, relative_dir_path);
+ fs::create_dir_all(&build_dir).unwrap();
+
+ // Add each `.rs` file as a test, and recurse further on any
+ // subdirectories we find, except for `aux` directories.
+ for file in fs::read_dir(dir)? {
+ let file = file?;
+ let file_path = file.path();
+ let file_name = file.file_name();
+ if is_test(&file_name) && (!config.only_modified || modified_tests.contains(&file_path)) {
+ debug!("found test file: {:?}", file_path.display());
+ let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
+ found_paths.insert(rel_test_path);
+ let paths =
+ TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
+
+ 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 {
+ debug!("found other file/directory: {:?}", file_path.display());
+ }
+ }
+ Ok(())
+}
+
+/// Returns true if `file_name` looks like a proper test file name.
+pub fn is_test(file_name: &OsString) -> bool {
+ let file_name = file_name.to_str().unwrap();
+
+ if !file_name.ends_with(".rs") {
+ return false;
+ }
+
+ // `.`, `#`, and `~` are common temp-file prefixes.
+ let invalid_prefixes = &[".", "#", "~"];
+ !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
+}
+
+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
+ testpaths.file.join("Makefile")
+ } else {
+ PathBuf::from(&testpaths.file)
+ };
+ let early_props = EarlyProps::from_file(&config, &test_path);
+
+ // Incremental tests are special, they inherently cannot be run in parallel.
+ // `runtest::run` will be responsible for iterating over revisions.
+ let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
+ vec![None]
+ } else {
+ early_props.revisions.iter().map(Some).collect()
+ };
+
+ revisions
+ .into_iter()
+ .map(|revision| {
+ let src_file =
+ 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, 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(
+ &config,
+ testpaths,
+ &early_props,
+ revision.map(|s| s.as_str()),
+ inputs,
+ );
+ }
+ test::TestDescAndFn {
+ desc,
+ testfn: make_test_closure(config.clone(), testpaths, revision),
+ }
+ })
+ .collect()
+}
+
+fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
+ output_base_dir(config, testpaths, revision).join("stamp")
+}
+
+fn files_related_to_test(
+ config: &Config,
+ testpaths: &TestPaths,
+ props: &EarlyProps,
+ revision: Option<&str>,
+) -> Vec<PathBuf> {
+ let mut related = vec![];
+
+ if testpaths.file.is_dir() {
+ // run-make tests use their individual directory
+ for entry in WalkDir::new(&testpaths.file) {
+ let path = entry.unwrap().into_path();
+ if path.is_file() {
+ related.push(path);
+ }
+ }
+ } else {
+ related.push(testpaths.file.clone());
+ }
+
+ for aux in &props.aux {
+ let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
+ related.push(path);
+ }
+
+ // UI test files.
+ for extension in UI_EXTENSIONS {
+ let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
+ related.push(path);
+ }
+
+ related
+}
+
+fn is_up_to_date(
+ config: &Config,
+ testpaths: &TestPaths,
+ props: &EarlyProps,
+ revision: Option<&str>,
+ inputs: &Stamp,
+) -> bool {
+ let stamp_name = stamp(config, testpaths, revision);
+ // Check hash.
+ let contents = match fs::read_to_string(&stamp_name) {
+ Ok(f) => f,
+ Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
+ Err(_) => return false,
+ };
+ let expected_hash = runtest::compute_stamp_hash(config);
+ if contents != expected_hash {
+ return false;
+ }
+
+ // Check timestamps.
+ let mut inputs = inputs.clone();
+ for path in files_related_to_test(config, testpaths, props, revision) {
+ inputs.add_path(&path);
+ }
+
+ inputs < Stamp::from_path(&stamp_name)
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+struct Stamp {
+ time: SystemTime,
+}
+
+impl Stamp {
+ fn from_path(path: &Path) -> Self {
+ let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
+ stamp.add_path(path);
+ stamp
+ }
+
+ fn add_path(&mut self, path: &Path) {
+ let modified = fs::metadata(path)
+ .and_then(|metadata| metadata.modified())
+ .unwrap_or(SystemTime::UNIX_EPOCH);
+ self.time = self.time.max(modified);
+ }
+
+ fn add_dir(&mut self, path: &Path) {
+ for entry in WalkDir::new(path) {
+ let entry = entry.unwrap();
+ if entry.file_type().is_file() {
+ let modified = entry
+ .metadata()
+ .ok()
+ .and_then(|metadata| metadata.modified().ok())
+ .unwrap_or(SystemTime::UNIX_EPOCH);
+ self.time = self.time.max(modified);
+ }
+ }
+ }
+}
+
+fn make_test_name(
+ config: &Config,
+ testpaths: &TestPaths,
+ revision: Option<&String>,
+) -> test::TestName {
+ // Print the name of the file, relative to the repository root.
+ // `src_base` looks like `/path/to/rust/tests/ui`
+ let root_directory = config.src_base.parent().unwrap().parent().unwrap();
+ let path = testpaths.file.strip_prefix(root_directory).unwrap();
+ let debugger = match config.debugger {
+ Some(d) => format!("-{}", d),
+ None => String::new(),
+ };
+ let mode_suffix = match config.compare_mode {
+ Some(ref mode) => format!(" ({})", mode.to_str()),
+ None => String::new(),
+ };
+
+ test::DynTestName(format!(
+ "[{}{}{}] {}{}",
+ config.mode,
+ debugger,
+ mode_suffix,
+ path.display(),
+ revision.map_or("".to_string(), |rev| format!("#{}", rev))
+ ))
+}
+
+fn make_test_closure(
+ config: Arc<Config>,
+ testpaths: &TestPaths,
+ revision: Option<&String>,
+) -> test::TestFn {
+ let config = config.clone();
+ let testpaths = testpaths.clone();
+ let revision = revision.cloned();
+ test::DynTestFn(Box::new(move || {
+ runtest::run(config, &testpaths, revision.as_deref());
+ Ok(())
+ }))
+}
+
+/// Returns `true` if the given target is an Android target for the
+/// purposes of GDB testing.
+fn is_android_gdb_target(target: &str) -> bool {
+ matches!(
+ &target[..],
+ "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
+ )
+}
+
+/// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
+fn is_pc_windows_msvc_target(target: &str) -> bool {
+ target.ends_with("-pc-windows-msvc")
+}
+
+fn find_cdb(target: &str) -> Option<OsString> {
+ if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
+ return None;
+ }
+
+ let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
+ let cdb_arch = if cfg!(target_arch = "x86") {
+ "x86"
+ } else if cfg!(target_arch = "x86_64") {
+ "x64"
+ } else if cfg!(target_arch = "aarch64") {
+ "arm64"
+ } else if cfg!(target_arch = "arm") {
+ "arm"
+ } else {
+ return None; // No compatible CDB.exe in the Windows 10 SDK
+ };
+
+ let mut path = PathBuf::new();
+ path.push(pf86);
+ path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
+ path.push(cdb_arch);
+ path.push(r"cdb.exe");
+
+ if !path.exists() {
+ return None;
+ }
+
+ Some(path.into_os_string())
+}
+
+/// Returns Path to CDB
+fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) {
+ let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
+
+ let mut version = None;
+ if let Some(cdb) = cdb.as_ref() {
+ if let Ok(output) = Command::new(cdb).arg("/version").output() {
+ if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
+ version = extract_cdb_version(&first_line);
+ }
+ }
+ }
+
+ (cdb, version)
+}
+
+fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
+ // Example full_version_line: "cdb version 10.0.18362.1"
+ let version = full_version_line.rsplit(' ').next()?;
+ let mut components = version.split('.');
+ let major: u16 = components.next().unwrap().parse().unwrap();
+ let minor: u16 = components.next().unwrap().parse().unwrap();
+ let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
+ let build: u16 = components.next().unwrap_or("0").parse().unwrap();
+ Some([major, minor, patch, build])
+}
+
+/// Returns (Path to GDB, GDB Version, GDB has Rust Support)
+fn analyze_gdb(
+ gdb: Option<String>,
+ target: &str,
+ android_cross_path: &PathBuf,
+) -> (Option<String>, Option<u32>, bool) {
+ #[cfg(not(windows))]
+ const GDB_FALLBACK: &str = "gdb";
+ #[cfg(windows)]
+ const GDB_FALLBACK: &str = "gdb.exe";
+
+ const MIN_GDB_WITH_RUST: u32 = 7011010;
+
+ let fallback_gdb = || {
+ if is_android_gdb_target(target) {
+ let mut gdb_path = match android_cross_path.to_str() {
+ Some(x) => x.to_owned(),
+ None => panic!("cannot find android cross path"),
+ };
+ gdb_path.push_str("/bin/gdb");
+ gdb_path
+ } else {
+ GDB_FALLBACK.to_owned()
+ }
+ };
+
+ let gdb = match gdb {
+ None => fallback_gdb(),
+ Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
+ Some(ref s) => s.to_owned(),
+ };
+
+ let mut version_line = None;
+ if let Ok(output) = Command::new(&gdb).arg("--version").output() {
+ if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
+ version_line = Some(first_line.to_string());
+ }
+ }
+
+ let version = match version_line {
+ Some(line) => extract_gdb_version(&line),
+ None => return (None, None, false),
+ };
+
+ let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
+
+ (Some(gdb), version, gdb_native_rust)
+}
+
+fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
+ let full_version_line = full_version_line.trim();
+
+ // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
+ // of the ? sections being optional
+
+ // We will parse up to 3 digits for each component, ignoring the date
+
+ // We skip text in parentheses. This avoids accidentally parsing
+ // the openSUSE version, which looks like:
+ // GNU gdb (GDB; openSUSE Leap 15.0) 8.1
+ // This particular form is documented in the GNU coding standards:
+ // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
+
+ let unbracketed_part = full_version_line.split('[').next().unwrap();
+ let mut splits = unbracketed_part.trim_end().rsplit(' ');
+ let version_string = splits.next().unwrap();
+
+ let mut splits = version_string.split('.');
+ let major = splits.next().unwrap();
+ let minor = splits.next().unwrap();
+ let patch = splits.next();
+
+ let major: u32 = major.parse().unwrap();
+ let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
+ None => {
+ let minor = minor.parse().unwrap();
+ let patch: u32 = match patch {
+ Some(patch) => match patch.find(not_a_digit) {
+ None => patch.parse().unwrap(),
+ Some(idx) if idx > 3 => 0,
+ Some(idx) => patch[..idx].parse().unwrap(),
+ },
+ None => 0,
+ };
+ (minor, patch)
+ }
+ // There is no patch version after minor-date (e.g. "4-2012").
+ Some(idx) => {
+ let minor = minor[..idx].parse().unwrap();
+ (minor, 0)
+ }
+ };
+
+ Some(((major * 1000) + minor) * 1000 + patch)
+}
+
+/// Returns (LLDB version, LLDB is rust-enabled)
+fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
+ // Extract the major LLDB version from the given version string.
+ // LLDB version strings are different for Apple and non-Apple platforms.
+ // The Apple variant looks like this:
+ //
+ // LLDB-179.5 (older versions)
+ // lldb-300.2.51 (new versions)
+ //
+ // We are only interested in the major version number, so this function
+ // will return `Some(179)` and `Some(300)` respectively.
+ //
+ // Upstream versions look like:
+ // lldb version 6.0.1
+ //
+ // There doesn't seem to be a way to correlate the Apple version
+ // with the upstream version, and since the tests were originally
+ // written against Apple versions, we make a fake Apple version by
+ // multiplying the first number by 100. This is a hack, but
+ // normally fine because the only non-Apple version we test is
+ // rust-enabled.
+
+ let full_version_line = full_version_line.trim();
+
+ if let Some(apple_ver) =
+ full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
+ {
+ if let Some(idx) = apple_ver.find(not_a_digit) {
+ let version: u32 = apple_ver[..idx].parse().unwrap();
+ return Some((version, full_version_line.contains("rust-enabled")));
+ }
+ } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
+ if let Some(idx) = lldb_ver.find(not_a_digit) {
+ let version: u32 = lldb_ver[..idx].parse().ok()?;
+ return Some((version * 100, full_version_line.contains("rust-enabled")));
+ }
+ }
+ None
+}
+
+fn not_a_digit(c: char) -> bool {
+ !c.is_digit(10)
+}
+
+fn check_overlapping_tests(found_paths: &BTreeSet<PathBuf>) {
+ let mut collisions = Vec::new();
+ for path in found_paths {
+ for ancestor in path.ancestors().skip(1) {
+ if found_paths.contains(ancestor) {
+ collisions.push((path, ancestor.clone()));
+ }
+ }
+ }
+ if !collisions.is_empty() {
+ let collisions: String = collisions
+ .into_iter()
+ .map(|(path, check_parent)| format!("test {path:?} clashes with {check_parent:?}\n"))
+ .collect();
+ panic!(
+ "{collisions}\n\
+ Tests cannot have overlapping names. Make sure they use unique prefixes."
+ );
+ }
+}
diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs
index 6a91d25a8..34d48559c 100644
--- a/src/tools/compiletest/src/main.rs
+++ b/src/tools/compiletest/src/main.rs
@@ -1,44 +1,6 @@
-#![crate_name = "compiletest"]
-// The `test` crate is the only unstable feature
-// allowed here, just to share similar code.
-#![feature(test)]
+use std::{env, sync::Arc};
-extern crate test;
-
-use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS};
-use crate::common::{Config, Debugger, Mode, PassMode, TestPaths};
-use crate::util::logv;
-use build_helper::git::{get_git_modified_files, get_git_untracked_files};
-use core::panic;
-use getopts::Options;
-use lazycell::AtomicLazyCell;
-use std::collections::BTreeSet;
-use std::ffi::OsString;
-use std::fs;
-use std::io::{self, ErrorKind};
-use std::path::{Path, PathBuf};
-use std::process::{Command, Stdio};
-use std::time::SystemTime;
-use std::{env, vec};
-use test::ColorConfig;
-use tracing::*;
-use walkdir::WalkDir;
-
-use self::header::{make_test_description, EarlyProps};
-use std::sync::Arc;
-
-#[cfg(test)]
-mod tests;
-
-pub mod common;
-pub mod compute_diff;
-pub mod errors;
-pub mod header;
-mod json;
-mod raise_fd_limit;
-mod read2;
-pub mod runtest;
-pub mod util;
+use compiletest::{common::Mode, log_config, parse_config, run_tests};
fn main() {
tracing_subscriber::fmt::init();
@@ -56,1076 +18,3 @@ fn main() {
log_config(&config);
run_tests(config);
}
-
-pub fn parse_config(args: Vec<String>) -> Config {
- let mut opts = Options::new();
- opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
- .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
- .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
- .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
- .optopt("", "rust-demangler-path", "path to rust-demangler to use in tests", "PATH")
- .reqopt("", "python", "path to python to use for doc tests", "PATH")
- .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
- .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
- .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM")
- .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind")
- .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
- .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
- .reqopt("", "src-base", "directory to scan for test files", "PATH")
- .reqopt("", "build-base", "directory to deposit test outputs", "PATH")
- .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH")
- .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
- .reqopt(
- "",
- "mode",
- "which sort of compile tests to run",
- "run-pass-valgrind | pretty | debug-info | codegen | rustdoc \
- | rustdoc-json | codegen-units | incremental | run-make | ui | js-doc-test | mir-opt | assembly",
- )
- .reqopt(
- "",
- "suite",
- "which suite of compile tests to run. used for nicer error reporting.",
- "SUITE",
- )
- .optopt(
- "",
- "pass",
- "force {check,build,run}-pass tests to this mode.",
- "check | build | run",
- )
- .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
- .optflag("", "ignored", "run tests marked as ignored")
- .optmulti("", "skip", "skip tests matching SUBSTRING. Can be passed multiple times", "SUBSTRING")
- .optflag("", "exact", "filters match exactly")
- .optopt(
- "",
- "runtool",
- "supervisor program to run tests under \
- (eg. emulator, valgrind)",
- "PROGRAM",
- )
- .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
- .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
- .optflag("", "optimize-tests", "run tests with optimizations enabled")
- .optflag("", "verbose", "run tests verbosely, showing all output")
- .optflag(
- "",
- "bless",
- "overwrite stderr/stdout files instead of complaining about a mismatch",
- )
- .optflag("", "quiet", "print one character per test instead of one line")
- .optopt("", "color", "coloring: auto, always, never", "WHEN")
- .optflag("", "json", "emit json output instead of plaintext output")
- .optopt("", "logfile", "file to log test execution to", "FILE")
- .optopt("", "target", "the target to build for", "TARGET")
- .optopt("", "host", "the host to build for", "HOST")
- .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
- .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
- .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
- .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
- .optflag("", "system-llvm", "is LLVM the system LLVM")
- .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
- .optopt("", "adb-path", "path to the android debugger", "PATH")
- .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
- .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
- .reqopt("", "cc", "path to a C compiler", "PATH")
- .reqopt("", "cxx", "path to a C++ compiler", "PATH")
- .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
- .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
- .optopt("", "ar", "path to an archiver", "PATH")
- .optopt("", "target-linker", "path to a linker for the target", "PATH")
- .optopt("", "host-linker", "path to a linker for the host", "PATH")
- .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
- .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
- .optopt("", "nodejs", "the name of nodejs", "PATH")
- .optopt("", "npm", "the name of npm", "PATH")
- .optopt("", "remote-test-client", "path to the remote test client", "PATH")
- .optopt(
- "",
- "compare-mode",
- "mode describing what file the actual ui output will be compared to",
- "COMPARE MODE",
- )
- .optflag(
- "",
- "rustfix-coverage",
- "enable this to generate a Rustfix coverage file, which is saved in \
- `./<build_base>/rustfix_missing_coverage.txt`",
- )
- .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
- .optflag("", "only-modified", "only run tests that result been modified")
- .optflag("", "nocapture", "")
- .optflag("h", "help", "show this message")
- .reqopt("", "channel", "current Rust channel", "CHANNEL")
- .optopt("", "edition", "default Rust edition", "EDITION");
-
- let (argv0, args_) = args.split_first().unwrap();
- if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
- let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
- println!("{}", opts.usage(&message));
- println!();
- panic!()
- }
-
- let matches = &match opts.parse(args_) {
- Ok(m) => m,
- Err(f) => panic!("{:?}", f),
- };
-
- if matches.opt_present("h") || matches.opt_present("help") {
- let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
- println!("{}", opts.usage(&message));
- println!();
- panic!()
- }
-
- fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
- match m.opt_str(nm) {
- Some(s) => PathBuf::from(&s),
- None => panic!("no option (=path) found for {}", nm),
- }
- }
-
- fn make_absolute(path: PathBuf) -> PathBuf {
- if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
- }
-
- let target = opt_str2(matches.opt_str("target"));
- let android_cross_path = opt_path(matches, "android-cross-path");
- let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target);
- let (gdb, gdb_version, gdb_native_rust) =
- analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
- let (lldb_version, lldb_native_rust) = matches
- .opt_str("lldb-version")
- .as_deref()
- .and_then(extract_lldb_version)
- .map(|(v, b)| (Some(v), b))
- .unwrap_or((None, false));
- let color = match matches.opt_str("color").as_deref() {
- Some("auto") | None => ColorConfig::AutoColor,
- Some("always") => ColorConfig::AlwaysColor,
- Some("never") => ColorConfig::NeverColor,
- 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().and_then(header::extract_llvm_version).or_else(
- || header::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
- );
-
- let src_base = opt_path(matches, "src-base");
- let run_ignored = matches.opt_present("ignored");
- let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
- let has_tidy = if mode == Mode::Rustdoc {
- Command::new("tidy")
- .arg("--version")
- .stdout(Stdio::null())
- .status()
- .map_or(false, |status| status.success())
- } else {
- // Avoid spawning an external command when we know tidy won't be used.
- false
- };
- Config {
- bless: matches.opt_present("bless"),
- compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
- run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
- rustc_path: opt_path(matches, "rustc-path"),
- rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
- rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
- python: matches.opt_str("python").unwrap(),
- jsondocck_path: matches.opt_str("jsondocck-path"),
- jsondoclint_path: matches.opt_str("jsondoclint-path"),
- valgrind_path: matches.opt_str("valgrind-path"),
- force_valgrind: matches.opt_present("force-valgrind"),
- run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
- llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
- llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
- src_base,
- build_base: opt_path(matches, "build-base"),
- sysroot_base: opt_path(matches, "sysroot-base"),
- stage_id: matches.opt_str("stage-id").unwrap(),
- mode,
- suite: matches.opt_str("suite").unwrap(),
- debugger: None,
- run_ignored,
- filters: matches.free.clone(),
- skip: matches.opt_strs("skip"),
- filter_exact: matches.opt_present("exact"),
- force_pass_mode: matches.opt_str("pass").map(|mode| {
- mode.parse::<PassMode>()
- .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
- }),
- run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
- "auto" => None,
- "always" => Some(true),
- "never" => Some(false),
- _ => panic!("unknown `--run` option `{}` given", mode),
- }),
- logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
- runtool: matches.opt_str("runtool"),
- host_rustcflags: matches.opt_strs("host-rustcflags"),
- target_rustcflags: matches.opt_strs("target-rustcflags"),
- optimize_tests: matches.opt_present("optimize-tests"),
- target,
- host: opt_str2(matches.opt_str("host")),
- cdb,
- cdb_version,
- gdb,
- gdb_version,
- gdb_native_rust,
- lldb_version,
- lldb_native_rust,
- llvm_version,
- system_llvm: matches.opt_present("system-llvm"),
- android_cross_path,
- adb_path: opt_str2(matches.opt_str("adb-path")),
- adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
- adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
- && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
- && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
- lldb_python_dir: matches.opt_str("lldb-python-dir"),
- verbose: matches.opt_present("verbose"),
- format: match (matches.opt_present("quiet"), matches.opt_present("json")) {
- (true, true) => panic!("--quiet and --json are incompatible"),
- (true, false) => test::OutputFormat::Terse,
- (false, true) => test::OutputFormat::Json,
- (false, false) => test::OutputFormat::Pretty,
- },
- only_modified: matches.opt_present("only-modified"),
- color,
- remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
- compare_mode: matches
- .opt_str("compare-mode")
- .map(|s| s.parse().expect("invalid --compare-mode provided")),
- rustfix_coverage: matches.opt_present("rustfix-coverage"),
- has_tidy,
- channel: matches.opt_str("channel").unwrap(),
- edition: matches.opt_str("edition"),
-
- cc: matches.opt_str("cc").unwrap(),
- cxx: matches.opt_str("cxx").unwrap(),
- cflags: matches.opt_str("cflags").unwrap(),
- cxxflags: matches.opt_str("cxxflags").unwrap(),
- ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
- target_linker: matches.opt_str("target-linker"),
- host_linker: matches.opt_str("host-linker"),
- llvm_components: matches.opt_str("llvm-components").unwrap(),
- nodejs: matches.opt_str("nodejs"),
- npm: matches.opt_str("npm"),
-
- force_rerun: matches.opt_present("force-rerun"),
-
- target_cfgs: AtomicLazyCell::new(),
-
- nocapture: matches.opt_present("nocapture"),
- }
-}
-
-pub fn log_config(config: &Config) {
- let c = config;
- logv(c, "configuration:".to_string());
- logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
- logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
- logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
- logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
- logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
- logv(c, format!("src_base: {:?}", config.src_base.display()));
- logv(c, format!("build_base: {:?}", config.build_base.display()));
- logv(c, format!("stage_id: {}", config.stage_id));
- logv(c, format!("mode: {}", config.mode));
- logv(c, format!("run_ignored: {}", config.run_ignored));
- logv(c, format!("filters: {:?}", config.filters));
- logv(c, format!("skip: {:?}", config.skip));
- logv(c, format!("filter_exact: {}", config.filter_exact));
- logv(
- c,
- format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
- );
- logv(c, format!("runtool: {}", opt_str(&config.runtool)));
- logv(c, format!("host-rustcflags: {:?}", config.host_rustcflags));
- logv(c, format!("target-rustcflags: {:?}", config.target_rustcflags));
- logv(c, format!("target: {}", config.target));
- logv(c, format!("host: {}", config.host));
- logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
- logv(c, format!("adb_path: {:?}", config.adb_path));
- logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
- logv(c, format!("adb_device_status: {}", config.adb_device_status));
- logv(c, format!("ar: {}", config.ar));
- logv(c, format!("target-linker: {:?}", config.target_linker));
- logv(c, format!("host-linker: {:?}", config.host_linker));
- logv(c, format!("verbose: {}", config.verbose));
- logv(c, format!("format: {:?}", config.format));
- logv(c, "\n".to_string());
-}
-
-pub fn opt_str(maybestr: &Option<String>) -> &str {
- match *maybestr {
- None => "(none)",
- Some(ref s) => s,
- }
-}
-
-pub fn opt_str2(maybestr: Option<String>) -> String {
- match maybestr {
- None => "(none)".to_owned(),
- Some(s) => s,
- }
-}
-
-pub fn run_tests(config: Arc<Config>) {
- // If we want to collect rustfix coverage information,
- // we first make sure that the coverage file does not exist.
- // It will be created later on.
- if config.rustfix_coverage {
- let mut coverage_file_path = config.build_base.clone();
- coverage_file_path.push("rustfix_missing_coverage.txt");
- if coverage_file_path.exists() {
- if let Err(e) = fs::remove_file(&coverage_file_path) {
- panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
- }
- }
- }
-
- // sadly osx needs some file descriptor limits raised for running tests in
- // parallel (especially when we have lots and lots of child processes).
- // For context, see #8904
- unsafe {
- raise_fd_limit::raise_fd_limit();
- }
- // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
- // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
- env::set_var("__COMPAT_LAYER", "RunAsInvoker");
-
- // Let tests know which target they're running as
- env::set_var("TARGET", &config.target);
-
- let opts = test_opts(&config);
-
- let mut configs = Vec::new();
- if let Mode::DebugInfo = config.mode {
- // Debugging emscripten code doesn't make sense today
- if !config.target.contains("emscripten") {
- configs.extend(configure_cdb(&config));
- configs.extend(configure_gdb(&config));
- configs.extend(configure_lldb(&config));
- }
- } else {
- configs.push(config.clone());
- };
-
- let mut tests = Vec::new();
- for c in configs {
- let mut found_paths = BTreeSet::new();
- make_tests(c, &mut tests, &mut found_paths);
- check_overlapping_tests(&found_paths);
- }
-
- tests.sort_by(|a, b| a.desc.name.as_slice().cmp(&b.desc.name.as_slice()));
-
- let res = test::run_tests_console(&opts, tests);
- match res {
- Ok(true) => {}
- Ok(false) => {
- // We want to report that the tests failed, but we also want to give
- // some indication of just what tests we were running. Especially on
- // CI, where there can be cross-compiled tests for a lot of
- // architectures, without this critical information it can be quite
- // easy to miss which tests failed, and as such fail to reproduce
- // the failure locally.
-
- println!(
- "Some tests failed in compiletest suite={}{} mode={} host={} target={}",
- config.suite,
- config
- .compare_mode
- .as_ref()
- .map(|c| format!(" compare_mode={:?}", c))
- .unwrap_or_default(),
- config.mode,
- config.host,
- config.target
- );
-
- std::process::exit(1);
- }
- Err(e) => {
- // We don't know if tests passed or not, but if there was an error
- // during testing we don't want to just succeed (we may not have
- // tested something), so fail.
- //
- // This should realistically "never" happen, so don't try to make
- // this a pretty error message.
- panic!("I/O failure during tests: {:?}", e);
- }
- }
-}
-
-fn configure_cdb(config: &Config) -> Option<Arc<Config>> {
- config.cdb.as_ref()?;
-
- Some(Arc::new(Config { debugger: Some(Debugger::Cdb), ..config.clone() }))
-}
-
-fn configure_gdb(config: &Config) -> Option<Arc<Config>> {
- config.gdb_version?;
-
- if config.matches_env("msvc") {
- return None;
- }
-
- if config.remote_test_client.is_some() && !config.target.contains("android") {
- println!(
- "WARNING: debuginfo tests are not available when \
- testing with remote"
- );
- return None;
- }
-
- if config.target.contains("android") {
- println!(
- "{} debug-info test uses tcp 5039 port.\
- please reserve it",
- config.target
- );
-
- // android debug-info test uses remote debugger so, we test 1 thread
- // at once as they're all sharing the same TCP port to communicate
- // over.
- //
- // we should figure out how to lift this restriction! (run them all
- // on different ports allocated dynamically).
- env::set_var("RUST_TEST_THREADS", "1");
- }
-
- Some(Arc::new(Config { debugger: Some(Debugger::Gdb), ..config.clone() }))
-}
-
-fn configure_lldb(config: &Config) -> Option<Arc<Config>> {
- config.lldb_python_dir.as_ref()?;
-
- if let Some(350) = config.lldb_version {
- println!(
- "WARNING: The used version of LLDB (350) has a \
- known issue that breaks debuginfo tests. See \
- issue #32520 for more information. Skipping all \
- LLDB-based tests!",
- );
- return None;
- }
-
- Some(Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.clone() }))
-}
-
-pub fn test_opts(config: &Config) -> test::TestOpts {
- if env::var("RUST_TEST_NOCAPTURE").is_ok() {
- eprintln!(
- "WARNING: RUST_TEST_NOCAPTURE is no longer used. \
- Use the `--nocapture` flag instead."
- );
- }
-
- test::TestOpts {
- exclude_should_panic: false,
- filters: config.filters.clone(),
- filter_exact: config.filter_exact,
- run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
- format: config.format,
- logfile: config.logfile.clone(),
- run_tests: true,
- bench_benchmarks: true,
- nocapture: config.nocapture,
- color: config.color,
- shuffle: false,
- shuffle_seed: None,
- test_threads: None,
- skip: config.skip.clone(),
- list: false,
- options: test::Options::new(),
- time_options: None,
- force_run_in_process: false,
- fail_fast: std::env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
- }
-}
-
-pub fn make_tests(
- config: Arc<Config>,
- tests: &mut Vec<test::TestDescAndFn>,
- found_paths: &mut BTreeSet<PathBuf>,
-) {
- debug!("making tests from {:?}", config.src_base.display());
- let inputs = common_inputs_stamp(&config);
- 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)
- });
- collect_tests_from_dir(
- config.clone(),
- &config.src_base,
- &PathBuf::new(),
- &inputs,
- tests,
- found_paths,
- &modified_tests,
- )
- .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
-}
-
-/// Returns a stamp constructed from input files common to all test cases.
-fn common_inputs_stamp(config: &Config) -> Stamp {
- let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
-
- let mut stamp = Stamp::from_path(&config.rustc_path);
-
- // Relevant pretty printer files
- let pretty_printer_files = [
- "src/etc/rust_types.py",
- "src/etc/gdb_load_rust_pretty_printers.py",
- "src/etc/gdb_lookup.py",
- "src/etc/gdb_providers.py",
- "src/etc/lldb_batchmode.py",
- "src/etc/lldb_lookup.py",
- "src/etc/lldb_providers.py",
- ];
- for file in &pretty_printer_files {
- let path = rust_src_dir.join(file);
- stamp.add_path(&path);
- }
-
- stamp.add_dir(&rust_src_dir.join("src/etc/natvis"));
-
- stamp.add_dir(&config.run_lib_path);
-
- if let Some(ref rustdoc_path) = config.rustdoc_path {
- stamp.add_path(&rustdoc_path);
- stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
- }
-
- // Compiletest itself.
- stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
-
- stamp
-}
-
-fn modified_tests(config: &Config, dir: &Path) -> Result<Vec<PathBuf>, String> {
- if !config.only_modified {
- return Ok(vec![]);
- }
- let files =
- get_git_modified_files(Some(dir), &vec!["rs", "stderr", "fixed"])?.unwrap_or(vec![]);
- // Add new test cases to the list, it will be convenient in daily development.
- let untracked_files = get_git_untracked_files(None)?.unwrap_or(vec![]);
-
- let all_paths = [&files[..], &untracked_files[..]].concat();
- let full_paths = {
- let mut full_paths: Vec<PathBuf> = all_paths
- .into_iter()
- .map(|f| PathBuf::from(f).with_extension("").with_extension("rs"))
- .filter_map(|f| if Path::new(&f).exists() { f.canonicalize().ok() } else { None })
- .collect();
- full_paths.dedup();
- full_paths.sort_unstable();
- full_paths
- };
- Ok(full_paths)
-}
-
-fn collect_tests_from_dir(
- config: Arc<Config>,
- dir: &Path,
- relative_dir_path: &Path,
- inputs: &Stamp,
- tests: &mut Vec<test::TestDescAndFn>,
- found_paths: &mut BTreeSet<PathBuf>,
- modified_tests: &Vec<PathBuf>,
-) -> io::Result<()> {
- // Ignore directories that contain a file named `compiletest-ignore-dir`.
- if dir.join("compiletest-ignore-dir").exists() {
- return Ok(());
- }
-
- if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
- let paths = TestPaths {
- file: dir.to_path_buf(),
- relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
- };
- tests.extend(make_test(config, &paths, inputs));
- return Ok(());
- }
-
- // If we find a test foo/bar.rs, we have to build the
- // output directory `$build/foo` so we can write
- // `$build/foo/bar` into it. We do this *now* in this
- // sequential loop because otherwise, if we do it in the
- // tests themselves, they race for the privilege of
- // creating the directories and sometimes fail randomly.
- let build_dir = output_relative_path(&config, relative_dir_path);
- fs::create_dir_all(&build_dir).unwrap();
-
- // Add each `.rs` file as a test, and recurse further on any
- // subdirectories we find, except for `aux` directories.
- for file in fs::read_dir(dir)? {
- let file = file?;
- let file_path = file.path();
- let file_name = file.file_name();
- if is_test(&file_name) && (!config.only_modified || modified_tests.contains(&file_path)) {
- debug!("found test file: {:?}", file_path.display());
- let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
- found_paths.insert(rel_test_path);
- let paths =
- TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
-
- tests.extend(make_test(config.clone(), &paths, inputs))
- } 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(),
- &file_path,
- &relative_file_path,
- inputs,
- tests,
- found_paths,
- modified_tests,
- )?;
- }
- } else {
- debug!("found other file/directory: {:?}", file_path.display());
- }
- }
- Ok(())
-}
-
-/// Returns true if `file_name` looks like a proper test file name.
-pub fn is_test(file_name: &OsString) -> bool {
- let file_name = file_name.to_str().unwrap();
-
- if !file_name.ends_with(".rs") {
- return false;
- }
-
- // `.`, `#`, and `~` are common temp-file prefixes.
- let invalid_prefixes = &[".", "#", "~"];
- !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
-}
-
-fn make_test(
- config: Arc<Config>,
- testpaths: &TestPaths,
- inputs: &Stamp,
-) -> Vec<test::TestDescAndFn> {
- let test_path = if config.mode == Mode::RunMake {
- // Parse directives in the Makefile
- testpaths.file.join("Makefile")
- } else {
- PathBuf::from(&testpaths.file)
- };
- let early_props = EarlyProps::from_file(&config, &test_path);
-
- // Incremental tests are special, they inherently cannot be run in parallel.
- // `runtest::run` will be responsible for iterating over revisions.
- let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
- vec![None]
- } else {
- early_props.revisions.iter().map(Some).collect()
- };
- revisions
- .into_iter()
- .map(|revision| {
- let src_file =
- 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);
- // Ignore tests that already run and are up to date with respect to inputs.
- if !config.force_rerun {
- desc.ignore |= is_up_to_date(
- &config,
- testpaths,
- &early_props,
- revision.map(|s| s.as_str()),
- inputs,
- );
- }
- test::TestDescAndFn {
- desc,
- testfn: make_test_closure(config.clone(), testpaths, revision),
- }
- })
- .collect()
-}
-
-fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
- output_base_dir(config, testpaths, revision).join("stamp")
-}
-
-fn files_related_to_test(
- config: &Config,
- testpaths: &TestPaths,
- props: &EarlyProps,
- revision: Option<&str>,
-) -> Vec<PathBuf> {
- let mut related = vec![];
-
- if testpaths.file.is_dir() {
- // run-make tests use their individual directory
- for entry in WalkDir::new(&testpaths.file) {
- let path = entry.unwrap().into_path();
- if path.is_file() {
- related.push(path);
- }
- }
- } else {
- related.push(testpaths.file.clone());
- }
-
- for aux in &props.aux {
- let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
- related.push(path);
- }
-
- // UI test files.
- for extension in UI_EXTENSIONS {
- let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
- related.push(path);
- }
-
- related
-}
-
-fn is_up_to_date(
- config: &Config,
- testpaths: &TestPaths,
- props: &EarlyProps,
- revision: Option<&str>,
- inputs: &Stamp,
-) -> bool {
- let stamp_name = stamp(config, testpaths, revision);
- // Check hash.
- let contents = match fs::read_to_string(&stamp_name) {
- Ok(f) => f,
- Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
- Err(_) => return false,
- };
- let expected_hash = runtest::compute_stamp_hash(config);
- if contents != expected_hash {
- return false;
- }
-
- // Check timestamps.
- let mut inputs = inputs.clone();
- for path in files_related_to_test(config, testpaths, props, revision) {
- inputs.add_path(&path);
- }
-
- inputs < Stamp::from_path(&stamp_name)
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-struct Stamp {
- time: SystemTime,
-}
-
-impl Stamp {
- fn from_path(path: &Path) -> Self {
- let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
- stamp.add_path(path);
- stamp
- }
-
- fn add_path(&mut self, path: &Path) {
- let modified = fs::metadata(path)
- .and_then(|metadata| metadata.modified())
- .unwrap_or(SystemTime::UNIX_EPOCH);
- self.time = self.time.max(modified);
- }
-
- fn add_dir(&mut self, path: &Path) {
- for entry in WalkDir::new(path) {
- let entry = entry.unwrap();
- if entry.file_type().is_file() {
- let modified = entry
- .metadata()
- .ok()
- .and_then(|metadata| metadata.modified().ok())
- .unwrap_or(SystemTime::UNIX_EPOCH);
- self.time = self.time.max(modified);
- }
- }
- }
-}
-
-fn make_test_name(
- config: &Config,
- testpaths: &TestPaths,
- revision: Option<&String>,
-) -> test::TestName {
- // Print the name of the file, relative to the repository root.
- // `src_base` looks like `/path/to/rust/tests/ui`
- let root_directory = config.src_base.parent().unwrap().parent().unwrap();
- let path = testpaths.file.strip_prefix(root_directory).unwrap();
- let debugger = match config.debugger {
- Some(d) => format!("-{}", d),
- None => String::new(),
- };
- let mode_suffix = match config.compare_mode {
- Some(ref mode) => format!(" ({})", mode.to_str()),
- None => String::new(),
- };
-
- test::DynTestName(format!(
- "[{}{}{}] {}{}",
- config.mode,
- debugger,
- mode_suffix,
- path.display(),
- revision.map_or("".to_string(), |rev| format!("#{}", rev))
- ))
-}
-
-fn make_test_closure(
- config: Arc<Config>,
- testpaths: &TestPaths,
- revision: Option<&String>,
-) -> test::TestFn {
- let config = config.clone();
- let testpaths = testpaths.clone();
- let revision = revision.cloned();
- test::DynTestFn(Box::new(move || {
- runtest::run(config, &testpaths, revision.as_deref());
- Ok(())
- }))
-}
-
-/// Returns `true` if the given target is an Android target for the
-/// purposes of GDB testing.
-fn is_android_gdb_target(target: &str) -> bool {
- matches!(
- &target[..],
- "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
- )
-}
-
-/// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
-fn is_pc_windows_msvc_target(target: &str) -> bool {
- target.ends_with("-pc-windows-msvc")
-}
-
-fn find_cdb(target: &str) -> Option<OsString> {
- if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
- return None;
- }
-
- let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
- let cdb_arch = if cfg!(target_arch = "x86") {
- "x86"
- } else if cfg!(target_arch = "x86_64") {
- "x64"
- } else if cfg!(target_arch = "aarch64") {
- "arm64"
- } else if cfg!(target_arch = "arm") {
- "arm"
- } else {
- return None; // No compatible CDB.exe in the Windows 10 SDK
- };
-
- let mut path = PathBuf::new();
- path.push(pf86);
- path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
- path.push(cdb_arch);
- path.push(r"cdb.exe");
-
- if !path.exists() {
- return None;
- }
-
- Some(path.into_os_string())
-}
-
-/// Returns Path to CDB
-fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) {
- let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
-
- let mut version = None;
- if let Some(cdb) = cdb.as_ref() {
- if let Ok(output) = Command::new(cdb).arg("/version").output() {
- if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
- version = extract_cdb_version(&first_line);
- }
- }
- }
-
- (cdb, version)
-}
-
-fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
- // Example full_version_line: "cdb version 10.0.18362.1"
- let version = full_version_line.rsplit(' ').next()?;
- let mut components = version.split('.');
- let major: u16 = components.next().unwrap().parse().unwrap();
- let minor: u16 = components.next().unwrap().parse().unwrap();
- let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
- let build: u16 = components.next().unwrap_or("0").parse().unwrap();
- Some([major, minor, patch, build])
-}
-
-/// Returns (Path to GDB, GDB Version, GDB has Rust Support)
-fn analyze_gdb(
- gdb: Option<String>,
- target: &str,
- android_cross_path: &PathBuf,
-) -> (Option<String>, Option<u32>, bool) {
- #[cfg(not(windows))]
- const GDB_FALLBACK: &str = "gdb";
- #[cfg(windows)]
- const GDB_FALLBACK: &str = "gdb.exe";
-
- const MIN_GDB_WITH_RUST: u32 = 7011010;
-
- let fallback_gdb = || {
- if is_android_gdb_target(target) {
- let mut gdb_path = match android_cross_path.to_str() {
- Some(x) => x.to_owned(),
- None => panic!("cannot find android cross path"),
- };
- gdb_path.push_str("/bin/gdb");
- gdb_path
- } else {
- GDB_FALLBACK.to_owned()
- }
- };
-
- let gdb = match gdb {
- None => fallback_gdb(),
- Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
- Some(ref s) => s.to_owned(),
- };
-
- let mut version_line = None;
- if let Ok(output) = Command::new(&gdb).arg("--version").output() {
- if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
- version_line = Some(first_line.to_string());
- }
- }
-
- let version = match version_line {
- Some(line) => extract_gdb_version(&line),
- None => return (None, None, false),
- };
-
- let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
-
- (Some(gdb), version, gdb_native_rust)
-}
-
-fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
- let full_version_line = full_version_line.trim();
-
- // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
- // of the ? sections being optional
-
- // We will parse up to 3 digits for each component, ignoring the date
-
- // We skip text in parentheses. This avoids accidentally parsing
- // the openSUSE version, which looks like:
- // GNU gdb (GDB; openSUSE Leap 15.0) 8.1
- // This particular form is documented in the GNU coding standards:
- // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
-
- let unbracketed_part = full_version_line.split('[').next().unwrap();
- let mut splits = unbracketed_part.trim_end().rsplit(' ');
- let version_string = splits.next().unwrap();
-
- let mut splits = version_string.split('.');
- let major = splits.next().unwrap();
- let minor = splits.next().unwrap();
- let patch = splits.next();
-
- let major: u32 = major.parse().unwrap();
- let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
- None => {
- let minor = minor.parse().unwrap();
- let patch: u32 = match patch {
- Some(patch) => match patch.find(not_a_digit) {
- None => patch.parse().unwrap(),
- Some(idx) if idx > 3 => 0,
- Some(idx) => patch[..idx].parse().unwrap(),
- },
- None => 0,
- };
- (minor, patch)
- }
- // There is no patch version after minor-date (e.g. "4-2012").
- Some(idx) => {
- let minor = minor[..idx].parse().unwrap();
- (minor, 0)
- }
- };
-
- Some(((major * 1000) + minor) * 1000 + patch)
-}
-
-/// Returns (LLDB version, LLDB is rust-enabled)
-fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
- // Extract the major LLDB version from the given version string.
- // LLDB version strings are different for Apple and non-Apple platforms.
- // The Apple variant looks like this:
- //
- // LLDB-179.5 (older versions)
- // lldb-300.2.51 (new versions)
- //
- // We are only interested in the major version number, so this function
- // will return `Some(179)` and `Some(300)` respectively.
- //
- // Upstream versions look like:
- // lldb version 6.0.1
- //
- // There doesn't seem to be a way to correlate the Apple version
- // with the upstream version, and since the tests were originally
- // written against Apple versions, we make a fake Apple version by
- // multiplying the first number by 100. This is a hack, but
- // normally fine because the only non-Apple version we test is
- // rust-enabled.
-
- let full_version_line = full_version_line.trim();
-
- if let Some(apple_ver) =
- full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
- {
- if let Some(idx) = apple_ver.find(not_a_digit) {
- let version: u32 = apple_ver[..idx].parse().unwrap();
- return Some((version, full_version_line.contains("rust-enabled")));
- }
- } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
- if let Some(idx) = lldb_ver.find(not_a_digit) {
- let version: u32 = lldb_ver[..idx].parse().ok()?;
- return Some((version * 100, full_version_line.contains("rust-enabled")));
- }
- }
- None
-}
-
-fn not_a_digit(c: char) -> bool {
- !c.is_digit(10)
-}
-
-fn check_overlapping_tests(found_paths: &BTreeSet<PathBuf>) {
- let mut collisions = Vec::new();
- for path in found_paths {
- for ancestor in path.ancestors().skip(1) {
- if found_paths.contains(ancestor) {
- collisions.push((path, ancestor.clone()));
- }
- }
- }
- if !collisions.is_empty() {
- let collisions: String = collisions
- .into_iter()
- .map(|(path, check_parent)| format!("test {path:?} clashes with {check_parent:?}\n"))
- .collect();
- panic!(
- "{collisions}\n\
- Tests cannot have overlapping names. Make sure they use unique prefixes."
- );
- }
-}
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index 7f0b894f5..5bc4d1642 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -32,6 +32,7 @@ use std::process::{Child, Command, ExitStatus, Output, Stdio};
use std::str;
use std::sync::Arc;
+use anyhow::Context;
use glob::glob;
use once_cell::sync::Lazy;
use tracing::*;
@@ -131,7 +132,11 @@ pub fn run(config: Arc<Config>, testpaths: &TestPaths, revision: Option<&str>) {
}
let cx = TestCx { config: &config, props: &props, testpaths, revision };
- create_dir_all(&cx.output_base_dir()).unwrap();
+ create_dir_all(&cx.output_base_dir())
+ .with_context(|| {
+ format!("failed to create output base directory {}", cx.output_base_dir().display())
+ })
+ .unwrap();
if props.incremental {
cx.init_incremental_test();
}
@@ -224,6 +229,7 @@ enum Emit {
Metadata,
LlvmIr,
Asm,
+ LinkArgsAsm,
}
impl<'test> TestCx<'test> {
@@ -1384,7 +1390,9 @@ impl<'test> TestCx<'test> {
let actual_errors = json::parse_output(&diagnostic_file_name, &proc_res.stderr, proc_res);
let mut unexpected = Vec::new();
let mut found = vec![false; expected_errors.len()];
- for actual_error in &actual_errors {
+ for mut actual_error in actual_errors {
+ actual_error.msg = self.normalize_output(&actual_error.msg, &[]);
+
let opt_index =
expected_errors.iter().enumerate().position(|(index, expected_error)| {
!found[index]
@@ -1403,7 +1411,8 @@ impl<'test> TestCx<'test> {
None => {
// If the test is a known bug, don't require that the error is annotated
- if self.is_unexpected_compiler_message(actual_error, expect_help, expect_note) {
+ if self.is_unexpected_compiler_message(&actual_error, expect_help, expect_note)
+ {
self.error(&format!(
"{}:{}: unexpected {}: '{}'",
file_name,
@@ -1479,7 +1488,16 @@ impl<'test> TestCx<'test> {
}
fn compile_test(&self, will_execute: WillExecute, emit: Emit) -> ProcRes {
- self.compile_test_general(will_execute, emit, self.props.local_pass_mode())
+ self.compile_test_general(will_execute, emit, self.props.local_pass_mode(), Vec::new())
+ }
+
+ fn compile_test_with_passes(
+ &self,
+ will_execute: WillExecute,
+ emit: Emit,
+ passes: Vec<String>,
+ ) -> ProcRes {
+ self.compile_test_general(will_execute, emit, self.props.local_pass_mode(), passes)
}
fn compile_test_general(
@@ -1487,6 +1505,7 @@ impl<'test> TestCx<'test> {
will_execute: WillExecute,
emit: Emit,
local_pm: Option<PassMode>,
+ passes: Vec<String>,
) -> ProcRes {
// Only use `make_exe_name` when the test ends up being executed.
let output_file = match will_execute {
@@ -1523,6 +1542,7 @@ impl<'test> TestCx<'test> {
emit,
allow_unused,
LinkToAux::Yes,
+ passes,
);
self.compose_and_run_compiler(rustc, None)
@@ -1773,6 +1793,7 @@ impl<'test> TestCx<'test> {
Emit::None,
AllowUnused::No,
LinkToAux::No,
+ Vec::new(),
);
for key in &aux_props.unset_rustc_env {
@@ -1904,6 +1925,7 @@ impl<'test> TestCx<'test> {
emit: Emit,
allow_unused: AllowUnused,
link_to_aux: LinkToAux,
+ passes: Vec<String>, // Vec of passes under mir-opt test to be dumped
) -> Command {
let is_aux = input_file.components().map(|c| c.as_os_str()).any(|c| c == "auxiliary");
let is_rustdoc = self.is_rustdoc() && !is_aux;
@@ -1917,6 +1939,12 @@ impl<'test> TestCx<'test> {
// Use a single thread for efficiency and a deterministic error message order
rustc.arg("-Zthreads=1");
+ // Optionally prevent default --sysroot if specified in test compile-flags.
+ if !self.props.compile_flags.iter().any(|flag| flag.starts_with("--sysroot")) {
+ // In stage 0, make sure we use `stage0-sysroot` instead of the bootstrap sysroot.
+ rustc.arg("--sysroot").arg(&self.config.sysroot_base);
+ }
+
// Optionally prevent default --target if specified in test compile-flags.
let custom_target = self.props.compile_flags.iter().any(|x| x.starts_with("--target"));
@@ -1998,9 +2026,18 @@ impl<'test> TestCx<'test> {
rustc.arg("-Cstrip=debuginfo");
}
MirOpt => {
+ // We check passes under test to minimize the mir-opt test dump
+ // if files_for_miropt_test parses the passes, we dump only those passes
+ // otherwise we conservatively pass -Zdump-mir=all
+ let zdump_arg = if !passes.is_empty() {
+ format!("-Zdump-mir={}", passes.join(" | "))
+ } else {
+ "-Zdump-mir=all".to_string()
+ };
+
rustc.args(&[
"-Copt-level=1",
- "-Zdump-mir=all",
+ &zdump_arg,
"-Zvalidate-mir",
"-Zdump-mir-exclude-pass-number",
"-Zmir-pretty-relative-line-numbers=yes",
@@ -2046,6 +2083,9 @@ impl<'test> TestCx<'test> {
Emit::Asm => {
rustc.args(&["--emit", "asm"]);
}
+ Emit::LinkArgsAsm => {
+ rustc.args(&["-Clink-args=--emit=asm"]);
+ }
}
if !is_rustdoc {
@@ -2320,6 +2360,7 @@ impl<'test> TestCx<'test> {
Emit::LlvmIr,
AllowUnused::No,
LinkToAux::Yes,
+ Vec::new(),
);
self.compose_and_run_compiler(rustc, None)
@@ -2339,16 +2380,26 @@ impl<'test> TestCx<'test> {
emit = Emit::Asm;
}
+ Some("bpf-linker") => {
+ emit = Emit::LinkArgsAsm;
+ }
+
Some("ptx-linker") => {
// No extra flags needed.
}
- Some(_) => self.fatal("unknown 'assembly-output' header"),
+ Some(header) => self.fatal(&format!("unknown 'assembly-output' header: {header}")),
None => self.fatal("missing 'assembly-output' header"),
}
- let rustc =
- self.make_compile_args(input_file, output_file, emit, AllowUnused::No, LinkToAux::Yes);
+ let rustc = self.make_compile_args(
+ input_file,
+ output_file,
+ emit,
+ AllowUnused::No,
+ LinkToAux::Yes,
+ Vec::new(),
+ );
(self.compose_and_run_compiler(rustc, None), output_path)
}
@@ -2479,6 +2530,7 @@ impl<'test> TestCx<'test> {
Emit::None,
AllowUnused::Yes,
LinkToAux::Yes,
+ Vec::new(),
);
new_rustdoc.build_all_auxiliary(&mut rustc);
@@ -3255,17 +3307,35 @@ impl<'test> TestCx<'test> {
match output_kind {
TestOutput::Compile => {
if !self.props.dont_check_compiler_stdout {
- errors +=
- self.compare_output(stdout_kind, &normalized_stdout, &expected_stdout);
+ errors += self.compare_output(
+ stdout_kind,
+ &normalized_stdout,
+ &expected_stdout,
+ self.props.compare_output_lines_by_subset,
+ );
}
if !self.props.dont_check_compiler_stderr {
- errors +=
- self.compare_output(stderr_kind, &normalized_stderr, &expected_stderr);
+ errors += self.compare_output(
+ stderr_kind,
+ &normalized_stderr,
+ &expected_stderr,
+ self.props.compare_output_lines_by_subset,
+ );
}
}
TestOutput::Run => {
- errors += self.compare_output(stdout_kind, &normalized_stdout, &expected_stdout);
- errors += self.compare_output(stderr_kind, &normalized_stderr, &expected_stderr);
+ errors += self.compare_output(
+ stdout_kind,
+ &normalized_stdout,
+ &expected_stdout,
+ self.props.compare_output_lines_by_subset,
+ );
+ errors += self.compare_output(
+ stderr_kind,
+ &normalized_stderr,
+ &expected_stderr,
+ self.props.compare_output_lines_by_subset,
+ );
}
}
errors
@@ -3275,7 +3345,8 @@ impl<'test> TestCx<'test> {
if let Some(FailMode::Build) = self.props.fail_mode {
// Make sure a build-fail test cannot fail due to failing analysis (e.g. typeck).
let pm = Some(PassMode::Check);
- let proc_res = self.compile_test_general(WillExecute::No, Emit::Metadata, pm);
+ let proc_res =
+ self.compile_test_general(WillExecute::No, Emit::Metadata, pm, Vec::new());
self.check_if_test_should_compile(&proc_res, pm);
}
@@ -3349,7 +3420,12 @@ impl<'test> TestCx<'test> {
)
});
- errors += self.compare_output("fixed", &fixed_code, &expected_fixed);
+ errors += self.compare_output(
+ "fixed",
+ &fixed_code,
+ &expected_fixed,
+ self.props.compare_output_lines_by_subset,
+ );
} else if !expected_fixed.is_empty() {
panic!(
"the `// run-rustfix` directive wasn't found but a `*.fixed` \
@@ -3439,6 +3515,7 @@ impl<'test> TestCx<'test> {
emit_metadata,
AllowUnused::No,
LinkToAux::Yes,
+ Vec::new(),
);
let res = self.compose_and_run_compiler(rustc, None);
if !res.status.success() {
@@ -3457,14 +3534,14 @@ impl<'test> TestCx<'test> {
let pm = self.pass_mode();
let should_run = self.should_run(pm);
let emit_metadata = self.should_emit_metadata(pm);
- let proc_res = self.compile_test(should_run, emit_metadata);
+ let passes = self.get_passes();
+ let proc_res = self.compile_test_with_passes(should_run, emit_metadata, passes);
+ self.check_mir_dump();
if !proc_res.status.success() {
self.fatal_proc_rec("compilation failed!", &proc_res);
}
- self.check_mir_dump();
-
if let WillExecute::Yes = should_run {
let proc_res = self.exec_compiled_test();
@@ -3474,6 +3551,26 @@ impl<'test> TestCx<'test> {
}
}
+ fn get_passes(&self) -> Vec<String> {
+ let files = miropt_test_tools::files_for_miropt_test(
+ &self.testpaths.file,
+ self.config.get_pointer_width(),
+ );
+
+ let mut out = Vec::new();
+
+ for miropt_test_tools::MiroptTestFiles {
+ from_file: _,
+ to_file: _,
+ expected_file: _,
+ passes,
+ } in files
+ {
+ out.extend(passes);
+ }
+ out
+ }
+
fn check_mir_dump(&self) {
let test_file_contents = fs::read_to_string(&self.testpaths.file).unwrap();
@@ -3503,8 +3600,9 @@ impl<'test> TestCx<'test> {
&self.testpaths.file,
self.config.get_pointer_width(),
);
-
- for miropt_test_tools::MiroptTestFiles { from_file, to_file, expected_file } in files {
+ for miropt_test_tools::MiroptTestFiles { from_file, to_file, expected_file, passes: _ } in
+ files
+ {
let dumped_string = if let Some(after) = to_file {
self.diff_mir_files(from_file.into(), after.into())
} else {
@@ -3807,11 +3905,38 @@ impl<'test> TestCx<'test> {
}
}
- fn compare_output(&self, kind: &str, actual: &str, expected: &str) -> usize {
+ fn compare_output(
+ &self,
+ kind: &str,
+ actual: &str,
+ expected: &str,
+ compare_output_by_lines: bool,
+ ) -> usize {
if actual == expected {
return 0;
}
+ let tmp;
+ let (expected, actual): (&str, &str) = if compare_output_by_lines {
+ let actual_lines: HashSet<_> = actual.lines().collect();
+ let expected_lines: Vec<_> = expected.lines().collect();
+ let mut used = expected_lines.clone();
+ used.retain(|line| actual_lines.contains(line));
+ // check if `expected` contains a subset of the lines of `actual`
+ if used.len() == expected_lines.len() && (expected.is_empty() == actual.is_empty()) {
+ return 0;
+ }
+ if expected_lines.is_empty() {
+ // if we have no lines to check, force a full overwite
+ ("", actual)
+ } else {
+ tmp = (expected_lines.join("\n"), used.join("\n"));
+ (&tmp.0, &tmp.1)
+ }
+ } else {
+ (expected, actual)
+ };
+
if !self.config.bless {
if expected.is_empty() {
println!("normalized {}:\n{}\n", kind, actual);