Skip to content

Commit

Permalink
Merge pull request #134 from epage/context
Browse files Browse the repository at this point in the history
fix(assert): Show caller on panic
  • Loading branch information
epage committed Sep 6, 2021
2 parents 2670f36 + 150d7be commit bb8ef0c
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 99 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Expand Up @@ -69,7 +69,7 @@ jobs:
- name: No-default features
run: cargo test --workspace --no-default-features
msrv:
name: "Check MSRV: 1.44.0"
name: "Check MSRV: 1.46.0"
needs: smoke
runs-on: ubuntu-latest
steps:
Expand All @@ -78,7 +78,7 @@ jobs:
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: 1.44.0 # MSRV
toolchain: 1.46.0 # MSRV
profile: minimal
override: true
- uses: Swatinem/rust-cache@v1
Expand Down Expand Up @@ -133,7 +133,7 @@ jobs:
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: 1.44.0 # MSRV
toolchain: 1.46.0 # MSRV
profile: minimal
override: true
components: clippy
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/rust-next.yml
Expand Up @@ -66,9 +66,9 @@ jobs:
strategy:
matrix:
rust:
- 1.44.0 # MSRV
- 1.46.0 # MSRV
- stable
continue-on-error: ${{ matrix.rust != '1.44.0' }} # MSRV
continue-on-error: ${{ matrix.rust != '1.46.0' }} # MSRV
runs-on: ubuntu-latest
steps:
- name: Checkout repository
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
<!-- next-header -->
## [Unreleased] - ReleaseDate

#### Fixes

- Show caller for panic, rather than `assert_cmd`

## [2.0.0] - 2021-08-05

### Breaking Changes
Expand Down
195 changes: 101 additions & 94 deletions src/assert.rs
Expand Up @@ -66,100 +66,6 @@ impl<'c> OutputAssertExt for &'c mut process::Command {
}
}

/// [`Assert`] represented as a [`Result`].
///
/// Produced by the `try_` variants the [`Assert`] methods.
///
/// # Example
///
/// ```rust
/// use assert_cmd::prelude::*;
///
/// use std::process::Command;
///
/// let result = Command::new("echo")
/// .assert()
/// .try_success();
/// assert!(result.is_ok());
/// ```
///
/// [`Result`]: std::result::Result
pub type AssertResult = Result<Assert, AssertError>;

/// [`Assert`] error (see [`AssertResult`]).
#[derive(Debug)]
pub struct AssertError {
assert: Assert,
reason: AssertReason,
}

#[derive(Debug)]
enum AssertReason {
UnexpectedFailure { actual_code: Option<i32> },
UnexpectedSuccess,
UnexpectedCompletion,
CommandInterrupted,
UnexpectedReturnCode { case_tree: CaseTree },
UnexpectedStdout { case_tree: CaseTree },
UnexpectedStderr { case_tree: CaseTree },
}

struct CaseTree(predicates_tree::CaseTree);

impl fmt::Display for CaseTree {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<predicates_tree::CaseTree as fmt::Display>::fmt(&self.0, f)
}
}

// Work around `Debug` not being implemented for `predicates_tree::CaseTree`.
impl fmt::Debug for CaseTree {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<predicates_tree::CaseTree as fmt::Display>::fmt(&self.0, f)
}
}

impl AssertError {
fn panic<T>(self) -> T {
panic!("{}", self.to_string())
}
}

impl Error for AssertError {}

impl fmt::Display for AssertError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.reason {
AssertReason::UnexpectedFailure { actual_code } => writeln!(
f,
"Unexpected failure.\ncode-{}\nstderr=```{}```",
actual_code.map_or("<interrupted>".to_owned(), |actual_code| actual_code
.to_string()),
DebugBytes::new(&self.assert.output.stderr),
),
AssertReason::UnexpectedSuccess => {
writeln!(f, "Unexpected success")
}
AssertReason::UnexpectedCompletion => {
writeln!(f, "Unexpected completion")
}
AssertReason::CommandInterrupted => {
writeln!(f, "Command interrupted")
}
AssertReason::UnexpectedReturnCode { case_tree } => {
writeln!(f, "Unexpected return code, failed {}", case_tree)
}
AssertReason::UnexpectedStdout { case_tree } => {
writeln!(f, "Unexpected stdout, failed {}", case_tree)
}
AssertReason::UnexpectedStderr { case_tree } => {
writeln!(f, "Unexpected stderr, failed {}", case_tree)
}
}?;
write!(f, "{}", self.assert)
}
}

/// Assert the state of an [`Output`].
///
/// Create an `Assert` through the [`OutputAssertExt`] trait.
Expand Down Expand Up @@ -245,6 +151,7 @@ impl Assert {
/// .assert()
/// .success();
/// ```
#[track_caller]
pub fn success(self) -> Self {
self.try_success().unwrap_or_else(AssertError::panic)
}
Expand Down Expand Up @@ -273,6 +180,7 @@ impl Assert {
/// .assert()
/// .failure();
/// ```
#[track_caller]
pub fn failure(self) -> Self {
self.try_failure().unwrap_or_else(AssertError::panic)
}
Expand All @@ -286,6 +194,7 @@ impl Assert {
}

/// Ensure the command aborted before returning a code.
#[track_caller]
pub fn interrupted(self) -> Self {
self.try_interrupted().unwrap_or_else(AssertError::panic)
}
Expand Down Expand Up @@ -346,6 +255,7 @@ impl Assert {
/// .code(&[2, 42] as &[i32]);
/// ```
///
#[track_caller]
pub fn code<I, P>(self, pred: I) -> Self
where
I: IntoCodePredicate<P>,
Expand Down Expand Up @@ -443,6 +353,7 @@ impl Assert {
/// .stdout("hello\n");
/// ```
///
#[track_caller]
pub fn stdout<I, P>(self, pred: I) -> Self
where
I: IntoOutputPredicate<P>,
Expand Down Expand Up @@ -538,6 +449,7 @@ impl Assert {
/// .stderr("world\n");
/// ```
///
#[track_caller]
pub fn stderr<I, P>(self, pred: I) -> Self
where
I: IntoOutputPredicate<P>,
Expand Down Expand Up @@ -1074,6 +986,101 @@ where
}
}

/// [`Assert`] represented as a [`Result`].
///
/// Produced by the `try_` variants the [`Assert`] methods.
///
/// # Example
///
/// ```rust
/// use assert_cmd::prelude::*;
///
/// use std::process::Command;
///
/// let result = Command::new("echo")
/// .assert()
/// .try_success();
/// assert!(result.is_ok());
/// ```
///
/// [`Result`]: std::result::Result
pub type AssertResult = Result<Assert, AssertError>;

/// [`Assert`] error (see [`AssertResult`]).
#[derive(Debug)]
pub struct AssertError {
assert: Assert,
reason: AssertReason,
}

#[derive(Debug)]
enum AssertReason {
UnexpectedFailure { actual_code: Option<i32> },
UnexpectedSuccess,
UnexpectedCompletion,
CommandInterrupted,
UnexpectedReturnCode { case_tree: CaseTree },
UnexpectedStdout { case_tree: CaseTree },
UnexpectedStderr { case_tree: CaseTree },
}

impl AssertError {
#[track_caller]
fn panic<T>(self) -> T {
panic!("{}", self)
}
}

impl Error for AssertError {}

impl fmt::Display for AssertError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.reason {
AssertReason::UnexpectedFailure { actual_code } => writeln!(
f,
"Unexpected failure.\ncode-{}\nstderr=```{}```",
actual_code.map_or("<interrupted>".to_owned(), |actual_code| actual_code
.to_string()),
DebugBytes::new(&self.assert.output.stderr),
),
AssertReason::UnexpectedSuccess => {
writeln!(f, "Unexpected success")
}
AssertReason::UnexpectedCompletion => {
writeln!(f, "Unexpected completion")
}
AssertReason::CommandInterrupted => {
writeln!(f, "Command interrupted")
}
AssertReason::UnexpectedReturnCode { case_tree } => {
writeln!(f, "Unexpected return code, failed {}", case_tree)
}
AssertReason::UnexpectedStdout { case_tree } => {
writeln!(f, "Unexpected stdout, failed {}", case_tree)
}
AssertReason::UnexpectedStderr { case_tree } => {
writeln!(f, "Unexpected stderr, failed {}", case_tree)
}
}?;
write!(f, "{}", self.assert)
}
}

struct CaseTree(predicates_tree::CaseTree);

impl fmt::Display for CaseTree {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<predicates_tree::CaseTree as fmt::Display>::fmt(&self.0, f)
}
}

// Work around `Debug` not being implemented for `predicates_tree::CaseTree`.
impl fmt::Debug for CaseTree {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<predicates_tree::CaseTree as fmt::Display>::fmt(&self.0, f)
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down

0 comments on commit bb8ef0c

Please sign in to comment.