Switch cargo-test-support to anyhow.

This commit is contained in:
Eric Huss 2021-06-11 12:37:27 -07:00
parent 81537ee3f7
commit 5d1b0f9c43

View File

@ -16,6 +16,7 @@ use std::process::{Command, Output};
use std::str; use std::str;
use std::time::{self, Duration}; use std::time::{self, Duration};
use anyhow::{bail, format_err, Result};
use cargo_util::{is_ci, ProcessBuilder, ProcessError}; use cargo_util::{is_ci, ProcessBuilder, ProcessError};
use serde_json::{self, Value}; use serde_json::{self, Value};
use url::Url; use url::Url;
@ -413,19 +414,6 @@ pub fn main_file(println: &str, deps: &[&str]) -> String {
buf buf
} }
trait ErrMsg<T> {
fn with_err_msg(self, val: String) -> Result<T, String>;
}
impl<T, E: fmt::Display> ErrMsg<T> for Result<T, E> {
fn with_err_msg(self, val: String) -> Result<T, String> {
match self {
Ok(val) => Ok(val),
Err(err) => Err(format!("{}; original={}", val, err)),
}
}
}
// Path to cargo executables // Path to cargo executables
pub fn cargo_dir() -> PathBuf { pub fn cargo_dir() -> PathBuf {
env::var_os("CARGO_BIN_PATH") env::var_os("CARGO_BIN_PATH")
@ -452,8 +440,6 @@ pub fn cargo_exe() -> PathBuf {
* *
*/ */
pub type MatchResult = Result<(), String>;
#[must_use] #[must_use]
#[derive(Clone)] #[derive(Clone)]
pub struct Execs { pub struct Execs {
@ -703,7 +689,7 @@ impl Execs {
self self
} }
pub fn exec_with_output(&mut self) -> anyhow::Result<Output> { pub fn exec_with_output(&mut self) -> Result<Output> {
self.ran = true; self.ran = true;
// TODO avoid unwrap // TODO avoid unwrap
let p = (&self.process_builder).clone().unwrap(); let p = (&self.process_builder).clone().unwrap();
@ -778,7 +764,7 @@ impl Execs {
} }
} }
fn match_process(&self, process: &ProcessBuilder) -> MatchResult { fn match_process(&self, process: &ProcessBuilder) -> Result<()> {
println!("running {}", process); println!("running {}", process);
let res = if self.stream_output { let res = if self.stream_output {
if is_ci() { if is_ci() {
@ -814,32 +800,32 @@ impl Execs {
.and(self.match_stdout(stdout, stderr)) .and(self.match_stdout(stdout, stderr))
.and(self.match_stderr(stdout, stderr)); .and(self.match_stderr(stdout, stderr));
} }
Err(format!("could not exec process {}: {:?}", process, e)) bail!("could not exec process {}: {:?}", process, e)
} }
} }
} }
fn match_output(&self, actual: &Output) -> MatchResult { fn match_output(&self, actual: &Output) -> Result<()> {
self.verify_checks_output(actual); self.verify_checks_output(actual);
self.match_status(actual.status.code(), &actual.stdout, &actual.stderr) self.match_status(actual.status.code(), &actual.stdout, &actual.stderr)
.and(self.match_stdout(&actual.stdout, &actual.stderr)) .and(self.match_stdout(&actual.stdout, &actual.stderr))
.and(self.match_stderr(&actual.stdout, &actual.stderr)) .and(self.match_stderr(&actual.stdout, &actual.stderr))
} }
fn match_status(&self, code: Option<i32>, stdout: &[u8], stderr: &[u8]) -> MatchResult { fn match_status(&self, code: Option<i32>, stdout: &[u8], stderr: &[u8]) -> Result<()> {
match self.expect_exit_code { match self.expect_exit_code {
None => Ok(()), None => Ok(()),
Some(expected) if code == Some(expected) => Ok(()), Some(expected) if code == Some(expected) => Ok(()),
Some(_) => Err(format!( Some(_) => bail!(
"exited with {:?}\n--- stdout\n{}\n--- stderr\n{}", "exited with {:?}\n--- stdout\n{}\n--- stderr\n{}",
code, code,
String::from_utf8_lossy(stdout), String::from_utf8_lossy(stdout),
String::from_utf8_lossy(stderr) String::from_utf8_lossy(stderr)
)), ),
} }
} }
fn match_stdout(&self, stdout: &[u8], stderr: &[u8]) -> MatchResult { fn match_stdout(&self, stdout: &[u8], stderr: &[u8]) -> Result<()> {
self.match_std( self.match_std(
self.expect_stdout.as_ref(), self.expect_stdout.as_ref(),
stdout, stdout,
@ -908,12 +894,12 @@ impl Execs {
self.match_std(Some(expect), stderr, "stderr", stderr, MatchKind::Partial); self.match_std(Some(expect), stderr, "stderr", stderr, MatchKind::Partial);
if let (Err(_), Err(_)) = (match_std, match_err) { if let (Err(_), Err(_)) = (match_std, match_err) {
return Err(format!( bail!(
"expected to find:\n\ "expected to find:\n\
{}\n\n\ {}\n\n\
did not find in either output.", did not find in either output.",
expect expect
)); );
} }
} }
@ -923,18 +909,18 @@ impl Execs {
if let Some(ref objects) = self.expect_json { if let Some(ref objects) = self.expect_json {
let stdout = let stdout =
str::from_utf8(stdout).map_err(|_| "stdout was not utf8 encoded".to_owned())?; str::from_utf8(stdout).map_err(|_| format_err!("stdout was not utf8 encoded"))?;
let lines = stdout let lines = stdout
.lines() .lines()
.filter(|line| line.starts_with('{')) .filter(|line| line.starts_with('{'))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if lines.len() != objects.len() { if lines.len() != objects.len() {
return Err(format!( bail!(
"expected {} json lines, got {}, stdout:\n{}", "expected {} json lines, got {}, stdout:\n{}",
objects.len(), objects.len(),
lines.len(), lines.len(),
stdout stdout
)); );
} }
for (obj, line) in objects.iter().zip(lines) { for (obj, line) in objects.iter().zip(lines) {
self.match_json(obj, line)?; self.match_json(obj, line)?;
@ -943,7 +929,7 @@ impl Execs {
if !self.expect_json_contains_unordered.is_empty() { if !self.expect_json_contains_unordered.is_empty() {
let stdout = let stdout =
str::from_utf8(stdout).map_err(|_| "stdout was not utf8 encoded".to_owned())?; str::from_utf8(stdout).map_err(|_| format_err!("stdout was not utf8 encoded"))?;
let mut lines = stdout let mut lines = stdout
.lines() .lines()
.filter(|line| line.starts_with('{')) .filter(|line| line.starts_with('{'))
@ -955,14 +941,14 @@ impl Execs {
{ {
Some(index) => lines.remove(index), Some(index) => lines.remove(index),
None => { None => {
return Err(format!( bail!(
"Did not find expected JSON:\n\ "Did not find expected JSON:\n\
{}\n\ {}\n\
Remaining available output:\n\ Remaining available output:\n\
{}\n", {}\n",
serde_json::to_string_pretty(obj).unwrap(), serde_json::to_string_pretty(obj).unwrap(),
lines.join("\n") lines.join("\n")
)); );
} }
}; };
} }
@ -970,7 +956,7 @@ impl Execs {
Ok(()) Ok(())
} }
fn match_stderr(&self, stdout: &[u8], stderr: &[u8]) -> MatchResult { fn match_stderr(&self, stdout: &[u8], stderr: &[u8]) -> Result<()> {
self.match_std( self.match_std(
self.expect_stderr.as_ref(), self.expect_stderr.as_ref(),
stderr, stderr,
@ -980,9 +966,9 @@ impl Execs {
) )
} }
fn normalize_actual(&self, description: &str, actual: &[u8]) -> Result<String, String> { fn normalize_actual(&self, description: &str, actual: &[u8]) -> Result<String> {
let actual = match str::from_utf8(actual) { let actual = match str::from_utf8(actual) {
Err(..) => return Err(format!("{} was not utf8 encoded", description)), Err(..) => bail!("{} was not utf8 encoded", description),
Ok(actual) => actual, Ok(actual) => actual,
}; };
Ok(self.normalize_matcher(actual)) Ok(self.normalize_matcher(actual))
@ -1002,7 +988,7 @@ impl Execs {
description: &str, description: &str,
extra: &[u8], extra: &[u8],
kind: MatchKind, kind: MatchKind,
) -> MatchResult { ) -> Result<()> {
let out = match expected { let out = match expected {
Some(out) => self.normalize_matcher(out), Some(out) => self.normalize_matcher(out),
None => return Ok(()), None => return Ok(()),
@ -1019,14 +1005,14 @@ impl Execs {
if diffs.is_empty() { if diffs.is_empty() {
Ok(()) Ok(())
} else { } else {
Err(format!( bail!(
"differences:\n\ "differences:\n\
{}\n\n\ {}\n\n\
other output:\n\ other output:\n\
`{}`", `{}`",
diffs.join("\n"), diffs.join("\n"),
String::from_utf8_lossy(extra) String::from_utf8_lossy(extra)
)) )
} }
} }
MatchKind::Partial => { MatchKind::Partial => {
@ -1043,13 +1029,14 @@ impl Execs {
if diffs.is_empty() { if diffs.is_empty() {
Ok(()) Ok(())
} else { } else {
Err(format!( bail!(
"expected to find:\n\ "expected to find:\n\
{}\n\n\ {}\n\n\
did not find in output:\n\ did not find in output:\n\
{}", {}",
out, actual out,
)) actual
)
} }
} }
MatchKind::PartialN(number) => { MatchKind::PartialN(number) => {
@ -1068,13 +1055,15 @@ impl Execs {
if matches == number { if matches == number {
Ok(()) Ok(())
} else { } else {
Err(format!( bail!(
"expected to find {} occurrences:\n\ "expected to find {} occurrences:\n\
{}\n\n\ {}\n\n\
did not find in output:\n\ did not find in output:\n\
{}", {}",
number, out, actual number,
)) out,
actual
)
} }
} }
MatchKind::NotPresent => { MatchKind::NotPresent => {
@ -1089,13 +1078,14 @@ impl Execs {
} }
} }
if diffs.is_empty() { if diffs.is_empty() {
Err(format!( bail!(
"expected not to find:\n\ "expected not to find:\n\
{}\n\n\ {}\n\n\
but found in output:\n\ but found in output:\n\
{}", {}",
out, actual out,
)) actual
)
} else { } else {
Ok(()) Ok(())
} }
@ -1104,12 +1094,7 @@ impl Execs {
} }
} }
fn match_with_without( fn match_with_without(&self, actual: &[u8], with: &[String], without: &[String]) -> Result<()> {
&self,
actual: &[u8],
with: &[String],
without: &[String],
) -> MatchResult {
let actual = self.normalize_actual("stderr", actual)?; let actual = self.normalize_actual("stderr", actual)?;
let contains = |s, line| { let contains = |s, line| {
let mut s = self.normalize_matcher(s); let mut s = self.normalize_matcher(s);
@ -1123,16 +1108,18 @@ impl Execs {
.filter(|line| !without.iter().any(|without| contains(without, line))) .filter(|line| !without.iter().any(|without| contains(without, line)))
.collect(); .collect();
match matches.len() { match matches.len() {
0 => Err(format!( 0 => bail!(
"Could not find expected line in output.\n\ "Could not find expected line in output.\n\
With contents: {:?}\n\ With contents: {:?}\n\
Without contents: {:?}\n\ Without contents: {:?}\n\
Actual stderr:\n\ Actual stderr:\n\
{}\n", {}\n",
with, without, actual with,
)), without,
actual
),
1 => Ok(()), 1 => Ok(()),
_ => Err(format!( _ => bail!(
"Found multiple matching lines, but only expected one.\n\ "Found multiple matching lines, but only expected one.\n\
With contents: {:?}\n\ With contents: {:?}\n\
Without contents: {:?}\n\ Without contents: {:?}\n\
@ -1141,17 +1128,17 @@ impl Execs {
with, with,
without, without,
matches.join("\n") matches.join("\n")
)), ),
} }
} }
fn match_json(&self, expected: &str, line: &str) -> MatchResult { fn match_json(&self, expected: &str, line: &str) -> Result<()> {
let actual = match line.parse() { let actual = match line.parse() {
Err(e) => return Err(format!("invalid json, {}:\n`{}`", e, line)), Err(e) => bail!("invalid json, {}:\n`{}`", e, line),
Ok(actual) => actual, Ok(actual) => actual,
}; };
let expected = match expected.parse() { let expected = match expected.parse() {
Err(e) => return Err(format!("invalid json, {}:\n`{}`", e, line)), Err(e) => bail!("invalid json, {}:\n`{}`", e, line),
Ok(expected) => expected, Ok(expected) => expected,
}; };
@ -1231,7 +1218,7 @@ pub fn lines_match(expected: &str, mut actual: &str) -> bool {
actual.is_empty() || expected.ends_with("[..]") actual.is_empty() || expected.ends_with("[..]")
} }
pub fn lines_match_unordered(expected: &str, actual: &str) -> Result<(), String> { pub fn lines_match_unordered(expected: &str, actual: &str) -> Result<()> {
let mut a = actual.lines().collect::<Vec<_>>(); let mut a = actual.lines().collect::<Vec<_>>();
// match more-constrained lines first, although in theory we'll // match more-constrained lines first, although in theory we'll
// need some sort of recursive match here. This handles the case // need some sort of recursive match here. This handles the case
@ -1252,19 +1239,19 @@ pub fn lines_match_unordered(expected: &str, actual: &str) -> Result<(), String>
} }
} }
if !failures.is_empty() { if !failures.is_empty() {
return Err(format!( bail!(
"Did not find expected line(s):\n{}\n\ "Did not find expected line(s):\n{}\n\
Remaining available output:\n{}\n", Remaining available output:\n{}\n",
failures.join("\n"), failures.join("\n"),
a.join("\n") a.join("\n")
)); );
} }
if !a.is_empty() { if !a.is_empty() {
Err(format!( bail!(
"Output included extra lines:\n\ "Output included extra lines:\n\
{}\n", {}\n",
a.join("\n") a.join("\n")
)) )
} else { } else {
Ok(()) Ok(())
} }
@ -1334,19 +1321,15 @@ fn lines_match_works() {
/// as paths). You can use a `"{...}"` string literal as a wildcard for /// as paths). You can use a `"{...}"` string literal as a wildcard for
/// arbitrary nested JSON (useful for parts of object emitted by other programs /// arbitrary nested JSON (useful for parts of object emitted by other programs
/// (e.g., rustc) rather than Cargo itself). /// (e.g., rustc) rather than Cargo itself).
pub fn find_json_mismatch( pub fn find_json_mismatch(expected: &Value, actual: &Value, cwd: Option<&Path>) -> Result<()> {
expected: &Value,
actual: &Value,
cwd: Option<&Path>,
) -> Result<(), String> {
match find_json_mismatch_r(expected, actual, cwd) { match find_json_mismatch_r(expected, actual, cwd) {
Some((expected_part, actual_part)) => Err(format!( Some((expected_part, actual_part)) => bail!(
"JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n", "JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n",
serde_json::to_string_pretty(expected).unwrap(), serde_json::to_string_pretty(expected).unwrap(),
serde_json::to_string_pretty(&actual).unwrap(), serde_json::to_string_pretty(&actual).unwrap(),
serde_json::to_string_pretty(expected_part).unwrap(), serde_json::to_string_pretty(expected_part).unwrap(),
serde_json::to_string_pretty(actual_part).unwrap(), serde_json::to_string_pretty(actual_part).unwrap(),
)), ),
None => Ok(()), None => Ok(()),
} }
} }