Error patterns can be regexes

This commit is contained in:
Oli Scherer 2022-07-13 13:09:23 +00:00
parent db5a2b9747
commit 837bf84271
6 changed files with 73 additions and 24 deletions

View File

@ -1,4 +1,4 @@
//@error-pattern: deallocating while item //@error-pattern: /deallocating while item \[Unique for .*\] is protected/
fn inner(x: &mut i32, f: fn(&mut i32)) { fn inner(x: &mut i32, f: fn(&mut i32)) {
// `f` may mutate, but it may not deallocate! // `f` may mutate, but it may not deallocate!

View File

@ -1,4 +1,4 @@
//@error-pattern: deallocating while item //@error-pattern: /deallocating while item \[SharedReadWrite for .*\] is protected/
use std::marker::PhantomPinned; use std::marker::PhantomPinned;
pub struct NotUnpin(i32, PhantomPinned); pub struct NotUnpin(i32, PhantomPinned);

View File

@ -17,6 +17,7 @@ to make sure that the test will always keep failing with a specific message at t
* If the all caps note is left out, a message of any level is matched. Leaving it out is not allowed for `ERROR` levels. * If the all caps note is left out, a message of any level is matched. Leaving it out is not allowed for `ERROR` levels.
* This checks the output *before* normalization, so you can check things that get normalized away, but need to * This checks the output *before* normalization, so you can check things that get normalized away, but need to
be careful not to accidentally have a pattern that differs between platforms. be careful not to accidentally have a pattern that differs between platforms.
* if `XXX` is of the form `/XXX/` it is treated as a regex instead of a substring and will succeed if the regex matches.
In order to change how a single test is tested, you can add various `//@` comments to the test. In order to change how a single test is tested, you can add various `//@` comments to the test.
Any other comments will be ignored, and all `//@` comments must be formatted precisely as Any other comments will be ignored, and all `//@` comments must be formatted precisely as

View File

@ -10,7 +10,7 @@ use std::sync::Mutex;
pub use color_eyre; pub use color_eyre;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use colored::*; use colored::*;
use parser::ErrorMatch; use parser::{ErrorMatch, Pattern};
use regex::Regex; use regex::Regex;
use rustc_stderr::{Level, Message}; use rustc_stderr::{Level, Message};
@ -177,7 +177,12 @@ pub fn run_tests(config: Config) -> Result<()> {
match error { match error {
Error::ExitStatus(mode, exit_status) => eprintln!("{mode:?} got {exit_status}"), Error::ExitStatus(mode, exit_status) => eprintln!("{mode:?} got {exit_status}"),
Error::PatternNotFound { pattern, definition_line } => { Error::PatternNotFound { pattern, definition_line } => {
eprintln!("`{pattern}` {} in stderr output", "not found".red()); match pattern {
Pattern::SubString(s) =>
eprintln!("substring `{s}` {} in stderr output", "not found".red()),
Pattern::Regex(r) =>
eprintln!("`/{r}/` does {} stderr output", "not match".red()),
}
eprintln!( eprintln!(
"expected because of pattern here: {}:{definition_line}", "expected because of pattern here: {}:{definition_line}",
path.display().to_string().bold() path.display().to_string().bold()
@ -257,7 +262,7 @@ enum Error {
/// Got an invalid exit status for the given mode. /// Got an invalid exit status for the given mode.
ExitStatus(Mode, ExitStatus), ExitStatus(Mode, ExitStatus),
PatternNotFound { PatternNotFound {
pattern: String, pattern: Pattern,
definition_line: usize, definition_line: usize,
}, },
/// A ui test checking for failure does not have any failure patterns /// A ui test checking for failure does not have any failure patterns
@ -384,14 +389,11 @@ fn check_annotations(
// in the messages. // in the messages.
if let Some(i) = messages_from_unknown_file_or_line if let Some(i) = messages_from_unknown_file_or_line
.iter() .iter()
.position(|msg| msg.message.contains(error_pattern)) .position(|msg| error_pattern.matches(&msg.message))
{ {
messages_from_unknown_file_or_line.remove(i); messages_from_unknown_file_or_line.remove(i);
} else { } else {
errors.push(Error::PatternNotFound { errors.push(Error::PatternNotFound { pattern: error_pattern.clone(), definition_line });
pattern: error_pattern.to_string(),
definition_line,
});
} }
} }
@ -399,7 +401,7 @@ fn check_annotations(
// We will ensure that *all* diagnostics of level at least `lowest_annotation_level` // We will ensure that *all* diagnostics of level at least `lowest_annotation_level`
// are matched. // are matched.
let mut lowest_annotation_level = Level::Error; let mut lowest_annotation_level = Level::Error;
for &ErrorMatch { ref matched, revision: ref rev, definition_line, line, level } in for &ErrorMatch { ref pattern, revision: ref rev, definition_line, line, level } in
&comments.error_matches &comments.error_matches
{ {
if let Some(rev) = rev { if let Some(rev) = rev {
@ -415,14 +417,14 @@ fn check_annotations(
if let Some(msgs) = messages.get_mut(line) { if let Some(msgs) = messages.get_mut(line) {
let found = let found =
msgs.iter().position(|msg| msg.message.contains(matched) && msg.level == level); msgs.iter().position(|msg| pattern.matches(&msg.message) && msg.level == level);
if let Some(found) = found { if let Some(found) = found {
msgs.remove(found); msgs.remove(found);
continue; continue;
} }
} }
errors.push(Error::PatternNotFound { pattern: matched.to_string(), definition_line }); errors.push(Error::PatternNotFound { pattern: pattern.clone(), definition_line });
} }
let filter = |msgs: Vec<Message>| -> Vec<_> { let filter = |msgs: Vec<Message>| -> Vec<_> {

View File

@ -29,7 +29,7 @@ pub(crate) struct Comments {
/// Normalizations to apply to the stderr output before emitting it to disk /// Normalizations to apply to the stderr output before emitting it to disk
pub normalize_stderr: Vec<(Regex, String)>, pub normalize_stderr: Vec<(Regex, String)>,
/// An arbitrary pattern to look for in the stderr. /// An arbitrary pattern to look for in the stderr.
pub error_pattern: Option<(String, usize)>, pub error_pattern: Option<(Pattern, usize)>,
pub error_matches: Vec<ErrorMatch>, pub error_matches: Vec<ErrorMatch>,
/// Ignore diagnostics below this level. /// Ignore diagnostics below this level.
/// `None` means pick the lowest level from the `error_pattern`s. /// `None` means pick the lowest level from the `error_pattern`s.
@ -45,9 +45,15 @@ pub(crate) enum Condition {
Bitwidth(u8), Bitwidth(u8),
} }
#[derive(Debug, Clone)]
pub(crate) enum Pattern {
SubString(String),
Regex(Regex),
}
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct ErrorMatch { pub(crate) struct ErrorMatch {
pub matched: String, pub pattern: Pattern,
pub revision: Option<String>, pub revision: Option<String>,
pub level: Level, pub level: Level,
/// The line where the message was defined, for reporting issues with it (e.g. in case it wasn't found). /// The line where the message was defined, for reporting issues with it (e.g. in case it wasn't found).
@ -184,7 +190,7 @@ impl Comments {
"cannot specifiy error_pattern twice, previous: {:?}", "cannot specifiy error_pattern twice, previous: {:?}",
self.error_pattern self.error_pattern
); );
self.error_pattern = Some((args.trim().to_string(), l)); self.error_pattern = Some((Pattern::parse(args.trim())?, l));
} }
"stderr-per-bitwidth" => { "stderr-per-bitwidth" => {
// args are ignored (can be used as comment) // args are ignored (can be used as comment)
@ -275,14 +281,16 @@ impl Comments {
let pattern = &pattern[offset..]; let pattern = &pattern[offset..];
let pattern = pattern.strip_prefix(':').ok_or_else(|| eyre!("no `:` after level found"))?; let pattern = pattern.strip_prefix(':').ok_or_else(|| eyre!("no `:` after level found"))?;
let matched = pattern.trim().to_string(); let pattern = pattern.trim();
ensure!(!matched.is_empty(), "no pattern specified"); ensure!(!pattern.is_empty(), "no pattern specified");
let pattern = Pattern::parse(pattern)?;
*fallthrough_to = Some(match_line); *fallthrough_to = Some(match_line);
self.error_matches.push(ErrorMatch { self.error_matches.push(ErrorMatch {
matched, pattern,
revision, revision,
level, level,
definition_line: l, definition_line: l,
@ -292,3 +300,22 @@ impl Comments {
Ok(()) Ok(())
} }
} }
impl Pattern {
pub(crate) fn matches(&self, message: &str) -> bool {
match self {
Pattern::SubString(s) => message.contains(s),
Pattern::Regex(r) => r.is_match(message),
}
}
pub(crate) fn parse(pattern: &str) -> Result<Self> {
if let Some(pattern) = pattern.strip_prefix('/') {
let regex =
pattern.strip_suffix('/').ok_or_else(|| eyre!("regex must end with `/`"))?;
Ok(Pattern::Regex(Regex::new(regex)?))
} else {
Ok(Pattern::SubString(pattern.to_string()))
}
}
}

View File

@ -1,5 +1,7 @@
use std::path::Path; use std::path::Path;
use crate::parser::Pattern;
use super::Comments; use super::Comments;
#[test] #[test]
@ -15,10 +17,11 @@ fn main() {
println!("parsed comments: {:#?}", comments); println!("parsed comments: {:#?}", comments);
assert_eq!(comments.error_matches[0].definition_line, 5); assert_eq!(comments.error_matches[0].definition_line, 5);
assert_eq!(comments.error_matches[0].revision, None); assert_eq!(comments.error_matches[0].revision, None);
assert_eq!( match &comments.error_matches[0].pattern {
comments.error_matches[0].matched, Pattern::SubString(s) =>
"encountered a dangling reference (address $HEX is unallocated)" assert_eq!(s, "encountered a dangling reference (address $HEX is unallocated)"),
); other => panic!("expected substring, got {other:?}"),
}
} }
#[test] #[test]
@ -42,7 +45,23 @@ use std::mem;
"; ";
let comments = Comments::parse(Path::new("<dummy>"), s).unwrap(); let comments = Comments::parse(Path::new("<dummy>"), s).unwrap();
println!("parsed comments: {:#?}", comments); println!("parsed comments: {:#?}", comments);
assert_eq!(comments.error_pattern, Some(("foomp".to_string(), 2))); let pat = comments.error_pattern.unwrap();
assert_eq!(format!("{:?}", pat.0), r#"SubString("foomp")"#);
assert_eq!(pat.1, 2);
}
#[test]
fn parse_regex_error_pattern() {
let s = r"
//@ error-pattern: /foomp/
use std::mem;
";
let comments = Comments::parse(Path::new("<dummy>"), s).unwrap();
println!("parsed comments: {:#?}", comments);
let pat = comments.error_pattern.unwrap();
assert_eq!(format!("{:?}", pat.0), r#"Regex(foomp)"#);
assert_eq!(pat.1, 2);
} }
#[test] #[test]