blob: df8c804705029021063cdda50b44bd2b26ffb413 [file] [log] [blame]
use crate::common::{CompareMode, Config, Debugger, Mode};
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>(
config: &Config,
line: &'a str,
prefix: &str,
) -> ParsedNameDirective<'a> {
if !line.as_bytes().starts_with(prefix.as_bytes()) {
return ParsedNameDirective::not_a_directive();
}
if line.as_bytes().get(prefix.len()) != Some(&b'-') {
return ParsedNameDirective::not_a_directive();
}
let line = &line[prefix.len() + 1..];
let (name, comment) =
line.split_once(&[':', ' ']).map(|(l, c)| (l, Some(c))).unwrap_or((line, None));
// Some of the matchers might be "" depending on what the target information is. To avoid
// problems we outright reject empty directives.
if name == "" {
return ParsedNameDirective::not_a_directive();
}
let mut outcome = MatchOutcome::Invalid;
let mut message = None;
macro_rules! condition {
(
name: $name:expr,
$(allowed_names: $allowed_names:expr,)?
$(condition: $condition:expr,)?
message: $($message:tt)*
) => {{
// This is not inlined to avoid problems with macro repetitions.
let format_message = || format!($($message)*);
if outcome != MatchOutcome::Invalid {
// Ignore all other matches if we already found one
} else if $name.custom_matches(name) {
message = Some(format_message());
if true $(&& $condition)? {
outcome = MatchOutcome::Match;
} else {
outcome = MatchOutcome::NoMatch;
}
}
$(else if $allowed_names.custom_contains(name) {
message = Some(format_message());
outcome = MatchOutcome::NoMatch;
})?
}};
}
let target_cfgs = config.target_cfgs();
let target_cfg = config.target_cfg();
condition! {
name: "test",
message: "always"
}
condition! {
name: &config.target,
allowed_names: &target_cfgs.all_targets,
message: "when the target is {name}"
}
condition! {
name: &[
Some(&*target_cfg.os),
// If something is ignored for emscripten, it likely also needs to be
// ignored for wasm32-unknown-unknown.
(config.target == "wasm32-unknown-unknown").then_some("emscripten"),
],
allowed_names: &target_cfgs.all_oses,
message: "when the operating system is {name}"
}
condition! {
name: &target_cfg.env,
allowed_names: &target_cfgs.all_envs,
message: "when the target environment is {name}"
}
condition! {
name: &target_cfg.os_and_env(),
allowed_names: &target_cfgs.all_oses_and_envs,
message: "when the operating system and target environment are {name}"
}
condition! {
name: &target_cfg.abi,
allowed_names: &target_cfgs.all_abis,
message: "when the ABI is {name}"
}
condition! {
name: &target_cfg.arch,
allowed_names: ContainsEither { a: &target_cfgs.all_archs, b: &EXTRA_ARCHS },
message: "when the architecture is {name}"
}
condition! {
name: format!("{}bit", target_cfg.pointer_width),
allowed_names: &target_cfgs.all_pointer_widths,
message: "when the pointer width is {name}"
}
condition! {
name: &*target_cfg.families,
allowed_names: &target_cfgs.all_families,
message: "when the target family is {name}"
}
// `wasm32-bare` is an alias to refer to just wasm32-unknown-unknown
// (in contrast to `wasm32` which also matches non-bare targets)
condition! {
name: "wasm32-bare",
condition: config.target == "wasm32-unknown-unknown",
message: "when the target is WASM"
}
condition! {
name: "thumb",
condition: config.target.starts_with("thumb"),
message: "when the architecture is part of the Thumb family"
}
// Technically the locally built compiler uses the "dev" channel rather than the "nightly"
// channel, even though most people don't know or won't care about it. To avoid confusion, we
// treat the "dev" channel as the "nightly" channel when processing the directive.
condition! {
name: if config.channel == "dev" { "nightly" } else { &config.channel },
allowed_names: &["stable", "beta", "nightly"],
message: "when the release channel is {name}",
}
condition! {
name: "cross-compile",
condition: config.target != config.host,
message: "when cross-compiling"
}
condition! {
name: "endian-big",
condition: config.is_big_endian(),
message: "on big-endian targets",
}
condition! {
name: config.stage_id.split('-').next().unwrap(),
allowed_names: &["stage0", "stage1", "stage2"],
message: "when the bootstrapping stage is {name}",
}
condition! {
name: "remote",
condition: config.remote_test_client.is_some(),
message: "when running tests remotely",
}
condition! {
name: "debug",
condition: config.with_debug_assertions,
message: "when running tests with `ignore-debug` header",
}
condition! {
name: config.debugger.as_ref().map(|d| d.to_str()),
allowed_names: &Debugger::STR_VARIANTS,
message: "when the debugger is {name}",
}
condition! {
name: config.compare_mode
.as_ref()
.map(|d| format!("compare-mode-{}", d.to_str())),
allowed_names: ContainsPrefixed {
prefix: "compare-mode-",
inner: CompareMode::STR_VARIANTS,
},
message: "when comparing with {name}",
}
// Coverage tests run the same test file in multiple modes.
// If a particular test should not be run in one of the modes, ignore it
// with "ignore-mode-coverage-map" or "ignore-mode-coverage-run".
condition! {
name: format!("mode-{}", config.mode.to_str()),
allowed_names: ContainsPrefixed {
prefix: "mode-",
inner: Mode::STR_VARIANTS,
},
message: "when the test mode is {name}",
}
if prefix == "ignore" && outcome == MatchOutcome::Invalid {
// Don't error out for ignore-tidy-* diretives, as those are not handled by compiletest.
if name.starts_with("tidy-") {
outcome = MatchOutcome::External;
}
// Don't error out for ignore-pass, as that is handled elsewhere.
if name == "pass" {
outcome = MatchOutcome::External;
}
// Don't error out for ignore-llvm-version, that has a custom syntax and is handled
// elsewhere.
if name == "llvm-version" {
outcome = MatchOutcome::External;
}
// Don't error out for ignore-llvm-version, that has a custom syntax and is handled
// elsewhere.
if name == "gdb-version" {
outcome = MatchOutcome::External;
}
}
ParsedNameDirective {
name: Some(name),
comment: comment.map(|c| c.trim().trim_start_matches('-').trim()),
outcome,
pretty_reason: message,
}
}
/// The result of parse_cfg_name_directive.
#[derive(Clone, PartialEq, Debug)]
pub(super) struct ParsedNameDirective<'a> {
pub(super) name: Option<&'a str>,
pub(super) pretty_reason: Option<String>,
pub(super) comment: Option<&'a str>,
pub(super) outcome: MatchOutcome,
}
impl ParsedNameDirective<'_> {
fn not_a_directive() -> Self {
Self {
name: None,
pretty_reason: None,
comment: None,
outcome: MatchOutcome::NotADirective,
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub(super) enum MatchOutcome {
/// No match.
NoMatch,
/// Match.
Match,
/// The directive was invalid.
Invalid,
/// The directive is handled by other parts of our tooling.
External,
/// The line is not actually a directive.
NotADirective,
}
trait CustomContains {
fn custom_contains(&self, item: &str) -> bool;
}
impl CustomContains for HashSet<String> {
fn custom_contains(&self, item: &str) -> bool {
self.contains(item)
}
}
impl CustomContains for &[&str] {
fn custom_contains(&self, item: &str) -> bool {
self.contains(&item)
}
}
impl<const N: usize> CustomContains for [&str; N] {
fn custom_contains(&self, item: &str) -> bool {
self.contains(&item)
}
}
struct ContainsPrefixed<T: CustomContains> {
prefix: &'static str,
inner: T,
}
impl<T: CustomContains> CustomContains for ContainsPrefixed<T> {
fn custom_contains(&self, item: &str) -> bool {
match item.strip_prefix(self.prefix) {
Some(stripped) => self.inner.custom_contains(stripped),
None => false,
}
}
}
struct ContainsEither<'a, A: CustomContains, B: CustomContains> {
a: &'a A,
b: &'a B,
}
impl<A: CustomContains, B: CustomContains> CustomContains for ContainsEither<'_, A, B> {
fn custom_contains(&self, item: &str) -> bool {
self.a.custom_contains(item) || self.b.custom_contains(item)
}
}
trait CustomMatches {
fn custom_matches(&self, name: &str) -> bool;
}
impl CustomMatches for &str {
fn custom_matches(&self, name: &str) -> bool {
name == *self
}
}
impl CustomMatches for String {
fn custom_matches(&self, name: &str) -> bool {
name == self
}
}
impl<T: CustomMatches> CustomMatches for &[T] {
fn custom_matches(&self, name: &str) -> bool {
self.iter().any(|m| m.custom_matches(name))
}
}
impl<const N: usize, T: CustomMatches> CustomMatches for [T; N] {
fn custom_matches(&self, name: &str) -> bool {
self.iter().any(|m| m.custom_matches(name))
}
}
impl<T: CustomMatches> CustomMatches for Option<T> {
fn custom_matches(&self, name: &str) -> bool {
match self {
Some(inner) => inner.custom_matches(name),
None => false,
}
}
}