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

View File

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