Skip to content

compiletest: Support matching on non-json lines in compiler output #140599

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/doc/rustc-dev-guide/src/tests/ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,7 @@ For checking runtime output, `//@ check-run-results` may be preferable.

Only use `error-pattern` if none of the above works.

Line annotations `//~` are still checked in tests using `error-pattern`.
In exceptional cases, use `//@ compile-flags: --error-format=human` to opt out of these checks.
Line annotations `//~` and `error-pattern` are compatible and can be used in the same test.

### Diagnostic kinds (error levels)

Expand All @@ -356,9 +355,12 @@ The diagnostic kinds that you can have are:
- `NOTE`
- `HELP`
- `SUGGESTION`
- `RAW`

The `SUGGESTION` kind is used for specifying what the expected replacement text
should be for a diagnostic suggestion.
The `RAW` kind can be used for matching on lines from non-structured output sometimes emitted
by the compiler instead of or in addition to structured json.

`ERROR` and `WARN` kinds are required to be exhaustively covered by line annotations
`//~` by default.
Expand Down
3 changes: 3 additions & 0 deletions src/tools/compiletest/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub enum ErrorKind {
Note,
Suggestion,
Warning,
Raw,
}

impl ErrorKind {
Expand All @@ -39,6 +40,7 @@ impl ErrorKind {
"NOTE" | "note" | "MONO_ITEM" => ErrorKind::Note,
"SUGGESTION" => ErrorKind::Suggestion,
"WARN" | "WARNING" | "warn" | "warning" => ErrorKind::Warning,
"RAW" => ErrorKind::Raw,
_ => panic!(
"unexpected diagnostic kind `{s}`, expected \
`ERROR`, `WARN`, `NOTE`, `HELP` or `SUGGESTION`"
Expand All @@ -55,6 +57,7 @@ impl fmt::Display for ErrorKind {
ErrorKind::Note => write!(f, "NOTE"),
ErrorKind::Suggestion => write!(f, "SUGGESTION"),
ErrorKind::Warning => write!(f, "WARN"),
ErrorKind::Raw => write!(f, "RAW"),
}
}
}
Expand Down
32 changes: 11 additions & 21 deletions src/tools/compiletest/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use regex::Regex;
use serde::Deserialize;

use crate::errors::{Error, ErrorKind};
use crate::runtest::ProcRes;

#[derive(Deserialize)]
struct Diagnostic {
Expand Down Expand Up @@ -140,28 +139,19 @@ pub fn extract_rendered(output: &str) -> String {
.collect()
}

pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
pub fn parse_output(file_name: &str, output: &str) -> Vec<Error> {
let mut errors = Vec::new();
for line in output.lines() {
// The compiler sometimes intermingles non-JSON stuff into the
// output. This hack just skips over such lines. Yuck.
if line.starts_with('{') {
match serde_json::from_str::<Diagnostic>(line) {
Ok(diagnostic) => push_actual_errors(&mut errors, &diagnostic, &[], file_name),
Err(error) => {
// Ignore the future compat report message - this is handled
// by `extract_rendered`
if serde_json::from_str::<FutureIncompatReport>(line).is_err() {
proc_res.fatal(
Some(&format!(
"failed to decode compiler output as json: `{}`\nline: {}\noutput: {}",
error, line, output
)),
|| (),
);
}
}
}
// Compiler can emit non-json lines in non-`--error-format=json` modes,
// and in some situations even in json mode.
match serde_json::from_str::<Diagnostic>(line) {
Ok(diagnostic) => push_actual_errors(&mut errors, &diagnostic, &[], file_name),
Err(_) => errors.push(Error {
line_num: None,
kind: ErrorKind::Raw,
msg: line.to_string(),
require_annotation: false,
}),
}
}
errors
Expand Down
43 changes: 22 additions & 21 deletions src/tools/compiletest/src/runtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::common::{
output_base_dir, output_base_name, output_testname_unique,
};
use crate::compute_diff::{DiffLine, make_diff, write_diff, write_filtered_diff};
use crate::errors::{Error, ErrorKind};
use crate::errors::{Error, ErrorKind, load_errors};
use crate::header::TestProps;
use crate::read2::{Truncated, read2_abbreviated};
use crate::util::{Utf8PathBufExt, add_dylib_path, logv, static_regex};
Expand Down Expand Up @@ -577,23 +577,9 @@ impl<'test> TestCx<'test> {
}
}

fn check_all_error_patterns(
&self,
output_to_check: &str,
proc_res: &ProcRes,
pm: Option<PassMode>,
) {
if self.props.error_patterns.is_empty() && self.props.regex_error_patterns.is_empty() {
if pm.is_some() {
// FIXME(#65865)
return;
} else {
self.fatal(&format!("no error pattern specified in {}", self.testpaths.file));
}
}

/// Check `error-pattern` and `regex-error-pattern` directives.
fn check_all_error_patterns(&self, output_to_check: &str, proc_res: &ProcRes) {
let mut missing_patterns: Vec<String> = Vec::new();

self.check_error_patterns(output_to_check, &mut missing_patterns);
self.check_regex_error_patterns(output_to_check, proc_res, &mut missing_patterns);

Expand Down Expand Up @@ -670,7 +656,9 @@ impl<'test> TestCx<'test> {
}
}

fn check_expected_errors(&self, expected_errors: Vec<Error>, proc_res: &ProcRes) {
/// Check `//~ KIND message` annotations.
fn check_expected_errors(&self, proc_res: &ProcRes) {
let expected_errors = load_errors(&self.testpaths.file, self.revision);
debug!(
"check_expected_errors: expected_errors={:?} proc_res.status={:?}",
expected_errors, proc_res.status
Expand Down Expand Up @@ -711,11 +699,24 @@ impl<'test> TestCx<'test> {
.collect();

// Parse the JSON output from the compiler and extract out the messages.
let actual_errors = json::parse_output(&diagnostic_file_name, &proc_res.stderr, proc_res);
let actual_errors = json::parse_output(&diagnostic_file_name, &self.get_output(proc_res))
.into_iter()
.map(|e| Error { msg: self.normalize_output(&e.msg, &[]), ..e });

let mut unexpected = Vec::new();
let mut found = vec![false; expected_errors.len()];
for mut actual_error in actual_errors {
actual_error.msg = self.normalize_output(&actual_error.msg, &[]);
for actual_error in actual_errors {
for pattern in &self.props.error_patterns {
let pattern = pattern.trim();
if actual_error.msg.contains(pattern) {
let q = if actual_error.line_num.is_none() { "?" } else { "" };
self.fatal(&format!(
"error pattern '{pattern}' is found in structured \
diagnostics, use `//~{q} {} {pattern}` instead",
actual_error.kind,
));
}
}

let opt_index =
expected_errors.iter().enumerate().position(|(index, expected_error)| {
Expand Down
14 changes: 3 additions & 11 deletions src/tools/compiletest/src/runtest/incremental.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,8 @@ impl TestCx<'_> {
self.check_no_compiler_crash(&proc_res, self.props.should_ice);

let output_to_check = self.get_output(&proc_res);
let expected_errors = errors::load_errors(&self.testpaths.file, self.revision);
if !expected_errors.is_empty() {
if !self.props.error_patterns.is_empty() || !self.props.regex_error_patterns.is_empty()
{
self.fatal("both error pattern and expected errors specified");
}
self.check_expected_errors(expected_errors, &proc_res);
} else {
self.check_all_error_patterns(&output_to_check, &proc_res, pm);
}
self.check_expected_errors(&proc_res);
self.check_all_error_patterns(&output_to_check, &proc_res);
if self.props.should_ice {
match proc_res.status.code() {
Some(101) => (),
Expand Down Expand Up @@ -137,6 +129,6 @@ impl TestCx<'_> {

let output_to_check = self.get_output(&proc_res);
self.check_correct_failure_status(&proc_res);
self.check_all_error_patterns(&output_to_check, &proc_res, pm);
self.check_all_error_patterns(&output_to_check, &proc_res);
}
}
47 changes: 10 additions & 37 deletions src/tools/compiletest/src/runtest/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use super::{
AllowUnused, Emit, FailMode, LinkToAux, PassMode, TargetLocation, TestCx, TestOutput,
Truncated, UI_FIXED, WillExecute,
};
use crate::{errors, json};
use crate::json;

impl TestCx<'_> {
pub(super) fn run_ui_test(&self) {
Expand Down Expand Up @@ -127,9 +127,7 @@ impl TestCx<'_> {
);
}

let expected_errors = errors::load_errors(&self.testpaths.file, self.revision);

if let WillExecute::Yes = should_run {
let output_to_check = if let WillExecute::Yes = should_run {
let proc_res = self.exec_compiled_test();
let run_output_errors = if self.props.check_run_results {
self.load_compare_outputs(&proc_res, TestOutput::Run, explicit)
Expand All @@ -150,44 +148,19 @@ impl TestCx<'_> {
self.fatal_proc_rec("test run succeeded!", &proc_res);
}

let output_to_check = self.get_output(&proc_res);
if !self.props.error_patterns.is_empty() || !self.props.regex_error_patterns.is_empty()
{
// "// error-pattern" comments
self.check_all_error_patterns(&output_to_check, &proc_res, pm);
}
self.check_forbid_output(&output_to_check, &proc_res)
}
self.get_output(&proc_res)
} else {
self.get_output(&proc_res)
};

debug!(
"run_ui_test: explicit={:?} config.compare_mode={:?} expected_errors={:?} \
"run_ui_test: explicit={:?} config.compare_mode={:?} \
proc_res.status={:?} props.error_patterns={:?}",
explicit,
self.config.compare_mode,
expected_errors,
proc_res.status,
self.props.error_patterns
explicit, self.config.compare_mode, proc_res.status, self.props.error_patterns
);

if !explicit && self.config.compare_mode.is_none() {
// "//~ERROR comments"
self.check_expected_errors(expected_errors, &proc_res);
} 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_str(),
expected_errors[0].kind,
);
self.fatal(&msg);
}
let output_to_check = self.get_output(&proc_res);
if should_run == WillExecute::No
&& (!self.props.error_patterns.is_empty()
|| !self.props.regex_error_patterns.is_empty())
{
// "// error-pattern" comments
self.check_all_error_patterns(&output_to_check, &proc_res, pm);
}
self.check_expected_errors(&proc_res);
self.check_all_error_patterns(&output_to_check, &proc_res);
self.check_forbid_output(&output_to_check, &proc_res);

if self.props.run_rustfix && self.config.compare_mode.is_none() {
Expand Down
1 change: 1 addition & 0 deletions tests/incremental/ich_nested_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#![crate_type = "rlib"]
#![feature(rustc_attrs)]
#![allow(dead_code)]

#[rustc_clean(except = "opt_hir_owner_nodes", cfg = "cfail2")]
pub fn foo() {
Expand Down
2 changes: 1 addition & 1 deletion tests/incremental/incremental_proc_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ extern crate incremental_proc_macro_aux;

#[derive(IncrementalMacro)]
pub struct Foo {
x: u32
_x: u32
}
2 changes: 1 addition & 1 deletion tests/incremental/issue-49595/issue-49595.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

mod tests {
#[cfg_attr(not(cfail1), test)]
fn test() {
fn _test() {
}
}

Expand Down
1 change: 1 addition & 0 deletions tests/incremental/issue-84252-global-alloc.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//@ revisions: cfail1 cfail2
//@ build-pass
//@ needs-crate-type: cdylib

#![crate_type="lib"]
#![crate_type="cdylib"]
Expand Down
2 changes: 2 additions & 0 deletions tests/incremental/issue-85360-eval-obligation-ice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
//@ edition: 2021
//@ build-pass

#![allow(dead_code)]

use core::any::Any;
use core::marker::PhantomData;

Expand Down
1 change: 1 addition & 0 deletions tests/incremental/no_mangle.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//@ revisions:cfail1 cfail2
//@ check-pass
//@ compile-flags: --crate-type cdylib
//@ needs-crate-type: cdylib

#![deny(unused_attributes)]

Expand Down
4 changes: 2 additions & 2 deletions tests/incremental/thinlto/cgu_keeps_identical_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@
mod foo {

// Trivial functions like this one are imported very reliably by ThinLTO.
#[cfg(any(cfail1, cfail4))]
#[cfg(cfail1)]
pub fn inlined_fn() -> u32 {
1234
}

#[cfg(not(any(cfail1, cfail4)))]
#[cfg(not(cfail1))]
pub fn inlined_fn() -> u32 {
1234
}
Expand Down
2 changes: 2 additions & 0 deletions tests/incremental/unrecoverable_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
//@ compile-flags: --crate-type=lib
//@ build-pass

#![allow(dead_code)]

pub trait P {
type A;
}
Expand Down
5 changes: 3 additions & 2 deletions tests/rustdoc-ui/ice-bug-report-url.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
//@ compile-flags: -Ztreat-err-as-bug
//@ rustc-env:RUSTC_ICE=0
//@ failure-status: 101
//@ error-pattern: aborting due to
//@ error-pattern: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-rustdoc&template=ice.md

//@ normalize-stderr: "note: compiler flags.*\n\n" -> ""
//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}"
Expand All @@ -13,3 +11,6 @@

fn wrong()
//~^ ERROR expected one of

//~? RAW aborting due to
//~? RAW we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-rustdoc&template=ice.md
2 changes: 1 addition & 1 deletion tests/rustdoc-ui/ice-bug-report-url.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: internal compiler error: expected one of `->`, `where`, or `{`, found `<eof>`
--> $DIR/ice-bug-report-url.rs:14:10
--> $DIR/ice-bug-report-url.rs:12:10
|
LL | fn wrong()
| ^ expected one of `->`, `where`, or `{`
Expand Down
3 changes: 2 additions & 1 deletion tests/rustdoc-ui/issues/issue-81662-shortness.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//@ compile-flags:--test --error-format=short
//@ check-stdout
//@ error-pattern:cannot find function `foo`
//@ normalize-stdout: "tests/rustdoc-ui/issues" -> "$$DIR"
//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
//@ failure-status: 101
Expand All @@ -11,3 +10,5 @@
fn foo() {
println!("Hello, world!");
}

//~? RAW cannot find function `foo`
8 changes: 4 additions & 4 deletions tests/rustdoc-ui/issues/issue-81662-shortness.stdout
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@

running 1 test
test $DIR/issue-81662-shortness.rs - foo (line 8) ... FAILED
test $DIR/issue-81662-shortness.rs - foo (line 7) ... FAILED

failures:

---- $DIR/issue-81662-shortness.rs - foo (line 8) stdout ----
$DIR/issue-81662-shortness.rs:9:1: error[E0425]: cannot find function `foo` in this scope: not found in this scope
---- $DIR/issue-81662-shortness.rs - foo (line 7) stdout ----
$DIR/issue-81662-shortness.rs:8:1: error[E0425]: cannot find function `foo` in this scope: not found in this scope
error: aborting due to 1 previous error
Couldn't compile the test.

failures:
$DIR/issue-81662-shortness.rs - foo (line 8)
$DIR/issue-81662-shortness.rs - foo (line 7)

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME

Loading
Loading