diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index 496b83a956b9..9df5c298757d 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -9,6 +9,7 @@ use std::str::FromStr; use crate::util::{add_dylib_path, PathBufExt}; use lazycell::LazyCell; +use std::collections::HashSet; use test::{ColorConfig, OutputFormat}; #[derive(Clone, Copy, PartialEq, Debug)] @@ -129,6 +130,14 @@ pub enum CompareMode { } impl CompareMode { + pub(crate) const VARIANTS: &'static [CompareMode] = &[ + CompareMode::Polonius, + CompareMode::Chalk, + CompareMode::NextSolver, + CompareMode::SplitDwarf, + CompareMode::SplitDwarfSingle, + ]; + pub(crate) fn to_str(&self) -> &'static str { match *self { CompareMode::Polonius => "polonius", @@ -159,6 +168,9 @@ pub enum Debugger { } impl Debugger { + pub(crate) const VARIANTS: &'static [Debugger] = + &[Debugger::Cdb, Debugger::Gdb, Debugger::Lldb]; + pub(crate) fn to_str(&self) -> &'static str { match self { Debugger::Cdb => "cdb", @@ -396,8 +408,12 @@ impl Config { }) } + pub fn target_cfgs(&self) -> &TargetCfgs { + self.target_cfgs.borrow_with(|| TargetCfgs::new(self)) + } + pub fn target_cfg(&self) -> &TargetCfg { - self.target_cfg.borrow_with(|| TargetCfg::new(self)) + &self.target_cfgs().current } pub fn matches_arch(&self, arch: &str) -> bool { @@ -449,6 +465,63 @@ impl Config { } } +#[derive(Debug, Clone)] +pub struct TargetCfgs { + pub current: TargetCfg, + pub all_targets: HashSet, + pub all_archs: HashSet, + pub all_oses: HashSet, + pub all_envs: HashSet, + pub all_abis: HashSet, + pub all_families: HashSet, + pub all_pointer_widths: HashSet, +} + +impl TargetCfgs { + fn new(config: &Config) -> TargetCfgs { + // Gather list of all targets + let targets = rustc_output(config, &["--print=target-list"]); + + let mut current = None; + let mut all_targets = HashSet::new(); + let mut all_archs = HashSet::new(); + let mut all_oses = HashSet::new(); + let mut all_envs = HashSet::new(); + let mut all_abis = HashSet::new(); + let mut all_families = HashSet::new(); + let mut all_pointer_widths = HashSet::new(); + + for target in targets.trim().lines() { + let cfg = TargetCfg::new(config, target); + + all_archs.insert(cfg.arch.clone()); + all_oses.insert(cfg.os.clone()); + all_envs.insert(cfg.env.clone()); + all_abis.insert(cfg.abi.clone()); + for family in &cfg.families { + all_families.insert(family.clone()); + } + 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"), + all_targets, + all_archs, + all_oses, + all_envs, + all_abis, + all_families, + all_pointer_widths, + } + } +} + #[derive(Clone, Debug)] pub struct TargetCfg { pub(crate) arch: String, @@ -468,28 +541,8 @@ pub enum Endian { } impl TargetCfg { - fn new(config: &Config) -> TargetCfg { - let mut command = Command::new(&config.rustc_path); - add_dylib_path(&mut command, iter::once(&config.compile_lib_path)); - let output = match command - .arg("--print=cfg") - .arg("--target") - .arg(&config.target) - .args(&config.target_rustcflags) - .output() - { - Ok(output) => output, - Err(e) => panic!("error: failed to get cfg info from {:?}: {e}", config.rustc_path), - }; - if !output.status.success() { - panic!( - "error: failed to get cfg info from {:?}\n--- stdout\n{}\n--- stderr\n{}", - config.rustc_path, - String::from_utf8(output.stdout).unwrap(), - String::from_utf8(output.stderr).unwrap(), - ); - } - let print_cfg = String::from_utf8(output.stdout).unwrap(); + fn new(config: &Config, target: &str) -> TargetCfg { + let print_cfg = rustc_output(config, &["--print=cfg", "--target", target]); let mut arch = None; let mut os = None; let mut env = None; @@ -539,6 +592,25 @@ impl TargetCfg { } } +fn rustc_output(config: &Config, args: &[&str]) -> String { + let mut command = Command::new(&config.rustc_path); + add_dylib_path(&mut command, iter::once(&config.compile_lib_path)); + command.args(&config.target_rustcflags).args(args); + + let output = match command.output() { + Ok(output) => output, + Err(e) => panic!("error: failed to run {command:?}: {e}"), + }; + if !output.status.success() { + panic!( + "error: failed to run {command:?}\n--- stdout\n{}\n--- stderr\n{}", + String::from_utf8(output.stdout).unwrap(), + String::from_utf8(output.stderr).unwrap(), + ); + } + String::from_utf8(output.stdout).unwrap() +} + #[derive(Debug, Clone)] pub struct TestPaths { pub file: PathBuf, // e.g., compile-test/foo/bar/baz.rs diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index ea900ff068fc..71e1072ceb37 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -8,6 +8,7 @@ use std::process::Command; use tracing::*; +use crate::common::CompareMode; use crate::common::{Config, Debugger, FailMode, Mode, PassMode}; use crate::util; use crate::{extract_cdb_version, extract_gdb_version}; @@ -36,6 +37,10 @@ enum MatchOutcome { NoMatch, /// Match. Match, + /// The directive was invalid. + Invalid, + /// The directive is handled by other parts of our tooling. + External, } /// Properties which must be known very early, before actually running @@ -692,26 +697,60 @@ impl Config { let (name, comment) = line.split_once(&[':', ' ']).map(|(l, c)| (l, Some(c))).unwrap_or((line, None)); - let mut is_match = None; + let mut outcome = MatchOutcome::Invalid; + let mut message = None; macro_rules! maybe_condition { - (name: $name:expr, $(condition: $condition:expr,)? message: $($message:tt)*) => { - if let Some(expected) = $name { - if name == expected $(&& $condition)? { - is_match = Some(format!($($message)*)); + ( + name: $name:expr, + $(allowed_names: $allowed_names:expr,)? + $(condition: $condition:expr,)? + message: $($message:tt)* + ) => {{ + // This is not inlined to avoid problems with macro repetitions. + let format_message = || format!($($message)*); + + if outcome != MatchOutcome::Invalid { + // Ignore all other matches if we already found one + } else if $name.as_ref().map(|n| n == &name).unwrap_or(false) { + message = Some(format_message()); + if true $(&& $condition)? { + outcome = MatchOutcome::Match; + } else { + outcome = MatchOutcome::NoMatch; } } - }; + $(else if $allowed_names.contains(name) { + message = Some(format_message()); + outcome = MatchOutcome::NoMatch; + })? + }}; } macro_rules! condition { - (name: $name:expr, $(condition: $condition:expr,)? message: $($message:tt)*) => { + ( + name: $name:expr, + $(allowed_names: $allowed_names:expr,)? + $(condition: $condition:expr,)? + message: $($message:tt)* + ) => { maybe_condition! { name: Some($name), + $(allowed_names: $allowed_names,)* $(condition: $condition,)* message: $($message)* } }; } + macro_rules! hashset { + ($($value:expr),* $(,)?) => {{ + let mut set = HashSet::new(); + $(set.insert($value);)* + set + }} + } + + let target_cfgs = self.target_cfgs(); + let target_cfg = self.target_cfg(); condition! { name: "test", @@ -719,33 +758,38 @@ impl Config { } condition! { name: &self.target, + allowed_names: &target_cfgs.all_targets, message: "when the target is {name}" } - - let target_cfg = self.target_cfg(); condition! { name: &target_cfg.os, + allowed_names: &target_cfgs.all_oses, message: "when the operative system is {name}" } condition! { name: &target_cfg.env, + allowed_names: &target_cfgs.all_envs, message: "when the target environment is {name}" } condition! { name: &target_cfg.abi, + allowed_names: &target_cfgs.all_abis, message: "when the ABI is {name}" } condition! { name: &target_cfg.arch, + allowed_names: &target_cfgs.all_archs, message: "when the architecture is {name}" } condition! { name: format!("{}bit", target_cfg.pointer_width), + allowed_names: &target_cfgs.all_pointer_widths, message: "when the pointer width is {name}" } for family in &target_cfg.families { condition! { name: family, + allowed_names: &target_cfgs.all_families, message: "when the target family is {name}" } } @@ -768,6 +812,7 @@ impl Config { condition! { name: &self.channel, + allowed_names: hashset!["stable", "beta", "nightly"], message: "when the release channel is {name}", } condition! { @@ -782,6 +827,7 @@ impl Config { } condition! { name: self.stage_id.split('-').next().unwrap(), + allowed_names: hashset!["stable", "beta", "nightly"], message: "when the bootstrapping stage is {name}", } condition! { @@ -796,20 +842,38 @@ impl Config { } maybe_condition! { name: self.debugger.as_ref().map(|d| d.to_str()), + allowed_names: Debugger::VARIANTS + .iter() + .map(|v| v.to_str()) + .collect::>(), message: "when the debugger is {name}", } maybe_condition! { name: self.compare_mode .as_ref() .map(|d| format!("compare-mode-{}", d.to_str())), + allowed_names: CompareMode::VARIANTS + .iter() + .map(|cm| format!("compare-mode-{}", cm.to_str())) + .collect::>(), message: "when comparing with {name}", } + // Don't error out for ignore-tidy-* diretives, as those are not handled by compiletest. + if prefix == "ignore" && name.starts_with("tidy-") && outcome == MatchOutcome::Invalid { + outcome = MatchOutcome::External; + } + + // Don't error out for ignore-pass, as that is handled elsewhere. + if prefix == "ignore" && name == "pass" && outcome == MatchOutcome::Invalid { + outcome = MatchOutcome::External; + } + ParsedNameDirective { name: Some(name), comment: comment.map(|c| c.trim().trim_start_matches('-').trim()), - outcome: if is_match.is_some() { MatchOutcome::Match } else { MatchOutcome::NoMatch }, - pretty_reason: is_match, + outcome, + pretty_reason: message, } } @@ -1095,6 +1159,8 @@ pub fn make_test_description( true } MatchOutcome::NoMatch => ignore, + MatchOutcome::External => ignore, + MatchOutcome::Invalid => panic!("invalid line in {}: {ln}", path.display()), }; } @@ -1103,16 +1169,18 @@ pub fn make_test_description( ignore = match parsed.outcome { MatchOutcome::Match => ignore, MatchOutcome::NoMatch => { - let name = parsed.name.unwrap(); + let reason = parsed.pretty_reason.unwrap(); // The ignore reason must be a &'static str, so we have to leak memory to // create it. This is fine, as the header is parsed only at the start of // compiletest so it won't grow indefinitely. ignore_message = Some(Box::leak(Box::::from(match parsed.comment { - Some(comment) => format!("did not match only-{name} ({comment})"), - None => format!("did not match only-{name}"), + 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()), }; } diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index 7048b0e08bbf..b10c2d36d602 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -311,7 +311,7 @@ pub fn parse_config(args: Vec) -> Config { force_rerun: matches.opt_present("force-rerun"), - target_cfg: LazyCell::new(), + target_cfgs: LazyCell::new(), nocapture: matches.opt_present("nocapture"), }