diff options
| author | bors <bors@rust-lang.org> | 2025-03-25 16:42:36 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2025-03-25 16:42:36 +0000 |
| commit | 40507bded561ca6d28f3e187aed8317bb81ce9e2 (patch) | |
| tree | 645b301936cf53db0db5bb60f45e9ae496faf99b /src | |
| parent | 48994b1674b3212d27b5e83841c0966bc2b4be43 (diff) | |
| parent | 8d5109aa6ea1b54a560774eb95ba7c1b8d404faa (diff) | |
| download | rust-40507bded561ca6d28f3e187aed8317bb81ce9e2.tar.gz rust-40507bded561ca6d28f3e187aed8317bb81ce9e2.zip | |
Auto merge of #138865 - petrochenkov:errwhere, r=jieyouxu
compiletest: Support matching on diagnostics without a span Using `//~? ERROR my message` on any line of the test. The new checks are exhaustive, like all other `//~` checks, and unlike the `error-pattern` directive that is sometimes used now to check for span-less diagnostics. This will allow to eliminate most on `error-pattern` directives in compile-fail tests (except those that are intentionally imprecise due to platform-specific diagnostics). I didn't migrate any of `error-pattern`s in this PR though, except those where the migration was necessary for the tests to pass.
Diffstat (limited to 'src')
| -rw-r--r-- | src/doc/rustc-dev-guide/src/tests/ui.md | 26 | ||||
| -rw-r--r-- | src/tools/compiletest/src/errors.rs | 55 | ||||
| -rw-r--r-- | src/tools/compiletest/src/json.rs | 88 | ||||
| -rw-r--r-- | src/tools/compiletest/src/runtest.rs | 4 | ||||
| -rw-r--r-- | src/tools/compiletest/src/runtest/ui.rs | 2 |
5 files changed, 95 insertions, 80 deletions
diff --git a/src/doc/rustc-dev-guide/src/tests/ui.md b/src/doc/rustc-dev-guide/src/tests/ui.md index c8536b0045c..98bb9dee76c 100644 --- a/src/doc/rustc-dev-guide/src/tests/ui.md +++ b/src/doc/rustc-dev-guide/src/tests/ui.md @@ -202,6 +202,9 @@ several ways to match the message with the line (see the examples below): * `~|`: Associates the error level and message with the *same* line as the *previous comment*. This is more convenient than using multiple carets when there are multiple messages associated with the same line. +* `~?`: Used to match error levels and messages with errors not having line + information. These can be placed on any line in the test file, but are + conventionally placed at the end. Example: @@ -270,10 +273,23 @@ fn main() { //~| ERROR this pattern has 1 field, but the corresponding tuple struct has 3 fields [E0023] ``` +#### Error without line information + +Use `//~?` to match an error without line information. +`//~?` is precise and will not match errors if their line information is available. +It should be preferred to using `error-pattern`, which is imprecise and non-exhaustive. + +```rust,ignore +//@ compile-flags: --print yyyy + +//~? ERROR unknown print request: `yyyy` +``` + ### `error-pattern` -The `error-pattern` [directive](directives.md) can be used for messages that don't -have a specific span. +The `error-pattern` [directive](directives.md) can be used for runtime messages, which don't +have a specific span, or for compile time messages if imprecise matching is required due to +multi-line platform specific diagnostics. Let's think about this test: @@ -300,7 +316,9 @@ fn main() { } ``` -But for strict testing, try to use the `ERROR` annotation as much as possible. +But for strict testing, try to use the `ERROR` annotation as much as possible, +including `//~?` annotations for diagnostics without span. +For compile time diagnostics `error-pattern` should very rarely be necessary. ### Error levels @@ -353,7 +371,7 @@ would be a `.mir.stderr` and `.thir.stderr` file with the different outputs of the different revisions. > Note: cfg revisions also work inside the source code with `#[cfg]` attributes. -> +> > By convention, the `FALSE` cfg is used to have an always-false config. ## Controlling pass/fail expectations diff --git a/src/tools/compiletest/src/errors.rs b/src/tools/compiletest/src/errors.rs index 37ef63ae42e..c0566ef93b9 100644 --- a/src/tools/compiletest/src/errors.rs +++ b/src/tools/compiletest/src/errors.rs @@ -9,8 +9,6 @@ use std::sync::OnceLock; use regex::Regex; use tracing::*; -use self::WhichLine::*; - #[derive(Copy, Clone, Debug, PartialEq)] pub enum ErrorKind { Help, @@ -50,7 +48,7 @@ impl fmt::Display for ErrorKind { #[derive(Debug)] pub struct Error { - pub line_num: usize, + pub line_num: Option<usize>, /// What kind of message we expect (e.g., warning, error, suggestion). /// `None` if not specified or unknown message kind. pub kind: Option<ErrorKind>, @@ -63,17 +61,14 @@ impl Error { format!( "{: <10}line {: >3}: {}", self.kind.map(|kind| kind.to_string()).unwrap_or_default().to_uppercase(), - self.line_num, + self.line_num_str(), self.msg.cyan(), ) } -} -#[derive(PartialEq, Debug)] -enum WhichLine { - ThisLine, - FollowPrevious(usize), - AdjustBackward(usize), + pub fn line_num_str(&self) -> String { + self.line_num.map_or("?".to_string(), |line_num| line_num.to_string()) + } } /// Looks for either "//~| KIND MESSAGE" or "//~^^... KIND MESSAGE" @@ -105,12 +100,10 @@ pub fn load_errors(testfile: &Path, revision: Option<&str>) -> Vec<Error> { .filter(|(_, line)| line.is_ok()) .filter_map(|(line_num, line)| { parse_expected(last_nonfollow_error, line_num + 1, &line.unwrap(), revision).map( - |(which, error)| { - match which { - FollowPrevious(_) => {} - _ => last_nonfollow_error = Some(error.line_num), + |(follow_prev, error)| { + if !follow_prev { + last_nonfollow_error = error.line_num; } - error }, ) @@ -123,18 +116,19 @@ fn parse_expected( line_num: usize, line: &str, test_revision: Option<&str>, -) -> Option<(WhichLine, Error)> { +) -> Option<(bool, Error)> { // Matches comments like: // //~ // //~| // //~^ // //~^^^^^ + // //~? // //[rev1]~ // //[rev1,rev2]~^^ static RE: OnceLock<Regex> = OnceLock::new(); let captures = RE - .get_or_init(|| Regex::new(r"//(?:\[(?P<revs>[\w\-,]+)])?~(?P<adjust>\||\^*)").unwrap()) + .get_or_init(|| Regex::new(r"//(?:\[(?P<revs>[\w\-,]+)])?~(?P<adjust>\?|\||\^*)").unwrap()) .captures(line)?; match (test_revision, captures.name("revs")) { @@ -151,11 +145,6 @@ fn parse_expected( (Some(_), None) | (None, None) => {} } - let (follow, adjusts) = match &captures["adjust"] { - "|" => (true, 0), - circumflexes => (false, circumflexes.len()), - }; - // Get the part of the comment after the sigil (e.g. `~^^` or ~|). let whole_match = captures.get(0).unwrap(); let (_, mut msg) = line.split_at(whole_match.end()); @@ -170,28 +159,24 @@ fn parse_expected( let msg = msg.trim().to_owned(); - let (which, line_num) = if follow { - assert_eq!(adjusts, 0, "use either //~| or //~^, not both."); - let line_num = last_nonfollow_error.expect( - "encountered //~| without \ - preceding //~^ line.", - ); - (FollowPrevious(line_num), line_num) + let line_num_adjust = &captures["adjust"]; + let (follow_prev, line_num) = if line_num_adjust == "|" { + (true, Some(last_nonfollow_error.expect("encountered //~| without preceding //~^ line"))) + } else if line_num_adjust == "?" { + (false, None) } else { - let which = if adjusts > 0 { AdjustBackward(adjusts) } else { ThisLine }; - let line_num = line_num - adjusts; - (which, line_num) + (false, Some(line_num - line_num_adjust.len())) }; debug!( - "line={} tag={:?} which={:?} kind={:?} msg={:?}", + "line={:?} tag={:?} follow_prev={:?} kind={:?} msg={:?}", line_num, whole_match.as_str(), - which, + follow_prev, kind, msg ); - Some((which, Error { line_num, kind, msg })) + Some((follow_prev, Error { line_num, kind, msg })) } #[cfg(test)] diff --git a/src/tools/compiletest/src/json.rs b/src/tools/compiletest/src/json.rs index 0da93dcafa2..9bc26fedf8f 100644 --- a/src/tools/compiletest/src/json.rs +++ b/src/tools/compiletest/src/json.rs @@ -2,7 +2,9 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; +use std::sync::OnceLock; +use regex::Regex; use serde::Deserialize; use crate::errors::{Error, ErrorKind}; @@ -213,36 +215,24 @@ fn push_expected_errors( // also ensure that `//~ ERROR E123` *always* works. The // assumption is that these multi-line error messages are on their // way out anyhow. - let with_code = |span: &DiagnosticSpan, text: &str| { - match diagnostic.code { - Some(ref code) => - // FIXME(#33000) -- it'd be better to use a dedicated - // UI harness than to include the line/col number like - // this, but some current tests rely on it. - // - // Note: Do NOT include the filename. These can easily - // cause false matches where the expected message - // appears in the filename, and hence the message - // changes but the test still passes. - { - format!( - "{}:{}: {}:{}: {} [{}]", - span.line_start, - span.column_start, - span.line_end, - span.column_end, - text, - code.code.clone() - ) - } - None => - // FIXME(#33000) -- it'd be better to use a dedicated UI harness - { - format!( - "{}:{}: {}:{}: {}", - span.line_start, span.column_start, span.line_end, span.column_end, text - ) + let with_code = |span: Option<&DiagnosticSpan>, text: &str| { + // FIXME(#33000) -- it'd be better to use a dedicated + // UI harness than to include the line/col number like + // this, but some current tests rely on it. + // + // Note: Do NOT include the filename. These can easily + // cause false matches where the expected message + // appears in the filename, and hence the message + // changes but the test still passes. + let span_str = match span { + Some(DiagnosticSpan { line_start, column_start, line_end, column_end, .. }) => { + format!("{line_start}:{column_start}: {line_end}:{column_end}") } + None => format!("?:?: ?:?"), + }; + match &diagnostic.code { + Some(code) => format!("{span_str}: {text} [{}]", code.code), + None => format!("{span_str}: {text}"), } }; @@ -251,19 +241,41 @@ fn push_expected_errors( // more structured shortly anyhow. let mut message_lines = diagnostic.message.lines(); if let Some(first_line) = message_lines.next() { - for span in primary_spans { - let msg = with_code(span, first_line); + let ignore = |s| { + static RE: OnceLock<Regex> = OnceLock::new(); + RE.get_or_init(|| { + Regex::new(r"aborting due to \d+ previous errors?|\d+ warnings? emitted").unwrap() + }) + .is_match(s) + }; + + if primary_spans.is_empty() && !ignore(first_line) { + let msg = with_code(None, first_line); let kind = ErrorKind::from_str(&diagnostic.level).ok(); - expected_errors.push(Error { line_num: span.line_start, kind, msg }); + expected_errors.push(Error { line_num: None, kind, msg }); + } else { + for span in primary_spans { + let msg = with_code(Some(span), first_line); + let kind = ErrorKind::from_str(&diagnostic.level).ok(); + expected_errors.push(Error { line_num: Some(span.line_start), kind, msg }); + } } } for next_line in message_lines { - for span in primary_spans { + if primary_spans.is_empty() { expected_errors.push(Error { - line_num: span.line_start, + line_num: None, kind: None, - msg: with_code(span, next_line), + msg: with_code(None, next_line), }); + } else { + for span in primary_spans { + expected_errors.push(Error { + line_num: Some(span.line_start), + kind: None, + msg: with_code(Some(span), next_line), + }); + } } } @@ -272,7 +284,7 @@ fn push_expected_errors( if let Some(ref suggested_replacement) = span.suggested_replacement { for (index, line) in suggested_replacement.lines().enumerate() { expected_errors.push(Error { - line_num: span.line_start + index, + line_num: Some(span.line_start + index), kind: Some(ErrorKind::Suggestion), msg: line.to_string(), }); @@ -290,7 +302,7 @@ fn push_expected_errors( // Add notes for any labels that appear in the message. for span in spans_in_this_file.iter().filter(|span| span.label.is_some()) { expected_errors.push(Error { - line_num: span.line_start, + line_num: Some(span.line_start), kind: Some(ErrorKind::Note), msg: span.label.clone().unwrap(), }); @@ -309,7 +321,7 @@ fn push_backtrace( ) { if Path::new(&expansion.span.file_name) == Path::new(&file_name) { expected_errors.push(Error { - line_num: expansion.span.line_start, + line_num: Some(expansion.span.line_start), kind: Some(ErrorKind::Note), msg: format!("in this expansion of {}", expansion.macro_decl_name), }); diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 6e250ca12c9..c8a60b68da8 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -747,7 +747,7 @@ impl<'test> TestCx<'test> { self.error(&format!( "{}:{}: unexpected {}: '{}'", file_name, - actual_error.line_num, + actual_error.line_num_str(), actual_error .kind .as_ref() @@ -767,7 +767,7 @@ impl<'test> TestCx<'test> { self.error(&format!( "{}:{}: expected {} not found: {}", file_name, - expected_error.line_num, + expected_error.line_num_str(), expected_error.kind.as_ref().map_or("message".into(), |k| k.to_string()), expected_error.msg )); diff --git a/src/tools/compiletest/src/runtest/ui.rs b/src/tools/compiletest/src/runtest/ui.rs index 3329e10745f..9b5b8b56b60 100644 --- a/src/tools/compiletest/src/runtest/ui.rs +++ b/src/tools/compiletest/src/runtest/ui.rs @@ -182,7 +182,7 @@ impl TestCx<'_> { } else if explicit && !expected_errors.is_empty() { let msg = format!( "line {}: cannot combine `--error-format` with {} annotations; use `error-pattern` instead", - expected_errors[0].line_num, + expected_errors[0].line_num_str(), expected_errors[0].kind.unwrap_or(ErrorKind::Error), ); self.fatal(&msg); |
