Actually use eyre and get rid of the ad-hoc macros emulating error handling

This commit is contained in:
Oli Scherer 2022-07-06 09:44:03 +00:00
parent 570032b0dd
commit 54b6b03410
4 changed files with 89 additions and 107 deletions

View File

@ -2,7 +2,7 @@ use colored::*;
use regex::Regex;
use std::env;
use std::path::PathBuf;
use ui_test::{Config, Mode, OutputConflictHandling, color_eyre::Result};
use ui_test::{color_eyre::Result, Config, Mode, OutputConflictHandling};
fn miri_path() -> PathBuf {
PathBuf::from(option_env!("MIRI").unwrap_or(env!("CARGO_BIN_EXE_miri")))

View File

@ -4,7 +4,7 @@ use regex::Regex;
use crate::rustc_stderr::Level;
use color_eyre::eyre::Result;
use color_eyre::eyre::{bail, ensure, eyre, Result};
#[cfg(test)]
mod tests;
@ -66,43 +66,6 @@ impl Condition {
}
}
macro_rules! checked {
($path:expr, $l:expr) => {
let path = $path;
let l = $l;
#[allow(unused_macros)]
macro_rules! exit {
($fmt:expr $$(,$args:expr)*) => {{
eprint!("{}:{l}: ", path.display());
eprintln!($fmt, $$($args,)*);
#[cfg(not(test))]
std::process::exit(1);
#[cfg(test)]
panic!();
}};
}
#[allow(unused_macros)]
macro_rules! check {
($cond:expr, $fmt:expr $$(,$args:expr)*) => {{
if !$cond {
exit!($fmt $$(,$args)*);
}
}};
}
#[allow(unused_macros)]
macro_rules! unwrap {
($cond:expr, $fmt:expr $$(,$args:expr)*) => {{
match $cond {
Some(val) => val,
None => {
exit!($fmt $$(,$args)*);
}
}
}};
}
};
}
impl Comments {
pub(crate) fn parse_file(path: &Path) -> Result<Self> {
let content = std::fs::read_to_string(path)?;
@ -121,26 +84,38 @@ impl Comments {
let mut fallthrough_to = None;
for (l, line) in content.lines().enumerate() {
let l = l + 1; // enumerate starts at 0, but line numbers start at 1
if let Some((_, command)) = line.split_once("//@") {
let command = command.trim();
if let Some((command, args)) = command.split_once(':') {
this.parse_command_with_args(command, args, path, l);
} else if let Some((command, _comments)) = command.split_once(' ') {
this.parse_command(command, path, l)
} else {
this.parse_command(command, path, l)
}
} else if let Some((_, pattern)) = line.split_once("//~") {
this.parse_pattern(pattern, &mut fallthrough_to, path, l)
} else if let Some((_, pattern)) = line.split_once("//[") {
this.parse_revisioned_pattern(pattern, &mut fallthrough_to, path, l)
} else {
fallthrough_to = None;
}
this.parse_checked_line(l, &mut fallthrough_to, line).map_err(|err| {
err.wrap_err(format!("{}:{l}: failed to parse annotation", path.display()))
})?;
}
Ok(this)
}
fn parse_checked_line(
&mut self,
l: usize,
fallthrough_to: &mut Option<usize>,
line: &str,
) -> Result<()> {
if let Some((_, command)) = line.split_once("//@") {
let command = command.trim();
if let Some((command, args)) = command.split_once(':') {
self.parse_command_with_args(command, args, l)
} else if let Some((command, _comments)) = command.split_once(' ') {
self.parse_command(command)
} else {
self.parse_command(command)
}
} else if let Some((_, pattern)) = line.split_once("//~") {
self.parse_pattern(pattern, fallthrough_to, l)
} else if let Some((_, pattern)) = line.split_once("//[") {
self.parse_revisioned_pattern(pattern, fallthrough_to, l)
} else {
*fallthrough_to = None;
Ok(())
}
}
/// Parse comments in `content`.
/// `path` is only used to emit diagnostics if parsing fails.
pub(crate) fn parse(path: &Path, content: &str) -> Result<Self> {
@ -214,11 +189,10 @@ impl Comments {
Ok(this)
}
fn parse_command_with_args(&mut self, command: &str, args: &str, path: &Path, l: usize) {
checked!(path, l);
fn parse_command_with_args(&mut self, command: &str, args: &str, l: usize) -> Result<()> {
match command {
"revisions" => {
check!(self.revisions.is_none(), "cannot specifiy revisions twice");
ensure!(self.revisions.is_none(), "cannot specifiy revisions twice");
self.revisions = Some(args.split_whitespace().map(|s| s.to_string()).collect());
}
"compile-flags" => {
@ -226,22 +200,22 @@ impl Comments {
}
"rustc-env" =>
for env in args.split_whitespace() {
let (k, v) = unwrap!(
env.split_once('='),
"environment variables must be key/value pairs separated by a `=`"
);
let (k, v) = env.split_once('=').ok_or_else(|| {
eyre!("environment variables must be key/value pairs separated by a `=`")
})?;
self.env_vars.push((k.to_string(), v.to_string()));
},
"normalize-stderr-test" => {
let (from, to) =
unwrap!(args.split_once("->"), "normalize-stderr-test needs a `->`");
let (from, to) = args
.split_once("->")
.ok_or_else(|| eyre!("normalize-stderr-test needs a `->`"))?;
let from = from.trim().trim_matches('"');
let to = to.trim().trim_matches('"');
let from = unwrap!(Regex::new(from).ok(), "invalid regex");
let from = Regex::new(from).ok().ok_or_else(|| eyre!("invalid regex"))?;
self.normalize_stderr.push((from, to.to_string()));
}
"error-pattern" => {
check!(
ensure!(
self.error_pattern.is_none(),
"cannot specifiy error_pattern twice, previous: {:?}",
self.error_pattern
@ -249,56 +223,52 @@ impl Comments {
self.error_pattern = Some((args.trim().to_string(), l));
}
// Maybe the user just left a comment explaining a command without arguments
_ => self.parse_command(command, path, l),
_ => self.parse_command(command)?,
}
Ok(())
}
fn parse_command(&mut self, command: &str, path: &Path, l: usize) {
checked!(path, l);
fn parse_command(&mut self, command: &str) -> Result<()> {
if let Some(s) = command.strip_prefix("ignore-") {
self.ignore.push(Condition::parse(s));
return;
return Ok(());
}
if let Some(s) = command.strip_prefix("only-") {
self.only.push(Condition::parse(s));
return;
return Ok(());
}
if command.starts_with("stderr-per-bitwidth") {
check!(!self.stderr_per_bitwidth, "cannot specifiy stderr-per-bitwidth twice");
ensure!(!self.stderr_per_bitwidth, "cannot specifiy stderr-per-bitwidth twice");
self.stderr_per_bitwidth = true;
return;
return Ok(());
}
exit!("unknown command {command}");
bail!("unknown command {command}");
}
fn parse_pattern(
&mut self,
pattern: &str,
fallthrough_to: &mut Option<usize>,
path: &Path,
l: usize,
) {
self.parse_pattern_inner(pattern, fallthrough_to, None, path, l)
) -> Result<()> {
self.parse_pattern_inner(pattern, fallthrough_to, None, l)
}
fn parse_revisioned_pattern(
&mut self,
pattern: &str,
fallthrough_to: &mut Option<usize>,
path: &Path,
l: usize,
) {
checked!(path, l);
) -> Result<()> {
let (revision, pattern) =
unwrap!(pattern.split_once(']'), "`//[` without corresponding `]`");
pattern.split_once(']').ok_or_else(|| eyre!("`//[` without corresponding `]`"))?;
if let Some(pattern) = pattern.strip_prefix('~') {
self.parse_pattern_inner(pattern, fallthrough_to, Some(revision.to_owned()), path, l)
self.parse_pattern_inner(pattern, fallthrough_to, Some(revision.to_owned()), l)
} else {
exit!("revisioned pattern must have `~` following the `]`");
bail!("revisioned pattern must have `~` following the `]`");
}
}
@ -308,21 +278,25 @@ impl Comments {
pattern: &str,
fallthrough_to: &mut Option<usize>,
revision: Option<String>,
path: &Path,
l: usize,
) {
checked!(path, l);
) -> Result<()> {
// FIXME: check that the error happens on the marked line
let (match_line, pattern) = match unwrap!(pattern.chars().next(), "no pattern specified") {
'|' =>
(*unwrap!(fallthrough_to, "`//~|` pattern without preceding line"), &pattern[1..]),
'^' => {
let offset = pattern.chars().take_while(|&c| c == '^').count();
(l - offset, &pattern[offset..])
}
_ => (l, pattern),
};
let (match_line, pattern) =
match pattern.chars().next().ok_or_else(|| eyre!("no pattern specified"))? {
'|' =>
(
*fallthrough_to
.as_mut()
.ok_or_else(|| eyre!("`//~|` pattern without preceding line"))?,
&pattern[1..],
),
'^' => {
let offset = pattern.chars().take_while(|&c| c == '^').count();
(l - offset, &pattern[offset..])
}
_ => (l, pattern),
};
let (level, pattern) = match pattern.trim_start().split_once(|c| matches!(c, ':' | ' ')) {
None => (None, pattern),
@ -335,7 +309,7 @@ impl Comments {
let matched = pattern.trim().to_string();
check!(!matched.is_empty(), "no pattern specified");
ensure!(!matched.is_empty(), "no pattern specified");
*fallthrough_to = Some(match_line);
@ -346,5 +320,7 @@ impl Comments {
definition_line: l,
line: match_line,
});
Ok(())
}
}

View File

@ -1,9 +1,9 @@
use std::{path::Path, panic::catch_unwind};
use std::path::Path;
use super::Comments;
use color_eyre::eyre::{Result, bail};
use crate::tests::init;
use color_eyre::eyre::{bail, Result};
#[test]
fn parse_simple_comment() -> Result<()> {
@ -48,8 +48,8 @@ fn parse_slash_slash_at_fail() -> Result<()> {
use std::mem;
";
match catch_unwind(|| Comments::parse(Path::new("<dummy>"), s)) {
Ok(_) => bail!("expected parsing to panic"),
match Comments::parse(Path::new("<dummy>"), s) {
Ok(_) => bail!("expected parsing to fail"),
Err(_) => Ok(()),
}
}

View File

@ -7,12 +7,12 @@ use std::process::{Command, ExitStatus};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
pub use color_eyre;
use color_eyre::eyre::Result;
use colored::*;
use comments::ErrorMatch;
use regex::Regex;
use rustc_stderr::{Level, Message};
use color_eyre::eyre::Result;
pub use color_eyre;
use crate::comments::{Comments, Condition};
@ -68,7 +68,7 @@ pub fn run_tests(config: Config) -> Result<()> {
let ignored = AtomicUsize::default();
let filtered = AtomicUsize::default();
crossbeam::scope(|s| {
crossbeam::scope(|s| -> Result<()> {
// Create a thread that is in charge of walking the directory and submitting jobs.
// It closes the channel when it is done.
s.spawn(|_| {
@ -94,9 +94,11 @@ pub fn run_tests(config: Config) -> Result<()> {
drop(submit);
});
let mut threads = vec![];
// Create N worker threads that receive files to test.
for _ in 0..std::thread::available_parallelism().unwrap().get() {
s.spawn(|_| -> Result<()> {
threads.push(s.spawn(|_| -> Result<()> {
for path in &receive {
if !config.path_filter.is_empty() {
let path_display = path.display().to_string();
@ -145,10 +147,14 @@ pub fn run_tests(config: Config) -> Result<()> {
}
}
Ok(())
});
}));
}
for thread in threads {
thread.join().unwrap()?;
}
Ok(())
})
.unwrap();
.unwrap()?;
// Print all errors in a single thread to show reliable output
let failures = failures.into_inner().unwrap();