Auto merge of #2364 - rust-lang:regex_error_annotations, r=RalfJung

Error patterns can be regexes

fixes #2362
This commit is contained in:
bors 2022-07-13 16:53:38 +00:00
commit 4e9de31174
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)) {
// `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;
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.
* 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.
* 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.
Any other comments will be ignored, and all `//@` comments must be formatted precisely as

View File

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

View File

@ -29,7 +29,7 @@ pub(crate) struct Comments {
/// Normalizations to apply to the stderr output before emitting it to disk
pub normalize_stderr: Vec<(Regex, String)>,
/// 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>,
/// Ignore diagnostics below this level.
/// `None` means pick the lowest level from the `error_pattern`s.
@ -45,9 +45,15 @@ pub(crate) enum Condition {
Bitwidth(u8),
}
#[derive(Debug, Clone)]
pub(crate) enum Pattern {
SubString(String),
Regex(Regex),
}
#[derive(Debug)]
pub(crate) struct ErrorMatch {
pub matched: String,
pub pattern: Pattern,
pub revision: Option<String>,
pub level: Level,
/// The line where the message was defined, for reporting issues with it (e.g. in case it wasn't found).
@ -184,7 +190,7 @@ fn parse_str(s: &str) -> Result<(&str, &str)> {
"cannot specifiy error_pattern twice, previous: {:?}",
self.error_pattern
);
self.error_pattern = Some((args.trim().to_string(), l));
self.error_pattern = Some((Pattern::parse(args.trim())?, l));
}
"stderr-per-bitwidth" => {
// args are ignored (can be used as comment)
@ -275,14 +281,16 @@ fn parse_pattern_inner(
let pattern = &pattern[offset..];
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);
self.error_matches.push(ErrorMatch {
matched,
pattern,
revision,
level,
definition_line: l,
@ -292,3 +300,22 @@ fn parse_pattern_inner(
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 crate::parser::Pattern;
use super::Comments;
#[test]
@ -15,10 +17,11 @@ fn main() {
println!("parsed comments: {:#?}", comments);
assert_eq!(comments.error_matches[0].definition_line, 5);
assert_eq!(comments.error_matches[0].revision, None);
assert_eq!(
comments.error_matches[0].matched,
"encountered a dangling reference (address $HEX is unallocated)"
);
match &comments.error_matches[0].pattern {
Pattern::SubString(s) =>
assert_eq!(s, "encountered a dangling reference (address $HEX is unallocated)"),
other => panic!("expected substring, got {other:?}"),
}
}
#[test]
@ -42,7 +45,23 @@ fn parse_slash_slash_at() {
";
let comments = Comments::parse(Path::new("<dummy>"), s).unwrap();
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]