diff --git a/CHANGELOG.md b/CHANGELOG.md index 97fd23f0..644b1d3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,13 +10,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * Started maintaining a changelog +* `Config::comment_defaults` allows setting `//@` comments for all tests ### Fixed ### Changed +* crate-private span handling was passed off to the `spanned` crate, improving some diagnostics along the way. + ### Removed * `$DIR` and `RUSTLIB` replacements +* `Config::edition` (replaced by `config.comment_defaults.base().edition`) +* `Config::filter_stdout` (replaced by `config.comment_defaults.base().normalize_stdout`) +* `Config::filter_stderr` (replaced by `config.comment_defaults.base().normalize_stderr`) +* `Config::mode` (replaced by `config.comment_defaults.base().mode`) ## [0.21.2] - 2023-09-27 diff --git a/Cargo.lock b/Cargo.lock index fad78cef..c0a74a4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -667,7 +667,7 @@ dependencies = [ [[package]] name = "ui_test" -version = "0.21.2" +version = "0.22.0" dependencies = [ "annotate-snippets", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 951ddbf6..ceda85fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ui_test" -version = "0.21.2" +version = "0.22.0" edition = "2021" license = "MIT OR Apache-2.0" description = "A test framework for testing rustc diagnostics output" diff --git a/README.md b/README.md index cd827d09..2c69403c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -A smaller version of compiletest-rs +A stable version of compiletest-rs ## Magic behavior diff --git a/src/config.rs b/src/config.rs index c3837923..73cb4f9c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,10 @@ use regex::bytes::Regex; +use spanned::{Span, Spanned}; -use crate::{dependencies::build_dependencies, CommandBuilder, Filter, Match, Mode, RustfixMode}; +use crate::{ + dependencies::build_dependencies, per_test_config::Comments, CommandBuilder, Match, Mode, + RustfixMode, +}; pub use color_eyre; use color_eyre::eyre::Result; use std::{ @@ -19,18 +23,8 @@ pub struct Config { pub host: Option, /// `None` to run on the host, otherwise a target triple pub target: Option, - /// Filters applied to stderr output before processing it. - /// By default contains a filter for replacing backslashes in paths with - /// regular slashes. - /// On windows, contains a filter to remove `\r`. - pub stderr_filters: Filter, - /// Filters applied to stdout output before processing it. - /// On windows, contains a filter to remove `\r`. - pub stdout_filters: Filter, /// The folder in which to start searching for .rs files pub root_dir: PathBuf, - /// The mode in which to run the tests. - pub mode: Mode, /// The binary to actually execute. pub program: CommandBuilder, /// The command to run to obtain the cfgs that the output is supposed to @@ -45,8 +39,6 @@ pub struct Config { /// Where to dump files like the binaries compiled from tests. /// Defaults to `target/ui` in the current directory. pub out_dir: PathBuf, - /// The default edition to use on all tests. - pub edition: Option, /// Skip test files whose names contain any of these entries. pub skip_files: Vec, /// Only test files whose names contain any of these entries. @@ -59,34 +51,37 @@ pub struct Config { pub run_only_ignored: bool, /// Filters must match exactly instead of just checking for substrings. pub filter_exact: bool, + /// The default settings settable via `@` comments + pub comment_defaults: Comments, } impl Config { /// Create a configuration for testing the output of running /// `rustc` on the test files. pub fn rustc(root_dir: impl Into) -> Self { + let mut comment_defaults = Comments::default(); + let _ = comment_defaults + .base() + .edition + .set("2021".into(), Span::default()); + let filters = vec![ + (Match::PathBackslash, b"/".to_vec()), + #[cfg(windows)] + (Match::Exact(vec![b'\r']), b"".to_vec()), + #[cfg(windows)] + (Match::Exact(br"\\?\".to_vec()), b"".to_vec()), + ]; + comment_defaults.base().normalize_stderr = filters.clone(); + comment_defaults.base().normalize_stdout = filters; + comment_defaults.base().mode = Spanned::dummy(Mode::Fail { + require_patterns: true, + rustfix: RustfixMode::MachineApplicable, + }) + .into(); Self { host: None, target: None, - stderr_filters: vec![ - (Match::PathBackslash, b"/"), - #[cfg(windows)] - (Match::Exact(vec![b'\r']), b""), - #[cfg(windows)] - (Match::Exact(br"\\?\".to_vec()), b""), - ], - stdout_filters: vec![ - (Match::PathBackslash, b"/"), - #[cfg(windows)] - (Match::Exact(vec![b'\r']), b""), - #[cfg(windows)] - (Match::Exact(br"\\?\".to_vec()), b""), - ], root_dir: root_dir.into(), - mode: Mode::Fail { - require_patterns: true, - rustfix: RustfixMode::MachineApplicable, - }, program: CommandBuilder::rustc(), cfgs: CommandBuilder::cfgs(), output_conflict_handling: OutputConflictHandling::Bless, @@ -96,28 +91,30 @@ impl Config { .map(PathBuf::from) .unwrap_or_else(|| std::env::current_dir().unwrap().join("target")) .join("ui"), - edition: Some("2021".into()), skip_files: Vec::new(), filter_files: Vec::new(), threads: None, list: false, run_only_ignored: false, filter_exact: false, + comment_defaults, } } /// Create a configuration for testing the output of running /// `cargo` on the test `Cargo.toml` files. pub fn cargo(root_dir: impl Into) -> Self { - Self { + let mut this = Self { program: CommandBuilder::cargo(), - edition: None, - mode: Mode::Fail { - require_patterns: true, - rustfix: RustfixMode::Disabled, - }, ..Self::rustc(root_dir) - } + }; + this.comment_defaults.base().edition = Default::default(); + this.comment_defaults.base().mode = Spanned::dummy(Mode::Fail { + require_patterns: true, + rustfix: RustfixMode::Disabled, + }) + .into(); + this } /// Populate the config with the values from parsed command line arguments. @@ -180,8 +177,10 @@ impl Config { replacement: &'static (impl AsRef<[u8]> + ?Sized), ) { let pattern = path.canonicalize().unwrap(); - self.stderr_filters - .push((pattern.parent().unwrap().into(), replacement.as_ref())); + self.comment_defaults.base().normalize_stderr.push(( + pattern.parent().unwrap().into(), + replacement.as_ref().to_owned(), + )); } /// Replace all occurrences of a path in stdout with a byte string. @@ -192,8 +191,10 @@ impl Config { replacement: &'static (impl AsRef<[u8]> + ?Sized), ) { let pattern = path.canonicalize().unwrap(); - self.stdout_filters - .push((pattern.parent().unwrap().into(), replacement.as_ref())); + self.comment_defaults.base().normalize_stdout.push(( + pattern.parent().unwrap().into(), + replacement.as_ref().to_owned(), + )); } /// Replace all occurrences of a regex pattern in stderr/stdout with a byte string. @@ -210,8 +211,10 @@ impl Config { pattern: &str, replacement: &'static (impl AsRef<[u8]> + ?Sized), ) { - self.stderr_filters - .push((Regex::new(pattern).unwrap().into(), replacement.as_ref())); + self.comment_defaults.base().normalize_stderr.push(( + Regex::new(pattern).unwrap().into(), + replacement.as_ref().to_owned(), + )); } /// Replace all occurrences of a regex pattern in stdout with a byte string. @@ -221,8 +224,10 @@ impl Config { pattern: &str, replacement: &'static (impl AsRef<[u8]> + ?Sized), ) { - self.stdout_filters - .push((Regex::new(pattern).unwrap().into(), replacement.as_ref())); + self.comment_defaults.base().normalize_stdout.push(( + Regex::new(pattern).unwrap().into(), + replacement.as_ref().to_owned(), + )); } /// Compile dependencies and return the right flags diff --git a/src/dependencies.rs b/src/dependencies.rs index 0e95e0f8..8fe80040 100644 --- a/src/dependencies.rs +++ b/src/dependencies.rs @@ -59,7 +59,15 @@ pub(crate) fn build_dependencies(config: &Config) -> Result { } // Reusable closure for setting up the environment both for artifact generation and `cargo_metadata` - let set_locking = |cmd: &mut Command| match (&config.output_conflict_handling, &config.mode) { + let set_locking = |cmd: &mut Command| match ( + &config.output_conflict_handling, + config + .comment_defaults + .base_immut() + .mode + .as_deref() + .unwrap(), + ) { (_, Mode::Yolo { .. }) => {} (OutputConflictHandling::Error(_), _) => { cmd.arg("--locked"); diff --git a/src/error.rs b/src/error.rs index 4217ae57..d46753ce 100644 --- a/src/error.rs +++ b/src/error.rs @@ -30,7 +30,7 @@ pub enum Error { /// A ui test checking for success has failure patterns PatternFoundInPassTest { /// Span of a flag changing the mode (if changed from default). - mode: Option, + mode: Span, /// Span of the pattern span: Span, }, @@ -59,6 +59,8 @@ pub enum Error { /// The character range in which it was defined. span: Span, }, + /// An invalid setting was used. + ConfigError(String), /// Conflicting comments MultipleRevisionsWithResults { /// The comment being looked for diff --git a/src/lib.rs b/src/lib.rs index 95f553e1..e82117f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ use color_eyre::eyre::{eyre, Result}; use crossbeam_channel::{unbounded, Receiver, Sender}; use dependencies::{Build, BuildManager}; use lazy_static::lazy_static; -use parser::{ErrorMatch, MaybeSpanned, OptWithLine, Revisioned, Spanned}; +use parser::{ErrorMatch, OptWithLine, Revisioned, Spanned}; use regex::bytes::{Captures, Regex}; use rustc_stderr::{Level, Message}; use spanned::Span; @@ -37,6 +37,7 @@ mod error; pub mod github_actions; mod mode; mod parser; +pub mod per_test_config; mod rustc_stderr; pub mod status_emitter; #[cfg(test)] @@ -47,6 +48,8 @@ pub use config::*; pub use error::*; pub use mode::*; +pub use spanned; + /// A filter's match rule. #[derive(Clone, Debug)] pub enum Match { @@ -110,9 +113,6 @@ impl From for Match { } } -/// Replacements to apply to output files. -pub type Filter = Vec<(Match, &'static [u8])>; - /// Run all tests as described in the config argument. /// Will additionally process command line arguments. pub fn run_tests(mut config: Config) -> Result<()> { @@ -189,8 +189,8 @@ pub fn test_command(mut config: Config, path: &Path) -> Result { config.fill_host_and_target()?; let extra_args = config.build_dependencies()?; - let comments = - Comments::parse_file(path)?.map_err(|errors| color_eyre::eyre::eyre!("{errors:#?}"))?; + let comments = Comments::parse_file(config.comment_defaults.clone(), path)? + .map_err(|errors| color_eyre::eyre::eyre!("{errors:#?}"))?; let mut result = build_command(path, &config, "", &comments).unwrap(); result.args(extra_args); @@ -433,7 +433,11 @@ fn parse_and_test_file( mut config: Config, file_contents: Vec, ) -> Result, Errored> { - let comments = parse_comments(&file_contents, status.path())?; + let comments = parse_comments( + &file_contents, + config.comment_defaults.clone(), + status.path(), + )?; const EMPTY: &[String] = &[String::new()]; // Run the test for all revisions let revisions = comments.revisions.as_deref().unwrap_or(EMPTY); @@ -471,8 +475,12 @@ fn parse_and_test_file( .collect()) } -fn parse_comments(file_contents: &[u8], file: &Path) -> Result { - match Comments::parse(file_contents, file) { +fn parse_comments( + file_contents: &[u8], + comments: Comments, + file: &Path, +) -> Result { + match Comments::parse(file_contents, comments, file) { Ok(comments) => Ok(comments), Err(errors) => Err(Errored { command: Command::new("parse comments"), @@ -500,7 +508,7 @@ fn build_command( { cmd.arg(arg); } - let edition = comments.edition(revision, config)?; + let edition = comments.edition(revision)?; if let Some(edition) = edition { cmd.arg("--edition").arg(&*edition); @@ -527,7 +535,7 @@ fn build_aux( stderr: err.to_string().into_bytes(), stdout: vec![], })?; - let comments = parse_comments(&file_contents, aux_file)?; + let comments = parse_comments(&file_contents, Comments::default(), aux_file)?; assert_eq!( comments.revisions, None, "aux builds cannot specify revisions" @@ -651,7 +659,7 @@ impl dyn TestStatus { let (cmd, status, stderr, stdout) = self.run_command(cmd)?; - let mode = config.mode.maybe_override(comments, revision)?; + let mode = comments.mode(revision)?; let cmd = check_test_result( cmd, match *mode { @@ -761,7 +769,7 @@ fn build_aux_files( } fn run_test_binary( - mode: MaybeSpanned, + mode: Spanned, path: &Path, revision: &str, comments: &Comments, @@ -877,13 +885,7 @@ fn run_rustfix( stdout: stdout.into(), })?; - let edition = comments.edition(revision, config)?; - let edition = edition - .map(|mwl| { - let line = mwl.span().unwrap_or_default(); - Spanned::new(mwl.into_inner(), line) - }) - .into(); + let edition = comments.edition(revision)?.into(); let rustfix_comments = Comments { revisions: None, revisioned: std::iter::once(( @@ -928,7 +930,6 @@ fn run_rustfix( path, &mut errors, "fixed", - &Filter::default(), config, &rustfix_comments, revision, @@ -1010,7 +1011,6 @@ fn check_test_result( diagnostics.messages_from_unknown_file_or_line, path, &mut errors, - config, revision, comments, )?; @@ -1037,26 +1037,8 @@ fn check_test_output( ) { // Check output files (if any) // Check output files against actual output - check_output( - stderr, - path, - errors, - "stderr", - &config.stderr_filters, - config, - comments, - revision, - ); - check_output( - stdout, - path, - errors, - "stdout", - &config.stdout_filters, - config, - comments, - revision, - ); + check_output(stderr, path, errors, "stderr", config, comments, revision); + check_output(stdout, path, errors, "stdout", config, comments, revision); } fn check_annotations( @@ -1064,7 +1046,6 @@ fn check_annotations( mut messages_from_unknown_file_or_line: Vec, path: &Path, errors: &mut Errors, - config: &Config, revision: &str, comments: &Comments, ) -> Result<(), Errored> { @@ -1141,9 +1122,9 @@ fn check_annotations( msgs }; - let mode = config.mode.maybe_override(comments, revision)?; + let mode = comments.mode(revision)?; - if !matches!(config.mode, Mode::Yolo { .. }) { + if !matches!(*mode, Mode::Yolo { .. }) { let messages_from_unknown_file_or_line = filter(messages_from_unknown_file_or_line); if !messages_from_unknown_file_or_line.is_empty() { errors.push(Error::ErrorsWithoutPattern { @@ -1194,13 +1175,12 @@ fn check_output( path: &Path, errors: &mut Errors, kind: &'static str, - filters: &Filter, config: &Config, comments: &Comments, revision: &str, ) -> PathBuf { let target = config.target.as_ref().unwrap(); - let output = normalize(output, filters, comments, revision, kind); + let output = normalize(output, comments, revision, kind); let path = output_path(path, comments, revised(revision, kind), target, revision); match &config.output_conflict_handling { OutputConflictHandling::Error(bless_command) => { @@ -1290,19 +1270,9 @@ fn get_pointer_width(triple: &str) -> u8 { } } -fn normalize( - text: &[u8], - filters: &Filter, - comments: &Comments, - revision: &str, - kind: &'static str, -) -> Vec { +fn normalize(text: &[u8], comments: &Comments, revision: &str, kind: &'static str) -> Vec { let mut text = text.to_owned(); - for (rule, replacement) in filters { - text = rule.replace_all(&text, replacement).into_owned(); - } - for (from, to) in comments.for_revision(revision).flat_map(|r| match kind { "fixed" => &[] as &[_], "stderr" => &r.normalize_stderr, diff --git a/src/mode.rs b/src/mode.rs index 39905874..16d330bb 100644 --- a/src/mode.rs +++ b/src/mode.rs @@ -1,7 +1,4 @@ use super::Error; -use crate::parser::Comments; -use crate::parser::MaybeSpanned; -use crate::Errored; use std::fmt::Display; use std::process::ExitStatus; @@ -68,16 +65,6 @@ impl Mode { }) } } - pub(crate) fn maybe_override( - self, - comments: &Comments, - revision: &str, - ) -> Result, Errored> { - let mode = comments.find_one_for_revision(revision, "mode changes", |r| r.mode.clone())?; - Ok(mode - .into_inner() - .map_or(MaybeSpanned::new_config(self), Into::into)) - } } impl Display for Mode { diff --git a/src/parser.rs b/src/parser.rs index 21d008d9..3d240c26 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -8,7 +8,7 @@ use std::{ use bstr::{ByteSlice, Utf8Error}; use regex::bytes::Regex; -use crate::{rustc_stderr::Level, Error, Errored, Mode}; +use crate::{rustc_stderr::Level, Error, Errored, Match, Mode}; use color_eyre::eyre::{Context, Result}; @@ -21,8 +21,8 @@ mod tests; /// This crate supports various magic comments that get parsed as file-specific /// configuration values. This struct parses them all in one go and then they /// get processed by their respective use sites. -#[derive(Default, Debug)] -pub(crate) struct Comments { +#[derive(Debug, Clone)] +pub struct Comments { /// List of revision names to execute. Can only be specified once pub revisions: Option>, /// Comments that are only available under specific revisions. @@ -30,6 +30,17 @@ pub(crate) struct Comments { pub revisioned: HashMap, Revisioned>, } +impl Default for Comments { + fn default() -> Self { + let mut this = Self { + revisions: Default::default(), + revisioned: Default::default(), + }; + this.revisioned.insert(vec![], Revisioned::default()); + this + } +} + impl Comments { /// Check that a comment isn't specified twice across multiple differently revisioned statements. /// e.g. `//@[foo, bar] error-in-other-file: bop` and `//@[foo, baz] error-in-other-file boop` would end up @@ -42,7 +53,10 @@ impl Comments { ) -> Result, Errored> { let mut result = None; let mut errors = vec![]; - for rev in self.for_revision(revision) { + for (k, rev) in &self.revisioned { + if !k.iter().any(|r| r == revision) { + continue; + } if let Some(found) = f(rev).into_inner() { if result.is_some() { errors.push(found.line()); @@ -51,6 +65,9 @@ impl Comments { } } } + if result.is_none() { + result = f(&self.revisioned[&[][..]]).into_inner(); + } if errors.is_empty() { Ok(result.into()) } else { @@ -77,24 +94,42 @@ impl Comments { }) } - pub(crate) fn edition( - &self, - revision: &str, - config: &crate::Config, - ) -> Result>, Errored> { - let edition = - self.find_one_for_revision(revision, "`edition` annotations", |r| r.edition.clone())?; - let edition = edition - .into_inner() - .map(MaybeSpanned::from) - .or(config.edition.clone().map(MaybeSpanned::new_config)); + pub(crate) fn edition(&self, revision: &str) -> Result>, Errored> { + let edition = self + .find_one_for_revision(revision, "`edition` annotations", |r| r.edition.clone())? + .into_inner(); Ok(edition) } + + /// The comments set for all revisions + pub fn base(&mut self) -> &mut Revisioned { + self.revisioned.get_mut(&[][..]).unwrap() + } + + /// The comments set for all revisions + pub fn base_immut(&self) -> &Revisioned { + self.revisioned.get(&[][..]).unwrap() + } + + pub(crate) fn mode(&self, revision: &str) -> Result, Errored> { + let mode = self + .find_one_for_revision(revision, "`mode` annotations", |r| r.mode.clone())? + .into_inner() + .ok_or_else(|| Errored { + command: Command::new(format!("")), + errors: vec![Error::ConfigError( + "no mode set up in Config::comment_defaults".into(), + )], + stderr: vec![], + stdout: vec![], + })?; + Ok(mode) + } } -#[derive(Debug)] +#[derive(Debug, Clone, Default)] /// Comments that can be filtered for specific revisions. -pub(crate) struct Revisioned { +pub struct Revisioned { /// The character range in which this revisioned item was first added. /// Used for reporting errors on unknown revisions. pub span: Span, @@ -109,22 +144,24 @@ pub(crate) struct Revisioned { /// Additional env vars to set for the executable pub env_vars: Vec<(String, String)>, /// Normalizations to apply to the stderr output before emitting it to disk - pub normalize_stderr: Vec<(Regex, Vec)>, + pub normalize_stderr: Vec<(Match, Vec)>, /// Normalizations to apply to the stdout output before emitting it to disk - pub normalize_stdout: Vec<(Regex, Vec)>, + pub normalize_stdout: Vec<(Match, Vec)>, /// Arbitrary patterns to look for in the stderr. /// The error must be from another file, as errors from the current file must be /// checked via `error_matches`. - pub error_in_other_files: Vec>, - pub error_matches: Vec, + pub(crate) error_in_other_files: Vec>, + pub(crate) error_matches: Vec, /// Ignore diagnostics below this level. /// `None` means pick the lowest level from the `error_pattern`s. pub require_annotations_for_level: OptWithLine, + /// Files that get built and exposed as dependencies to the current test. pub aux_builds: Vec>, + /// Set the `--edition` flag on the test. pub edition: OptWithLine, - /// Overwrites the mode from `Config`. + /// The mode this test is being run in. pub mode: OptWithLine, - pub needs_asm_support: bool, + pub(crate) needs_asm_support: bool, /// Don't run [`rustfix`] for this test pub no_rustfix: OptWithLine<()>, } @@ -156,8 +193,8 @@ impl std::ops::DerefMut for CommentParser { } /// The conditions used for "ignore" and "only" filters. -#[derive(Debug)] -pub(crate) enum Condition { +#[derive(Debug, Clone)] +pub enum Condition { /// The given string must appear in the host triple. Host(String), /// The given string must appear in the target triple. @@ -175,7 +212,7 @@ pub enum Pattern { Regex(Regex), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct ErrorMatch { pub pattern: Spanned, pub level: Level, @@ -205,24 +242,30 @@ impl Condition { } impl Comments { - pub(crate) fn parse_file(path: &Path) -> Result>> { + pub(crate) fn parse_file( + comments: Comments, + path: &Path, + ) -> Result>> { let content = std::fs::read(path).wrap_err_with(|| format!("failed to read {}", path.display()))?; - Ok(Self::parse(&content, path)) + Ok(Self::parse(&content, comments, path)) } /// Parse comments in `content`. /// `path` is only used to emit diagnostics if parsing fails. pub(crate) fn parse( content: &(impl AsRef<[u8]> + ?Sized), + comments: Comments, file: &Path, ) -> std::result::Result> { let mut parser = CommentParser { - comments: Comments::default(), + comments, errors: vec![], commands: CommentParser::<_>::commands(), }; + let defaults = std::mem::take(parser.comments.revisioned.get_mut(&[][..]).unwrap()); + let mut fallthrough_to = None; // The line that a `|` will refer to. for (l, line) in content.as_ref().lines().enumerate() { let l = NonZeroUsize::new(l + 1).unwrap(); // enumerate starts at 0, but line numbers start at 1 @@ -259,6 +302,51 @@ impl Comments { } } } + let Revisioned { + span, + ignore, + only, + stderr_per_bitwidth, + compile_flags, + env_vars, + normalize_stderr, + normalize_stdout, + error_in_other_files, + error_matches, + require_annotations_for_level, + aux_builds, + edition, + mode, + needs_asm_support, + no_rustfix, + } = parser.comments.base(); + if span.is_dummy() { + *span = defaults.span; + } + ignore.extend(defaults.ignore); + only.extend(defaults.only); + *stderr_per_bitwidth |= defaults.stderr_per_bitwidth; + compile_flags.extend(defaults.compile_flags); + env_vars.extend(defaults.env_vars); + normalize_stderr.extend(defaults.normalize_stderr); + normalize_stdout.extend(defaults.normalize_stdout); + error_in_other_files.extend(defaults.error_in_other_files); + error_matches.extend(defaults.error_matches); + aux_builds.extend(defaults.aux_builds); + if require_annotations_for_level.is_none() { + *require_annotations_for_level = defaults.require_annotations_for_level; + } + if edition.is_none() { + *edition = defaults.edition; + } + if mode.is_none() { + *mode = defaults.mode; + } + if no_rustfix.is_none() { + *no_rustfix = defaults.no_rustfix; + } + *needs_asm_support |= defaults.needs_asm_support; + if parser.errors.is_empty() { Ok(parser.comments) } else { @@ -403,21 +491,7 @@ impl CommentParser { .entry(revisions) .or_insert_with(|| Revisioned { span, - ignore: Default::default(), - only: Default::default(), - stderr_per_bitwidth: Default::default(), - compile_flags: Default::default(), - env_vars: Default::default(), - normalize_stderr: Default::default(), - normalize_stdout: Default::default(), - error_in_other_files: Default::default(), - error_matches: Default::default(), - require_annotations_for_level: Default::default(), - aux_builds: Default::default(), - edition: Default::default(), - mode: Default::default(), - needs_asm_support: Default::default(), - no_rustfix: Default::default(), + ..Default::default() }), }; f(&mut this); @@ -491,13 +565,13 @@ impl CommentParser<&mut Revisioned> { } } "normalize-stderr-test" => (this, args, _span){ - if let Some(res) = this.parse_normalize_test(args, "stderr") { - this.normalize_stderr.push(res) + if let Some((regex, replacement)) = this.parse_normalize_test(args, "stderr") { + this.normalize_stderr.push((regex.into(), replacement)) } } "normalize-stdout-test" => (this, args, _span){ - if let Some(res) = this.parse_normalize_test(args, "stdout") { - this.normalize_stdout.push(res) + if let Some((regex, replacement)) = this.parse_normalize_test(args, "stdout") { + this.normalize_stdout.push((regex.into(), replacement)) } } "error-pattern" => (this, _args, span){ diff --git a/src/parser/spanned.rs b/src/parser/spanned.rs index fd063c07..cbc122dd 100644 --- a/src/parser/spanned.rs +++ b/src/parser/spanned.rs @@ -1,43 +1,5 @@ pub use spanned::*; -#[derive(Default, Debug, Clone)] -pub struct MaybeSpanned { - data: T, - span: Option, -} - -impl std::ops::Deref for MaybeSpanned { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.data - } -} - -impl MaybeSpanned { - /// Values from the `Config` struct don't have lines. - pub fn new_config(data: T) -> Self { - Self { data, span: None } - } - - pub fn span(&self) -> Option { - self.span.clone() - } - - pub fn into_inner(self) -> T { - self.data - } -} - -impl From> for MaybeSpanned { - fn from(value: Spanned) -> Self { - Self { - data: value.content, - span: Some(value.span), - } - } -} - #[derive(Debug, Clone)] pub struct OptWithLine(Option>); diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 730b1855..8c058a10 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -16,7 +16,7 @@ fn main() { let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ ERROR: encountered a dangling reference (address $HEX is unallocated) } "; - let comments = Comments::parse(s, Path::new("")).unwrap(); + let comments = Comments::parse(s, Comments::default(), Path::new("")).unwrap(); println!("parsed comments: {:#?}", comments); assert_eq!(comments.revisioned.len(), 1); let revisioned = &comments.revisioned[&vec![]]; @@ -41,7 +41,7 @@ fn main() { let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ encountered a dangling reference (address $HEX is unallocated) } "; - let errors = Comments::parse(s, Path::new("")).unwrap_err(); + let errors = Comments::parse(s, Comments::default(), Path::new("")).unwrap_err(); println!("parsed comments: {:#?}", errors); assert_eq!(errors.len(), 1); match &errors[0] { @@ -59,7 +59,7 @@ fn parse_slash_slash_at() { use std::mem; "; - let comments = Comments::parse(s, Path::new("")).unwrap(); + let comments = Comments::parse(s, Comments::default(), Path::new("")).unwrap(); println!("parsed comments: {:#?}", comments); assert_eq!(comments.revisioned.len(), 1); let revisioned = &comments.revisioned[&vec![]]; @@ -75,7 +75,7 @@ fn parse_regex_error_pattern() { use std::mem; "; - let comments = Comments::parse(s, Path::new("")).unwrap(); + let comments = Comments::parse(s, Comments::default(), Path::new("")).unwrap(); println!("parsed comments: {:#?}", comments); assert_eq!(comments.revisioned.len(), 1); let revisioned = &comments.revisioned[&vec![]]; @@ -91,7 +91,7 @@ fn parse_slash_slash_at_fail() { use std::mem; "; - let errors = Comments::parse(s, Path::new("")).unwrap_err(); + let errors = Comments::parse(s, Comments::default(), Path::new("")).unwrap_err(); println!("parsed comments: {:#?}", errors); assert_eq!(errors.len(), 2); match &errors[0] { @@ -115,7 +115,7 @@ fn missing_colon_fail() { use std::mem; "; - let errors = Comments::parse(s, Path::new("")).unwrap_err(); + let errors = Comments::parse(s, Comments::default(), Path::new("")).unwrap_err(); println!("parsed comments: {:#?}", errors); assert_eq!(errors.len(), 1); match &errors[0] { @@ -129,7 +129,7 @@ use std::mem; #[test] fn parse_x86_64() { let s = r"//@ only-target-x86_64-unknown-linux"; - let comments = Comments::parse(s, Path::new("")).unwrap(); + let comments = Comments::parse(s, Comments::default(), Path::new("")).unwrap(); println!("parsed comments: {:#?}", comments); assert_eq!(comments.revisioned.len(), 1); let revisioned = &comments.revisioned[&vec![]]; diff --git a/src/per_test_config.rs b/src/per_test_config.rs new file mode 100644 index 00000000..d3e61bf5 --- /dev/null +++ b/src/per_test_config.rs @@ -0,0 +1,7 @@ +//! This module allows you to configure the default settings for all +//! tests. All data structures here are normally parsed from `@` comments +//! in the files. These comments still overwrite the defaults, although +//! some boolean settings have no way to disable them. + +pub use crate::parser::{Comments, Condition, Revisioned}; +pub use crate::rustc_stderr::Level; diff --git a/src/rustc_stderr.rs b/src/rustc_stderr.rs index 0daa422b..ec2d4ef4 100644 --- a/src/rustc_stderr.rs +++ b/src/rustc_stderr.rs @@ -15,11 +15,17 @@ struct RustcMessage { } #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] -pub(crate) enum Level { +/// The different levels of diagnostic messages and their relative ranking. +pub enum Level { + /// internal compiler errors Ice = 5, + /// ´error´ level messages Error = 4, + /// ´warn´ level messages Warn = 3, + /// ´help´ level messages Help = 2, + /// ´note´ level messages Note = 1, /// Only used for "For more information about this error, try `rustc --explain EXXXX`". FailureNote = 0, diff --git a/src/status_emitter.rs b/src/status_emitter.rs index a0540f13..de27ac6b 100644 --- a/src/status_emitter.rs +++ b/src/status_emitter.rs @@ -452,8 +452,8 @@ fn print_error(error: &Error, path: &Path) { Error::PatternFoundInPassTest { mode, span } => { let annot = [("expected because of this annotation", Some(span.clone()))]; let mut lines: Vec<(&[_], _)> = vec![(&annot, span.line_start)]; - let annot = [("expected because of this mode change", mode.clone())]; - if let Some(mode) = mode { + let annot = [("expected because of this mode change", Some(mode.clone()))]; + if !mode.is_dummy() { lines.push((&annot, mode.line_start)) } // This will print a suitable error header. @@ -562,6 +562,7 @@ fn print_error(error: &Error, path: &Path) { println!("{error}"); println!("Add //@no-rustfix to the test file to ignore rustfix suggestions"); } + Error::ConfigError(msg) => println!("{msg}"), } println!(); } @@ -778,6 +779,9 @@ fn gha_error(error: &Error, test_path: &str, revision: &str) { format!("failed to apply suggestions with rustfix: {error}"), ); } + Error::ConfigError(msg) => { + github_actions::error(test_path, msg.clone()); + } } } diff --git a/src/tests.rs b/src/tests.rs index 137001df..bf931bdd 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -22,9 +22,9 @@ fn main() { let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ ERROR: encountered a dangling reference (address $HEX is unallocated) } "; - let comments = Comments::parse(s, Path::new("")).unwrap(); - let mut errors = vec![]; let config = config(); + let comments = Comments::parse(s, config.comment_defaults.clone(), Path::new("")).unwrap(); + let mut errors = vec![]; let messages = vec![ vec![], vec![], vec![], vec![], vec![], vec![ @@ -40,7 +40,6 @@ fn main() { vec![], Path::new("moobar"), &mut errors, - &config, "", &comments, ) @@ -61,8 +60,8 @@ fn main() { let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ ERROR: encountered a dangling reference (address 0x10 is unallocated) } "; - let comments = Comments::parse(s, Path::new("")).unwrap(); let config = config(); + let comments = Comments::parse(s, config.comment_defaults.clone(), Path::new("")).unwrap(); { let messages = vec![vec![], vec![], vec![], vec![], vec![], vec![ Message { @@ -78,7 +77,6 @@ fn main() { vec![], Path::new("moobar"), &mut errors, - &config, "", &comments, ) @@ -105,7 +103,6 @@ fn main() { vec![], Path::new("moobar"), &mut errors, - &config, "", &comments, ) @@ -136,7 +133,6 @@ fn main() { vec![], Path::new("moobar"), &mut errors, - &config, "", &comments, ) @@ -159,8 +155,8 @@ fn main() { //~^ ERROR: encountered a dangling reference (address 0x10 is unallocated) } "; - let comments = Comments::parse(s, Path::new("")).unwrap(); let config = config(); + let comments = Comments::parse(s, config.comment_defaults.clone(), Path::new("")).unwrap(); let messages = vec![ vec![], vec![], vec![], vec![], vec![], vec![ @@ -177,7 +173,6 @@ fn main() { vec![], Path::new("moobar"), &mut errors, - &config, "", &comments, ) @@ -197,8 +192,8 @@ fn main() { let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ ERROR: encountered a dangling reference (address 0x10 is unallocated) } "; - let comments = Comments::parse(s, Path::new("")).unwrap(); let config = config(); + let comments = Comments::parse(s, config.comment_defaults.clone(), Path::new("")).unwrap(); let messages = vec![ vec![], vec![], vec![], vec![], vec![], vec![ @@ -220,7 +215,6 @@ fn main() { vec![], Path::new("moobar"), &mut errors, - &config, "", &comments, ) @@ -242,8 +236,8 @@ fn main() { //~^ WARN: cake } "; - let comments = Comments::parse(s, Path::new("")).unwrap(); let config = config(); + let comments = Comments::parse(s, config.comment_defaults.clone(), Path::new("")).unwrap(); let messages= vec![ vec![], vec![], @@ -274,7 +268,6 @@ fn main() { vec![], Path::new("moobar"), &mut errors, - &config, "", &comments, ) @@ -306,8 +299,8 @@ fn main() { //~^ WARN: cake } "; - let comments = Comments::parse(s, Path::new("")).unwrap(); let config = config(); + let comments = Comments::parse(s, config.comment_defaults.clone(), Path::new("")).unwrap(); let messages = vec![ vec![], vec![], @@ -338,7 +331,6 @@ fn main() { vec![], Path::new("moobar"), &mut errors, - &config, "", &comments, ) diff --git a/tests/integration.rs b/tests/integration.rs index cac20d5c..737a3e7a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,14 +1,12 @@ use std::path::Path; use ui_test::color_eyre::Result; -use ui_test::*; +use ui_test::{spanned::Spanned, *}; fn main() -> Result<()> { let path = Path::new(file!()).parent().unwrap(); let root_dir = path.join("integrations"); - let mut config = Config { - ..Config::cargo(root_dir.clone()) - }; + let mut config = Config::cargo(root_dir.clone()); let args = Args::test()?; config.with_args(&args, true); @@ -54,8 +52,10 @@ fn main() -> Result<()> { // Insert the replacement filter at the start to make sure the filter for single backslashes // runs afterwards. config - .stdout_filters - .insert(0, (Match::Exact(b"\\\\".to_vec()), b"\\")); + .comment_defaults + .base() + .normalize_stdout + .insert(0, (Match::Exact(b"\\\\".to_vec()), b"\\".to_vec())); config.stdout_filter(r#"(panic.*)\.rs:[0-9]+:[0-9]+"#, "$1.rs"); // We don't want to normalize lines starting with `+`, those are diffs of the inner ui_test // and normalizing these here doesn't make the "actual output differed from expected" go @@ -77,17 +77,13 @@ fn main() -> Result<()> { let text = ui_test::status_emitter::Text::from(args.format); + let mut pass_config = config.clone(); + pass_config.comment_defaults.base().mode = Spanned::dummy(Mode::Pass).into(); + let mut panic_config = config; + panic_config.comment_defaults.base().mode = Spanned::dummy(Mode::Panic).into(); + run_tests_generic( - vec![ - Config { - mode: Mode::Pass, - ..config.clone() - }, - Config { - mode: Mode::Panic, - ..config - }, - ], + vec![pass_config, panic_config], |path, config| { let fail = path .parent() @@ -103,7 +99,13 @@ fn main() -> Result<()> { } path.ends_with("Cargo.toml") && path.parent().unwrap().parent().unwrap() == root_dir - && match config.mode { + && match config + .comment_defaults + .base_immut() + .mode + .as_deref() + .unwrap() + { Mode::Pass => !fail, // This is weird, but `cargo test` returns 101 instead of 1 when // multiple [[test]]s exist. If there's only one test, it returns diff --git a/tests/integrations/basic-bin/Cargo.lock b/tests/integrations/basic-bin/Cargo.lock index e5da75e2..3d1f25c3 100644 --- a/tests/integrations/basic-bin/Cargo.lock +++ b/tests/integrations/basic-bin/Cargo.lock @@ -713,7 +713,7 @@ dependencies = [ [[package]] name = "ui_test" -version = "0.21.2" +version = "0.22.0" dependencies = [ "annotate-snippets", "anyhow", diff --git a/tests/integrations/basic-fail-mode/Cargo.lock b/tests/integrations/basic-fail-mode/Cargo.lock index c848fdf7..503da5b5 100644 --- a/tests/integrations/basic-fail-mode/Cargo.lock +++ b/tests/integrations/basic-fail-mode/Cargo.lock @@ -713,7 +713,7 @@ dependencies = [ [[package]] name = "ui_test" -version = "0.21.2" +version = "0.22.0" dependencies = [ "annotate-snippets", "anyhow", diff --git a/tests/integrations/basic-fail-mode/tests/ui_tests.rs b/tests/integrations/basic-fail-mode/tests/ui_tests.rs index 30b47abc..9fbf7d1c 100644 --- a/tests/integrations/basic-fail-mode/tests/ui_tests.rs +++ b/tests/integrations/basic-fail-mode/tests/ui_tests.rs @@ -1,13 +1,10 @@ -use ui_test::*; +use ui_test::{spanned::Spanned, *}; fn main() -> ui_test::color_eyre::Result<()> { let path = "../../../target"; let mut config = Config { dependencies_crate_manifest_path: Some("Cargo.toml".into()), - mode: Mode::Fail { - require_patterns: true, - rustfix: RustfixMode::MachineApplicable, - }, + output_conflict_handling: if std::env::var_os("BLESS").is_some() { OutputConflictHandling::Bless } else { @@ -15,6 +12,11 @@ fn main() -> ui_test::color_eyre::Result<()> { }, ..Config::rustc("tests/actual_tests") }; + config.comment_defaults.base().mode = Spanned::dummy(Mode::Fail { + require_patterns: true, + rustfix: RustfixMode::MachineApplicable, + }) + .into(); config.stderr_filter("in ([0-9]m )?[0-9\\.]+s", ""); config.stdout_filter("in ([0-9]m )?[0-9\\.]+s", ""); config.stderr_filter(r"[^ ]*/\.?cargo/registry/.*/", "$$CARGO_REGISTRY"); diff --git a/tests/integrations/basic-fail/Cargo.lock b/tests/integrations/basic-fail/Cargo.lock index f7f30e08..9f680b4d 100644 --- a/tests/integrations/basic-fail/Cargo.lock +++ b/tests/integrations/basic-fail/Cargo.lock @@ -713,7 +713,7 @@ dependencies = [ [[package]] name = "ui_test" -version = "0.21.2" +version = "0.22.0" dependencies = [ "annotate-snippets", "anyhow", diff --git a/tests/integrations/basic-fail/tests/ui_tests_bless.rs b/tests/integrations/basic-fail/tests/ui_tests_bless.rs index 20f481ec..fbfa01fa 100644 --- a/tests/integrations/basic-fail/tests/ui_tests_bless.rs +++ b/tests/integrations/basic-fail/tests/ui_tests_bless.rs @@ -1,4 +1,4 @@ -use ui_test::*; +use ui_test::{spanned::Spanned, *}; fn main() -> ui_test::color_eyre::Result<()> { for mode in [ @@ -25,9 +25,9 @@ fn main() -> ui_test::color_eyre::Result<()> { } else { OutputConflictHandling::Error("cargo test".to_string()) }, - mode, ..Config::rustc(root_dir) }; + config.comment_defaults.base().mode = Spanned::dummy(mode).into(); // hide binaries generated for successfully passing tests let tmp_dir = tempfile::tempdir_in(path)?; diff --git a/tests/integrations/basic/Cargo.lock b/tests/integrations/basic/Cargo.lock index 44379188..fc820c39 100644 --- a/tests/integrations/basic/Cargo.lock +++ b/tests/integrations/basic/Cargo.lock @@ -713,7 +713,7 @@ dependencies = [ [[package]] name = "ui_test" -version = "0.21.2" +version = "0.22.0" dependencies = [ "annotate-snippets", "anyhow", diff --git a/tests/integrations/basic/tests/run_file.rs b/tests/integrations/basic/tests/run_file.rs index 03bd8ead..5dc7b12d 100644 --- a/tests/integrations/basic/tests/run_file.rs +++ b/tests/integrations/basic/tests/run_file.rs @@ -59,7 +59,7 @@ fn non_utf8() -> Result<()> { } let mut config = Config::rustc(PathBuf::new()); config.program = CommandBuilder::cmd("cat"); - config.edition = None; + config.comment_defaults.base().edition = Default::default(); config.host = Some(String::new()); let mut result = ui_test::test_command(config, &path)?; diff --git a/tests/integrations/cargo-run/Cargo.lock b/tests/integrations/cargo-run/Cargo.lock index e5da75e2..3d1f25c3 100644 --- a/tests/integrations/cargo-run/Cargo.lock +++ b/tests/integrations/cargo-run/Cargo.lock @@ -713,7 +713,7 @@ dependencies = [ [[package]] name = "ui_test" -version = "0.21.2" +version = "0.22.0" dependencies = [ "annotate-snippets", "anyhow", diff --git a/tests/integrations/cargo-run/tests/ui_tests.rs b/tests/integrations/cargo-run/tests/ui_tests.rs index aa03b4bf..258dc888 100644 --- a/tests/integrations/cargo-run/tests/ui_tests.rs +++ b/tests/integrations/cargo-run/tests/ui_tests.rs @@ -1,4 +1,4 @@ -use ui_test::*; +use ui_test::{spanned::Spanned, *}; fn main() -> ui_test::color_eyre::Result<()> { let mut config = Config { @@ -7,9 +7,9 @@ fn main() -> ui_test::color_eyre::Result<()> { } else { OutputConflictHandling::Error("cargo test".to_string()) }, - mode: Mode::Panic, ..Config::cargo("tests/actual_tests") }; + config.comment_defaults.base().mode = Spanned::dummy(Mode::Panic).into(); config.program.args = vec!["run".into(), "--quiet".into()]; config.program.input_file_flag = Some("--".into());