Error patterns can be regexes
This commit is contained in:
parent
db5a2b9747
commit
837bf84271
@ -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!
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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<_> {
|
||||||
|
@ -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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user