Auto merge of #2364 - rust-lang:regex_error_annotations, r=RalfJung
Error patterns can be regexes fixes #2362
This commit is contained in:
commit
4e9de31174
@ -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!
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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<_> {
|
||||
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user