From 837bf8427165d6e56783a97a57061416d1ac2fec Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 13 Jul 2022 13:09:23 +0000 Subject: [PATCH] Error patterns can be regexes --- .../deallocate_against_barrier1.rs | 2 +- .../deallocate_against_barrier2.rs | 2 +- ui_test/README.md | 1 + ui_test/src/lib.rs | 24 ++++++------ ui_test/src/parser.rs | 39 ++++++++++++++++--- ui_test/src/parser/tests.rs | 29 +++++++++++--- 6 files changed, 73 insertions(+), 24 deletions(-) diff --git a/tests/fail/stacked_borrows/deallocate_against_barrier1.rs b/tests/fail/stacked_borrows/deallocate_against_barrier1.rs index 20e026df7b9..9b710424c55 100644 --- a/tests/fail/stacked_borrows/deallocate_against_barrier1.rs +++ b/tests/fail/stacked_borrows/deallocate_against_barrier1.rs @@ -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! diff --git a/tests/fail/stacked_borrows/deallocate_against_barrier2.rs b/tests/fail/stacked_borrows/deallocate_against_barrier2.rs index 9cb2d52bf2e..36e133e3836 100644 --- a/tests/fail/stacked_borrows/deallocate_against_barrier2.rs +++ b/tests/fail/stacked_borrows/deallocate_against_barrier2.rs @@ -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); diff --git a/ui_test/README.md b/ui_test/README.md index 24c5940382d..55639c9589e 100644 --- a/ui_test/README.md +++ b/ui_test/README.md @@ -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 diff --git a/ui_test/src/lib.rs b/ui_test/src/lib.rs index b2e71b762be..32c04290bca 100644 --- a/ui_test/src/lib.rs +++ b/ui_test/src/lib.rs @@ -10,7 +10,7 @@ use std::sync::Mutex; 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| -> Vec<_> { diff --git a/ui_test/src/parser.rs b/ui_test/src/parser.rs index 5b666eef66e..7d810a95e41 100644 --- a/ui_test/src/parser.rs +++ b/ui_test/src/parser.rs @@ -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, /// 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, 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 @@ impl Comments { "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 @@ impl Comments { 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 @@ impl Comments { 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 { + 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())) + } + } +} diff --git a/ui_test/src/parser/tests.rs b/ui_test/src/parser/tests.rs index f41c2f234ab..343857d44bd 100644 --- a/ui_test/src/parser/tests.rs +++ b/ui_test/src/parser/tests.rs @@ -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 @@ use std::mem; "; let comments = Comments::parse(Path::new(""), 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(""), 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]