Skip to content

Commit 4e9de31

Browse files
committed
Auto merge of rust-lang#2364 - rust-lang:regex_error_annotations, r=RalfJung
Error patterns can be regexes fixes rust-lang#2362
2 parents a45d6ef + 837bf84 commit 4e9de31

File tree

6 files changed

+73
-24
lines changed

6 files changed

+73
-24
lines changed

tests/fail/stacked_borrows/deallocate_against_barrier1.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//@error-pattern: deallocating while item
1+
//@error-pattern: /deallocating while item \[Unique for .*\] is protected/
22

33
fn inner(x: &mut i32, f: fn(&mut i32)) {
44
// `f` may mutate, but it may not deallocate!

tests/fail/stacked_borrows/deallocate_against_barrier2.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//@error-pattern: deallocating while item
1+
//@error-pattern: /deallocating while item \[SharedReadWrite for .*\] is protected/
22
use std::marker::PhantomPinned;
33

44
pub struct NotUnpin(i32, PhantomPinned);

ui_test/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ to make sure that the test will always keep failing with a specific message at t
1717
* If the all caps note is left out, a message of any level is matched. Leaving it out is not allowed for `ERROR` levels.
1818
* This checks the output *before* normalization, so you can check things that get normalized away, but need to
1919
be careful not to accidentally have a pattern that differs between platforms.
20+
* if `XXX` is of the form `/XXX/` it is treated as a regex instead of a substring and will succeed if the regex matches.
2021

2122
In order to change how a single test is tested, you can add various `//@` comments to the test.
2223
Any other comments will be ignored, and all `//@` comments must be formatted precisely as

ui_test/src/lib.rs

+13-11
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::sync::Mutex;
1010
pub use color_eyre;
1111
use color_eyre::eyre::Result;
1212
use colored::*;
13-
use parser::ErrorMatch;
13+
use parser::{ErrorMatch, Pattern};
1414
use regex::Regex;
1515
use rustc_stderr::{Level, Message};
1616

@@ -177,7 +177,12 @@ pub fn run_tests(config: Config) -> Result<()> {
177177
match error {
178178
Error::ExitStatus(mode, exit_status) => eprintln!("{mode:?} got {exit_status}"),
179179
Error::PatternNotFound { pattern, definition_line } => {
180-
eprintln!("`{pattern}` {} in stderr output", "not found".red());
180+
match pattern {
181+
Pattern::SubString(s) =>
182+
eprintln!("substring `{s}` {} in stderr output", "not found".red()),
183+
Pattern::Regex(r) =>
184+
eprintln!("`/{r}/` does {} stderr output", "not match".red()),
185+
}
181186
eprintln!(
182187
"expected because of pattern here: {}:{definition_line}",
183188
path.display().to_string().bold()
@@ -257,7 +262,7 @@ enum Error {
257262
/// Got an invalid exit status for the given mode.
258263
ExitStatus(Mode, ExitStatus),
259264
PatternNotFound {
260-
pattern: String,
265+
pattern: Pattern,
261266
definition_line: usize,
262267
},
263268
/// A ui test checking for failure does not have any failure patterns
@@ -384,22 +389,19 @@ fn check_annotations(
384389
// in the messages.
385390
if let Some(i) = messages_from_unknown_file_or_line
386391
.iter()
387-
.position(|msg| msg.message.contains(error_pattern))
392+
.position(|msg| error_pattern.matches(&msg.message))
388393
{
389394
messages_from_unknown_file_or_line.remove(i);
390395
} else {
391-
errors.push(Error::PatternNotFound {
392-
pattern: error_pattern.to_string(),
393-
definition_line,
394-
});
396+
errors.push(Error::PatternNotFound { pattern: error_pattern.clone(), definition_line });
395397
}
396398
}
397399

398400
// The order on `Level` is such that `Error` is the highest level.
399401
// We will ensure that *all* diagnostics of level at least `lowest_annotation_level`
400402
// are matched.
401403
let mut lowest_annotation_level = Level::Error;
402-
for &ErrorMatch { ref matched, revision: ref rev, definition_line, line, level } in
404+
for &ErrorMatch { ref pattern, revision: ref rev, definition_line, line, level } in
403405
&comments.error_matches
404406
{
405407
if let Some(rev) = rev {
@@ -415,14 +417,14 @@ fn check_annotations(
415417

416418
if let Some(msgs) = messages.get_mut(line) {
417419
let found =
418-
msgs.iter().position(|msg| msg.message.contains(matched) && msg.level == level);
420+
msgs.iter().position(|msg| pattern.matches(&msg.message) && msg.level == level);
419421
if let Some(found) = found {
420422
msgs.remove(found);
421423
continue;
422424
}
423425
}
424426

425-
errors.push(Error::PatternNotFound { pattern: matched.to_string(), definition_line });
427+
errors.push(Error::PatternNotFound { pattern: pattern.clone(), definition_line });
426428
}
427429

428430
let filter = |msgs: Vec<Message>| -> Vec<_> {

ui_test/src/parser.rs

+33-6
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub(crate) struct Comments {
2929
/// Normalizations to apply to the stderr output before emitting it to disk
3030
pub normalize_stderr: Vec<(Regex, String)>,
3131
/// An arbitrary pattern to look for in the stderr.
32-
pub error_pattern: Option<(String, usize)>,
32+
pub error_pattern: Option<(Pattern, usize)>,
3333
pub error_matches: Vec<ErrorMatch>,
3434
/// Ignore diagnostics below this level.
3535
/// `None` means pick the lowest level from the `error_pattern`s.
@@ -45,9 +45,15 @@ pub(crate) enum Condition {
4545
Bitwidth(u8),
4646
}
4747

48+
#[derive(Debug, Clone)]
49+
pub(crate) enum Pattern {
50+
SubString(String),
51+
Regex(Regex),
52+
}
53+
4854
#[derive(Debug)]
4955
pub(crate) struct ErrorMatch {
50-
pub matched: String,
56+
pub pattern: Pattern,
5157
pub revision: Option<String>,
5258
pub level: Level,
5359
/// 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 {
184190
"cannot specifiy error_pattern twice, previous: {:?}",
185191
self.error_pattern
186192
);
187-
self.error_pattern = Some((args.trim().to_string(), l));
193+
self.error_pattern = Some((Pattern::parse(args.trim())?, l));
188194
}
189195
"stderr-per-bitwidth" => {
190196
// args are ignored (can be used as comment)
@@ -275,14 +281,16 @@ impl Comments {
275281
let pattern = &pattern[offset..];
276282
let pattern = pattern.strip_prefix(':').ok_or_else(|| eyre!("no `:` after level found"))?;
277283

278-
let matched = pattern.trim().to_string();
284+
let pattern = pattern.trim();
285+
286+
ensure!(!pattern.is_empty(), "no pattern specified");
279287

280-
ensure!(!matched.is_empty(), "no pattern specified");
288+
let pattern = Pattern::parse(pattern)?;
281289

282290
*fallthrough_to = Some(match_line);
283291

284292
self.error_matches.push(ErrorMatch {
285-
matched,
293+
pattern,
286294
revision,
287295
level,
288296
definition_line: l,
@@ -292,3 +300,22 @@ impl Comments {
292300
Ok(())
293301
}
294302
}
303+
304+
impl Pattern {
305+
pub(crate) fn matches(&self, message: &str) -> bool {
306+
match self {
307+
Pattern::SubString(s) => message.contains(s),
308+
Pattern::Regex(r) => r.is_match(message),
309+
}
310+
}
311+
312+
pub(crate) fn parse(pattern: &str) -> Result<Self> {
313+
if let Some(pattern) = pattern.strip_prefix('/') {
314+
let regex =
315+
pattern.strip_suffix('/').ok_or_else(|| eyre!("regex must end with `/`"))?;
316+
Ok(Pattern::Regex(Regex::new(regex)?))
317+
} else {
318+
Ok(Pattern::SubString(pattern.to_string()))
319+
}
320+
}
321+
}

ui_test/src/parser/tests.rs

+24-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::path::Path;
22

3+
use crate::parser::Pattern;
4+
35
use super::Comments;
46

57
#[test]
@@ -15,10 +17,11 @@ fn main() {
1517
println!("parsed comments: {:#?}", comments);
1618
assert_eq!(comments.error_matches[0].definition_line, 5);
1719
assert_eq!(comments.error_matches[0].revision, None);
18-
assert_eq!(
19-
comments.error_matches[0].matched,
20-
"encountered a dangling reference (address $HEX is unallocated)"
21-
);
20+
match &comments.error_matches[0].pattern {
21+
Pattern::SubString(s) =>
22+
assert_eq!(s, "encountered a dangling reference (address $HEX is unallocated)"),
23+
other => panic!("expected substring, got {other:?}"),
24+
}
2225
}
2326

2427
#[test]
@@ -42,7 +45,23 @@ use std::mem;
4245
";
4346
let comments = Comments::parse(Path::new("<dummy>"), s).unwrap();
4447
println!("parsed comments: {:#?}", comments);
45-
assert_eq!(comments.error_pattern, Some(("foomp".to_string(), 2)));
48+
let pat = comments.error_pattern.unwrap();
49+
assert_eq!(format!("{:?}", pat.0), r#"SubString("foomp")"#);
50+
assert_eq!(pat.1, 2);
51+
}
52+
53+
#[test]
54+
fn parse_regex_error_pattern() {
55+
let s = r"
56+
//@ error-pattern: /foomp/
57+
use std::mem;
58+
59+
";
60+
let comments = Comments::parse(Path::new("<dummy>"), s).unwrap();
61+
println!("parsed comments: {:#?}", comments);
62+
let pat = comments.error_pattern.unwrap();
63+
assert_eq!(format!("{:?}", pat.0), r#"Regex(foomp)"#);
64+
assert_eq!(pat.1, 2);
4665
}
4766

4867
#[test]

0 commit comments

Comments
 (0)