Repository: assert-rs/assert_cmd (Rust)

Issue:
`main_binary` takes ~300ms per invocation
From @rcoh https://github.com/assert-rs/assert_cli/issues/100

<!--
Hi! If you want to report a bug, request a feature, or ask a question on how to
use assert_cli, you have come to the right place!

If you want to report a bug, please fill in the following. Otherwise, feel free
to remove these lines.
-->

- `assert_cli` version: 0.5
- Rust version: 1.25.0
- OS and version: Ubuntu 16.04
Unfortunately, `cargo run` takes at minimum about 300ms on my computer:
```
➜  angle-grinder git:(master) ✗ time cargo run --bin agrind -- '* | json | count' --file test_files/empty
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/agrind '* | json | count' --file test_files/empty`
No data
cargo run --bin agrind -- '* | json | count' --file test_files/empty  0.22s user 0.06s system 92% cpu 0.307 total
```

It makes it impractical to run hundreds of integration tests with assert_cli.

I'm not positive what a better way would be -- I guess running the binary in dist directly if it exists?

### Workaround

See [assert_cmd::cargo's docs](https://docs.rs/assert_cmd/0.10.1/assert_cmd/cargo/index.html)
`main_binary` takes minutes on first run
`cargo test` takes a long time with because the first run of `main_binary` takes a long time while holding the `cargo` locking, blocking other tests

Example
```
running 6 tests
test blackbox_backup ... test blackbox_backup has been running for over 60 seconds
test blackbox_help ... test blackbox_help has been running for over 60 seconds
test blackbox_help ... ok
test blackbox_no_args ... ok
test clean_error_on_non_archive ... ok
test empty_archive ... ok
test incomplete_version ... ok
test blackbox_backup ... ok
```
From https://ci.appveyor.com/project/sourcefrog/conserve/builds/20036005

## Cause
- If a bare `cargo test` is run, `cargo` uses `target/debug` for the `OUT_DIR`
- `main_binary` passes `--target <TRIPLET>` to `cargo` which changes the `OUT_DIR` to `target/<TRIPLE>/debug`
  - This means `cargo test`s build artifacts cannot be reused and those parts need to be rebuilt

## Background:

In Issue #4 we had to choose between performance and correctness (if you do specify `--target`, `main_binary` should run that target's `bin`) and sided on correctness

## Workarounds

1. Call into [escargot](https://docs.rs/assert_cmd/0.10.1/assert_cmd/cargo/index.html) without passing in `current_target`

2. Call `cargo test --target <TRIPLE>`



Discussion:
From killercup: cargo issue: https://github.com/rust-lang/cargo/issues/5315
So a user can work around this in assert_cmd by accessing `cargo_bin_path`, etc.  This causes the 300ms to only be hit once instead of on every run.
```rust
lazy_static! {
    const BIN_UNDER_TEST = assert-cmd::cargo_bin_path("bin_fixture");
}

#[test]
fn test_bin() {
    Command::new(BIN_UNDER_TEST).assert().success();
}
```

Should we provide an API on to simplify this?

Example
```rust
lazy_static!{
   const BIN_FIXTURE = assert_cmd::cargo_bin("bin_fixture");
}
...
println!("{:?}", BIN_FIXTURE.path());
BIN_FIXTURE.cmd().arg("stdout", 42).unwrap();
```
Additions to API aren't needed, but docs that would point out this functionality would be really helpful. Since right now one needs to go to the issue tracker of the crate to find the solution, which is suboptimal.

As for why an additional API is not needed – e.g. `regex` crate doesn't have an additional API for creating a static regexp, but it has a great documentation where it shows on the "main" page of its docs an example on how to use `lazy_static` to reduce unnecessary computations. [It's nice.](https://docs.rs/regex/1.0.2/regex/#example-avoid-compiling-the-same-regex-in-a-loop)


Also, a working example, freshly ~copypasted from a test for anyone else who's interested:

```rust
use std::path::PathBuf;

lazy_static! {
    static ref BIN_PATH: PathBuf = assert_cmd::cargo::main_binary_path().unwrap();
}

#[test]
fn test_bin() {
    Command::new(&*BIN_PATH).assert().success();
}
```
> As for why an additional API is not needed – e.g. regex crate doesn't have an additional API for creating a static regexp

The difference is the main purpose of this crate is running programs.  Its a bit of a wart in the API to have functions that provide paths to programs you could run.  It'd be nice if `Command` supported `clone`.
I think at this point this is mostly kept open to track and be aware of the slow down.  We have workarounds but thats all they are.
Have we ever measured this again with newer cargo versions? I recall them
adding some caching for this, too.

On Sun, 7 Oct 2018, 00:56 Ed Page, <notifications@github.com> wrote:

> I think at this point this is mostly kept open to track and be aware of
> the slow down. We have workarounds but thats all they are.
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/assert-rs/assert_cmd/issues/6#issuecomment-427612050>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AABOX9lsA8jsePQJ2AkErzGU37rEu00Xks5uiTUogaJpZM4UQkyB>
> .
>

@killercup , I just reran the original example that started everything. On the same laptop with nightly, it takes 125ms, so twice as fast as before. A big enough change that it actually feels way faster :+1: 
```
cargo 1.31.0-nightly (ad6e5c003 2018-09-28)
```

Good idea.  I should add a benchmark that compares running the underlying program against running it via cargo.
More data
- My computer
- `cargo 1.31.0-nightly (de314a8b2 2018-09-21)`
- Running #56 

`test escargot_rebuild ... bench: 368,686,700 ns/iter (+/- 64,925,719)`
I think we might have run into a related issue [in `bat`](https://github.com/sharkdp/bat/issues/394). I tried to implement the workaround but couldn't quite figure it out as some of the referenced functions are not available anymore(?). Is there still a workaround for this?
@sharkdp sorry, I have been maintaining [the work around in the docs](https://docs.rs/assert_cmd/0.10.1/assert_cmd/cargo/index.html) but forgot to update this issue.  I've done so now.
@epage Thank you very much! Sorry for not checking the documentation.

If I understand this correctly, this will still potentially compile (part of) the project in the first unit test, right?

(link to my current implementation: https://github.com/sharkdp/bat/pull/398)
> If I understand this correctly, this will still potentially compile (part of) the project in the first unit test, right?

To get the path, it will still need to call into `cargo`.  Whether anything builds is a different question.  If you match target/release with what you are building, then it should reuse what was built before.

I recommend passing in `current_release`.  An unfortunate thing is if you pass in `current_target` and you did not pass `--target` into `cargo test`, then it will rebuild everything on first test.  There was no good answer between correctness and performance, so the default for `Command::main_bin` and the workaround in the docs is `current_target`
@epage Thank you very much for the explanation.
On Windows, for my program `conserve`, switching to assert_cmd from my own hacky code seems to have increased the overall test time from ~3:00 to ~4:00.

https://ci.appveyor.com/project/sourcefrog/conserve/builds/20036005

I think it'd at least be nice if this was more prominent in the docs top-level page, where there are examples of using the crate.
@sourcefrog looking at your log, it looks like you are more so having problems with #57 (realized I should create a dedicated issue rather than pointing people to #4).

#57 does need to be mentioned in the docs but I'm unsure if the top-level docs are appropriate for each workaround or special case.  I also noticed that this issue's workaround is only mentioned in the `cargo` module and not on the trait or functions.  If someone jumps straight to one of those, they need to be aware of these workarounds.

I've created https://github.com/assert-rs/assert_cmd/issues/58 to document the workaround and try to find a way to improve visibility.

I noticed that, too.

Workaround 3: Only pass `--target` when it's different from the target used to compile the test.
> Workaround 3: Only pass --target when it's different from the target used to compile the test.

Could you clarify this?

The trade off is between when a user explicitly lists a `--target` and how `cargo` handles the default target.

As a possible hack in assert_cmd, even if I had a lookup table of default targets (keyed off of the current OS), there is still a trade off.  Some people in their CI always pass `--target` (if doing it for one build target, its easier to do it for all), we'd have to choose between hurting that person's performance and hurting the performance of the person running a bare `cargo test`.

I guess another hack is to have a build script that looks up `TARGET_DIR` and if it matches a certain pattern, pass `current_target` or not.
My idea was to try and detect if cargo was run with a `--target` arg -- if so, pass that ourselves, otherwise omit it. My hope is that leads to cargo using the same target directory that was used when building the tests itself.

(Cargo also sets a TARGET env var, but only for build scripts it seems.)
As far as I know, there is no direct way to detect `--target`.  `TARGET` is unconditionally set.  So, as mentioned earlier, the best I can figure is inferring whether `--target` was passed in based on other information.
Ah I see, sorry for the confusion. This a kinda unfortunate situation.

I wonder how common the explicit `--target` is, and if the cargo team might be able to help us?
> I wonder how common the explicit --target is, and if the cargo team might be able to help us?

If someone is creating a CLI using trust, its exclusively used in the CI while (I'm assuming) rarely used by the developer.

Once I have time to get back to `cargo-tarball`, we'll be able to lower our reliance on `--target` in a CI for simple cases but it will still be important for someone who wants to offer a lot of choice to their end users.
@matklad says the following should be guaranteed to work for bin targets
```rust
// Adapted from
// https://github.com/rust-lang/cargo/blob/485670b3983b52289a2f353d589c57fae2f60f82/tests/testsuite/support/mod.rs#L507
fn target_dir() -> PathBuf {
    env::current_exe().ok().map(|mut path| {
        path.pop();
        if path.ends_with("deps") {
            path.pop();
        }
        path
    }).unwrap()
}

fn cargo_review_deps_exe() -> PathBuf {
    target_dir().join(format!("cargo-review-deps{}", env::consts::EXE_SUFFIX))
}

fn base_cmd() -> Assert {
    Assert::command(&[&cargo_review_deps_exe()])
        .with_args(&["review-deps"])
}
```
Source: https://github.com/assert-rs/assert_cli/issues/118#issuecomment-444081696

This would effectively give us auto-detection of `--target` which would fix the problems we are having.  

Downsides
- No support for `main_binary`.  We'd always have to take in a path
  - We could use `env!("CARGO_PKG_NAME")` but that is not guaranteed to work.  Is it good enough?  Maybe a name change to `pkg_binary`?  This has the bonus of working regardless of the number of `bin`s in the crate.
- Doesn't help us with examples.
  - I'm concerned about having split-behavior API; two seemingly similar functions acting in very different ways.  We would have to expose bin targets one way and non-bin another way. 
  - One workaround is to tell people to directly use `escargot` for examples, bypassing us having to choose the policy of `current_target` or not
- Sounds like it won't work during integration tests
  - Probably a good thing for people to only be running these during integration tests anyways
  - But we can't enforce that as far as I'm aware
  - My biggest concern is this "happening to work" for someone but they might sometimes be running stale binaries.
#67 is forcing me in making a decision I've been procastinating.

Right now, we have a tension between #4 and #57. matklad's proposal resolves that tension but it would make cargo_bin function differently than main_binary and cargo_example such that I'm considering removing them both and telling people to use escargot if the want something besides cargo_bin.

In summary, matklad's approach would
- auto deduce `--target` and `--features` (#4)
- no performance penalty from target or features being out of sync (#57)
- no cargo overhead (#6) to be worked around with caching
- would require dropping `main_binary` and `cargo_example` but we'd point people to escargot.

Any thoughts?



Patch:
diff --git a/.travis.yml b/.travis.yml
index f731705..a0fdf37 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,22 +11,22 @@ os:
 matrix:
   include:
   - env: RUSTFMT
-    rust: 1.29.1  # `stable`: Locking down for consistent behavior
+    rust: 1.31.0  # `stable`: Locking down for consistent behavior
     install:
-      - rustup component add rustfmt-preview
+      - rustup component add rustfmt
     script:
       - cargo fmt -- --check
   - env: RUSTFLAGS="-D warnings"
-    rust: 1.29.1  # `stable`: Locking down for consistent behavior
+    rust: 1.31.0  # `stable`: Locking down for consistent behavior
     install:
     script:
     - cargo check --tests --all-features
   - env: CLIPPY
-    rust: nightly-2018-07-17
+    rust: 1.31.0  # `stable`: Locking down for consistent behavior
     install:
-      - rustup component add clippy-preview
+      - rustup component add clippy
     script:
-      - cargo clippy --all-features -- -D clippy
+      - cargo clippy --all-features
 
 install:
 - rustc -Vv
diff --git a/README.md b/README.md
index 6a46e02..9487dff 100644
--- a/README.md
+++ b/README.md
@@ -21,13 +21,13 @@ assert_cmd = "0.10"
 
 Here's a trivial example:
 
-```rust
+```rust,no_run
 extern crate assert_cmd;
 
 use std::process::Command;
 use assert_cmd::prelude::*;
 
-Command::main_binary()
+Command::cargo_bin("bin_fixture")
     .unwrap()
     .assert()
     .success();
diff --git a/src/assert.rs b/src/assert.rs
index 9203e3c..4884db8 100644
--- a/src/assert.rs
+++ b/src/assert.rs
@@ -18,15 +18,15 @@ use cmd::output_fmt;
 ///
 /// # Examples
 ///
-/// ```rust
+/// ```rust,no_run
 /// use assert_cmd::prelude::*;
 ///
 /// use std::process::Command;
 ///
-/// let mut cmd = Command::main_binary()
+/// let mut cmd = Command::cargo_bin("bin_fixture")
 ///     .unwrap();
 /// cmd.assert()
-///    .success();
+///     .success();
 /// ```
 ///
 /// [`Output`]: https://doc.rust-lang.org/std/process/struct.Output.html
@@ -35,15 +35,15 @@ pub trait OutputAssertExt {
     ///
     /// # Examples
     ///
-    /// ```rust
+    /// ```rust,no_run
     /// use assert_cmd::prelude::*;
     ///
     /// use std::process::Command;
     ///
-    /// let mut cmd = Command::main_binary()
+    /// let mut cmd = Command::cargo_bin("bin_fixture")
     ///     .unwrap();
     /// cmd.assert()
-    ///    .success();
+    ///     .success();
     /// ```
     ///
     /// [`Output`]: https://doc.rust-lang.org/std/process/struct.Output.html
@@ -69,15 +69,15 @@ impl<'c> OutputAssertExt for &'c mut process::Command {
 ///
 /// # Examples
 ///
-/// ```rust
+/// ```rust,no_run
 /// use assert_cmd::prelude::*;
 ///
 /// use std::process::Command;
 ///
-/// let mut cmd = Command::main_binary()
+/// let mut cmd = Command::cargo_bin("bin_fixture")
 ///     .unwrap();
 /// cmd.assert()
-///    .success();
+///     .success();
 /// ```
 ///
 /// [`Output`]: https://doc.rust-lang.org/std/process/struct.Output.html
@@ -102,12 +102,12 @@ impl Assert {
     ///
     /// # Examples
     ///
-    /// ```rust
+    /// ```rust,no_run
     /// use assert_cmd::prelude::*;
     ///
     /// use std::process::Command;
     ///
-    /// Command::main_binary()
+    /// Command::cargo_bin("bin_fixture")
     ///     .unwrap()
     ///     .assert()
     ///     .append_context("main", "no args")
@@ -132,12 +132,12 @@ impl Assert {
     ///
     /// # Examples
     ///
-    /// ```rust
+    /// ```rust,no_run
     /// use assert_cmd::prelude::*;
     ///
     /// use std::process::Command;
     ///
-    /// Command::main_binary()
+    /// Command::cargo_bin("bin_fixture")
     ///     .unwrap()
     ///     .assert()
     ///     .success();
@@ -165,12 +165,12 @@ impl Assert {
     ///
     /// # Examples
     ///
-    /// ```rust
+    /// ```rust,no_run
     /// use assert_cmd::prelude::*;
     ///
     /// use std::process::Command;
     ///
-    /// Command::main_binary()
+    /// Command::cargo_bin("bin_fixture")
     ///     .unwrap()
     ///     .env("exit", "1")
     ///     .assert()
@@ -200,7 +200,7 @@ impl Assert {
     /// # Examples
     ///
     /// Accepting a predicate:
-    /// ```rust
+    /// ```rust,no_run
     /// extern crate assert_cmd;
     /// extern crate predicates;
     ///
@@ -209,7 +209,7 @@ impl Assert {
     /// use std::process::Command;
     /// use predicates::prelude::*;
     ///
-    /// Command::main_binary()
+    /// Command::cargo_bin("bin_fixture")
     ///     .unwrap()
     ///     .env("exit", "42")
     ///     .assert()
@@ -217,12 +217,12 @@ impl Assert {
     /// ```
     ///
     /// Accepting an exit code:
-    /// ```rust
+    /// ```rust,no_run
     /// use assert_cmd::prelude::*;
     ///
     /// use std::process::Command;
     ///
-    /// Command::main_binary()
+    /// Command::cargo_bin("bin_fixture")
     ///     .unwrap()
     ///     .env("exit", "42")
     ///     .assert()
@@ -230,12 +230,12 @@ impl Assert {
     /// ```
     ///
     /// Accepting multiple exit codes:
-    /// ```rust
+    /// ```rust,no_run
     /// use assert_cmd::prelude::*;
     ///
     /// use std::process::Command;
     ///
-    /// Command::main_binary()
+    /// Command::cargo_bin("bin_fixture")
     ///     .unwrap()
     ///     .env("exit", "42")
     ///     .assert()
@@ -273,7 +273,7 @@ impl Assert {
     /// # Examples
     ///
     /// Accepting a bytes predicate:
-    /// ```rust
+    /// ```rust,no_run
     /// extern crate assert_cmd;
     /// extern crate predicates;
     ///
@@ -282,7 +282,7 @@ impl Assert {
     /// use std::process::Command;
     /// use predicates::prelude::*;
     ///
-    /// Command::main_binary()
+    /// Command::cargo_bin("bin_fixture")
     ///     .unwrap()
     ///     .env("stdout", "hello")
     ///     .env("stderr", "world")
@@ -291,7 +291,7 @@ impl Assert {
     /// ```
     ///
     /// Accepting a `str` predicate:
-    /// ```rust
+    /// ```rust,no_run
     /// extern crate assert_cmd;
     /// extern crate predicates;
     ///
@@ -300,7 +300,7 @@ impl Assert {
     /// use std::process::Command;
     /// use predicates::prelude::*;
     ///
-    /// Command::main_binary()
+    /// Command::cargo_bin("bin_fixture")
     ///     .unwrap()
     ///     .env("stdout", "hello")
     ///     .env("stderr", "world")
@@ -309,12 +309,12 @@ impl Assert {
     /// ```
     ///
     /// Accepting bytes:
-    /// ```rust
+    /// ```rust,no_run
     /// use assert_cmd::prelude::*;
     ///
     /// use std::process::Command;
     ///
-    /// Command::main_binary()
+    /// Command::cargo_bin("bin_fixture")
     ///     .unwrap()
     ///     .env("stdout", "hello")
     ///     .env("stderr", "world")
@@ -323,12 +323,12 @@ impl Assert {
     /// ```
     ///
     /// Accepting a `str`:
-    /// ```rust
+    /// ```rust,no_run
     /// use assert_cmd::prelude::*;
     ///
     /// use std::process::Command;
     ///
-    /// Command::main_binary()
+    /// Command::cargo_bin("bin_fixture")
     ///     .unwrap()
     ///     .env("stdout", "hello")
     ///     .env("stderr", "world")
@@ -365,7 +365,7 @@ impl Assert {
     /// # Examples
     ///
     /// Accepting a bytes predicate:
-    /// ```rust
+    /// ```rust,no_run
     /// extern crate assert_cmd;
     /// extern crate predicates;
     ///
@@ -374,7 +374,7 @@ impl Assert {
     /// use std::process::Command;
     /// use predicates::prelude::*;
     ///
-    /// Command::main_binary()
+    /// Command::cargo_bin("bin_fixture")
     ///     .unwrap()
     ///     .env("stdout", "hello")
     ///     .env("stderr", "world")
@@ -383,7 +383,7 @@ impl Assert {
     /// ```
     ///
     /// Accepting a `str` predicate:
-    /// ```rust
+    /// ```rust,no_run
     /// extern crate assert_cmd;
     /// extern crate predicates;
     ///
@@ -392,7 +392,7 @@ impl Assert {
     /// use std::process::Command;
     /// use predicates::prelude::*;
     ///
-    /// Command::main_binary()
+    /// Command::cargo_bin("bin_fixture")
     ///     .unwrap()
     ///     .env("stdout", "hello")
     ///     .env("stderr", "world")
@@ -401,12 +401,12 @@ impl Assert {
     /// ```
     ///
     /// Accepting bytes:
-    /// ```rust
+    /// ```rust,no_run
     /// use assert_cmd::prelude::*;
     ///
     /// use std::process::Command;
     ///
-    /// Command::main_binary()
+    /// Command::cargo_bin("bin_fixture")
     ///     .unwrap()
     ///     .env("stdout", "hello")
     ///     .env("stderr", "world")
@@ -415,12 +415,12 @@ impl Assert {
     /// ```
     ///
     /// Accepting a `str`:
-    /// ```rust
+    /// ```rust,no_run
     /// use assert_cmd::prelude::*;
     ///
     /// use std::process::Command;
     ///
-    /// Command::main_binary()
+    /// Command::cargo_bin("bin_fixture")
     ///     .unwrap()
     ///     .env("stdout", "hello")
     ///     .env("stderr", "world")
@@ -471,7 +471,7 @@ impl fmt::Debug for Assert {
 ///
 /// # Examples
 ///
-/// ```rust
+/// ```rust,no_run
 /// extern crate assert_cmd;
 /// extern crate predicates;
 ///
@@ -480,14 +480,14 @@ impl fmt::Debug for Assert {
 /// use std::process::Command;
 /// use predicates::prelude::*;
 ///
-/// Command::main_binary()
+/// Command::cargo_bin("bin_fixture")
 ///     .unwrap()
 ///     .env("exit", "42")
 ///     .assert()
 ///     .code(predicate::eq(42));
 ///
 /// // which can be shortened to:
-/// Command::main_binary()
+/// Command::cargo_bin("bin_fixture")
 ///     .unwrap()
 ///     .env("exit", "42")
 ///     .assert()
@@ -523,12 +523,12 @@ where
 ///
 /// # Example
 ///
-/// ```rust
+/// ```rust,no_run
 /// use assert_cmd::prelude::*;
 ///
 /// use std::process::Command;
 ///
-/// Command::main_binary()
+/// Command::cargo_bin("bin_fixture")
 ///     .unwrap()
 ///     .env("exit", "42")
 ///     .assert()
@@ -593,12 +593,12 @@ impl IntoCodePredicate<EqCodePredicate> for i32 {
 ///
 /// # Example
 ///
-/// ```rust
+/// ```rust,no_run
 /// use assert_cmd::prelude::*;
 ///
 /// use std::process::Command;
 ///
-/// Command::main_binary()
+/// Command::cargo_bin("bin_fixture")
 ///     .unwrap()
 ///     .env("exit", "42")
 ///     .assert()
@@ -671,7 +671,7 @@ impl IntoCodePredicate<InCodePredicate> for &'static [i32] {
 ///
 /// # Examples
 ///
-/// ```rust
+/// ```rust,no_run
 /// extern crate assert_cmd;
 /// extern crate predicates;
 ///
@@ -680,7 +680,7 @@ impl IntoCodePredicate<InCodePredicate> for &'static [i32] {
 /// use std::process::Command;
 /// use predicates::prelude::*;
 ///
-/// Command::main_binary()
+/// Command::cargo_bin("bin_fixture")
 ///     .unwrap()
 ///     .env("stdout", "hello")
 ///     .env("stderr", "world")
@@ -688,7 +688,7 @@ impl IntoCodePredicate<InCodePredicate> for &'static [i32] {
 ///     .stdout(predicate::str::similar("hello\n").from_utf8());
 ///
 /// // which can be shortened to:
-/// Command::main_binary()
+/// Command::cargo_bin("bin_fixture")
 ///     .unwrap()
 ///     .env("stdout", "hello")
 ///     .env("stderr", "world")
@@ -726,12 +726,12 @@ where
 ///
 /// # Example
 ///
-/// ```rust
+/// ```rust,no_run
 /// use assert_cmd::prelude::*;
 ///
 /// use std::process::Command;
 ///
-/// Command::main_binary()
+/// Command::cargo_bin("bin_fixture")
 ///     .unwrap()
 ///     .env("stdout", "hello")
 ///     .env("stderr", "world")
@@ -797,12 +797,12 @@ impl IntoOutputPredicate<BytesContentOutputPredicate> for &'static [u8] {
 ///
 /// # Example
 ///
-/// ```rust
+/// ```rust,no_run
 /// use assert_cmd::prelude::*;
 ///
 /// use std::process::Command;
 ///
-/// Command::main_binary()
+/// Command::cargo_bin("bin_fixture")
 ///     .unwrap()
 ///     .env("stdout", "hello")
 ///     .env("stderr", "world")
@@ -884,7 +884,7 @@ impl IntoOutputPredicate<StrContentOutputPredicate> for &'static str {
 ///
 /// # Example
 ///
-/// ```rust
+/// ```rust,no_run
 /// extern crate assert_cmd;
 /// extern crate predicates;
 ///
@@ -893,7 +893,7 @@ impl IntoOutputPredicate<StrContentOutputPredicate> for &'static str {
 /// use std::process::Command;
 /// use predicates::prelude::*;
 ///
-/// Command::main_binary()
+/// Command::cargo_bin("bin_fixture")
 ///     .unwrap()
 ///     .env("stdout", "hello")
 ///     .env("stderr", "world")
diff --git a/src/cargo.rs b/src/cargo.rs
index b7c5942..92b57ac 100644
--- a/src/cargo.rs
+++ b/src/cargo.rs
@@ -7,25 +7,26 @@
 //!
 //! Simple case:
 //!
-//! ```rust
+//! ```rust,no_run
 //! use assert_cmd::prelude::*;
 //!
 //! use std::process::Command;
 //!
-//! let mut cmd = Command::main_binary()
+//! let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME"))
 //!     .unwrap();
 //! let output = cmd.unwrap();
 //! ```
 //!
-//! # Further customizations
+//! # Limitations
 //!
-//! There are times when you might want to drop down to the underlying API, [`escargot`]:
-//! - Specifying feature flags
-//! - Workaround the [per-call cargo overhead][cargo-overhead] by caching the binary location with [`lazy_static`].
-//! - [If not using `--target <TRIPLET>`, bypass the first call overhead][first-call] by not
-//!   passing `current_target()` to [`escargot`].
+//! - Only works within the context of integration tests.  See [`escargot`] for a more
+//!   flexible API.
+//! - Only reuses your existing feature flags, targets, or build mode.
+//! - Only works with cargo binaries (`cargo test` ensures they are built).
 //!
-//! ```rust
+//! If you run into these limitations, we recommend trying out [`escargot`]:
+//!
+//! ```rust,no_run
 //! extern crate assert_cmd;
 //! extern crate escargot;
 //!
@@ -42,8 +43,15 @@
 //!     .unwrap();
 //! let mut cmd = bin_under_test.command();
 //! let output = cmd.unwrap();
+//! println!("{:?}", output);
 //! ```
 //!
+//! Notes:
+//! - There is a [noticeable per-call overhead](cargo-overhead) for `CargoBuild`.  We recommend
+//!   caching the binary location (`.path()` instead of `.command()`) with [`lazy_static`].
+//! - `.current_target()` improves platform coverage at the cost of [slower test runs if you don't
+//!   explicitly pass `--target <TRIPLET>` on the command line](first-call).
+//!
 //! [`lazy_static`]: https://crates.io/crates/lazy_static
 //! [`CommandCargoExt`]: trait.CommandCargoExt.html
 //! [`Command`]: https://doc.rust-lang.org/std/process/struct.Command.html
@@ -51,13 +59,12 @@
 //! [cargo-overhead]: https://github.com/assert-rs/assert_cmd/issues/6
 //! [first-call]: https://github.com/assert-rs/assert_cmd/issues/57
 
+use std::env;
 use std::error::Error;
-use std::ffi;
 use std::fmt;
+use std::path;
 use std::process;
 
-use escargot;
-
 /// Create a [`Command`] for a `bin` in the Cargo project.
 ///
 /// `CommandCargoExt` is an extension trait for [`Command`][Command] to easily launch a crate's
@@ -67,14 +74,15 @@ use escargot;
 ///
 /// # Examples
 ///
-/// ```rust
+/// ```rust,no_run
 /// use assert_cmd::prelude::*;
 ///
 /// use std::process::Command;
 ///
-/// let mut cmd = Command::main_binary()
+/// let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME"))
 ///     .unwrap();
 /// let output = cmd.unwrap();
+/// println!("{:?}", output);
 /// ```
 ///
 /// [`Command`]: https://doc.rust-lang.org/std/process/struct.Command.html
@@ -83,97 +91,47 @@ pub trait CommandCargoExt
 where
     Self: Sized,
 {
-    /// Create a [`Command`] to run the crate's main binary.
-    ///
-    /// Note: only works if there one bin in the crate.
-    ///
-    /// See the [`cargo` module documentation][`cargo`] for caveats and workarounds.
-    ///
-    /// # Examples
-    ///
-    /// ```rust
-    /// use assert_cmd::prelude::*;
-    ///
-    /// use std::process::Command;
-    ///
-    /// let mut cmd = Command::main_binary()
-    ///     .unwrap();
-    /// let output = cmd.unwrap();
-    /// ```
-    ///
-    /// [`Command`]: https://doc.rust-lang.org/std/process/struct.Command.html
-    /// [`cargo`]: index.html
-    fn main_binary() -> Result<Self, CargoError>;
-
     /// Create a [`Command`] to run a specific binary of the current crate.
     ///
     /// See the [`cargo` module documentation][`cargo`] for caveats and workarounds.
     ///
     /// # Examples
     ///
-    /// ```rust
+    /// ```rust,no_run
     /// use assert_cmd::prelude::*;
     ///
     /// use std::process::Command;
     ///
-    /// let mut cmd = Command::cargo_bin("bin_fixture")
+    /// let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME"))
     ///     .unwrap();
     /// let output = cmd.unwrap();
+    /// println!("{:?}", output);
     /// ```
     ///
-    /// [`Command`]: https://doc.rust-lang.org/std/process/struct.Command.html
-    /// [`cargo`]: index.html
-    fn cargo_bin<S: AsRef<ffi::OsStr>>(name: S) -> Result<Self, CargoError>;
-
-    /// Create a [`Command`] to run a specific example of the current crate.
-    ///
-    /// See the [`cargo` module documentation][`cargo`] for caveats and workarounds.
-    ///
-    /// # Examples
-    ///
-    /// ```rust
+    /// ```rust,no_run
     /// use assert_cmd::prelude::*;
     ///
     /// use std::process::Command;
     ///
-    /// let mut cmd = Command::cargo_example("example_fixture")
+    /// let mut cmd = Command::cargo_bin("bin_fixture")
     ///     .unwrap();
     /// let output = cmd.unwrap();
+    /// println!("{:?}", output);
     /// ```
     ///
     /// [`Command`]: https://doc.rust-lang.org/std/process/struct.Command.html
     /// [`cargo`]: index.html
-    fn cargo_example<S: AsRef<ffi::OsStr>>(name: S) -> Result<Self, CargoError>;
+    fn cargo_bin<S: AsRef<str>>(name: S) -> Result<Self, CargoError>;
 }
 
 impl CommandCargoExt for process::Command {
-    fn main_binary() -> Result<Self, CargoError> {
-        let runner = escargot::CargoBuild::new()
-            .current_release()
-            .current_target()
-            .run()
-            .map_err(CargoError::with_cause)?;
-        Ok(runner.command())
-    }
-
-    fn cargo_bin<S: AsRef<ffi::OsStr>>(name: S) -> Result<Self, CargoError> {
-        let runner = escargot::CargoBuild::new()
-            .bin(name)
-            .current_release()
-            .current_target()
-            .run()
-            .map_err(CargoError::with_cause)?;
-        Ok(runner.command())
-    }
-
-    fn cargo_example<S: AsRef<ffi::OsStr>>(name: S) -> Result<Self, CargoError> {
-        let runner = escargot::CargoBuild::new()
-            .example(name)
-            .current_release()
-            .current_target()
-            .run()
-            .map_err(CargoError::with_cause)?;
-        Ok(runner.command())
+    fn cargo_bin<S: AsRef<str>>(name: S) -> Result<Self, CargoError> {
+        let path = cargo_bin(name);
+        if path.is_file() {
+            Ok(process::Command::new(path))
+        } else {
+            Err(CargoError::with_cause(NotFoundError { path }))
+        }
     }
 }
 
@@ -184,7 +142,8 @@ pub struct CargoError {
 }
 
 impl CargoError {
-    fn with_cause<E>(cause: E) -> Self
+    /// Wrap the underlying error for passing up.
+    pub fn with_cause<E>(cause: E) -> Self
     where
         E: Error + Send + Sync + 'static,
     {
@@ -214,3 +173,49 @@ impl fmt::Display for CargoError {
         Ok(())
     }
 }
+
+/// Error when finding crate binary.
+#[derive(Debug)]
+struct NotFoundError {
+    path: path::PathBuf,
+}
+
+impl Error for NotFoundError {
+    fn description(&self) -> &str {
+        "Cargo command not found."
+    }
+
+    fn cause(&self) -> Option<&Error> {
+        None
+    }
+}
+
+impl fmt::Display for NotFoundError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(f, "Cargo command not found: {}", self.path.display())
+    }
+}
+
+// Adapted from
+// https://github.com/rust-lang/cargo/blob/485670b3983b52289a2f353d589c57fae2f60f82/tests/testsuite/support/mod.rs#L507
+fn target_dir() -> path::PathBuf {
+    env::current_exe()
+        .ok()
+        .map(|mut path| {
+            path.pop();
+            if path.ends_with("deps") {
+                path.pop();
+            }
+            path
+        })
+        .unwrap()
+}
+
+/// Look up the path to a cargo-built binary within an integration test.
+pub fn cargo_bin<S: AsRef<str>>(name: S) -> path::PathBuf {
+    cargo_bin_str(name.as_ref())
+}
+
+fn cargo_bin_str(name: &str) -> path::PathBuf {
+    target_dir().join(format!("{}{}", name, env::consts::EXE_SUFFIX))
+}
diff --git a/src/cmd.rs b/src/cmd.rs
index c19acb2..cd6431f 100644
--- a/src/cmd.rs
+++ b/src/cmd.rs
@@ -73,14 +73,13 @@ where
     ///
     /// # Examples
     ///
-    /// ```rust
+    /// ```rust,no_run
     /// use assert_cmd::prelude::*;
     ///
     /// use std::process::Command;
     ///
-    /// let err = Command::main_binary()
-    ///     .unwrap()
-    ///     .env("exit", "42")
+    /// let err = Command::new("a-command")
+    ///     .args(&["--will-fail"])
     ///     .unwrap_err();
     /// ```
     ///
@@ -158,14 +157,13 @@ pub type OutputResult = Result<process::Output, OutputError>;
 ///
 /// # Examples
 ///
-/// ```rust
+/// ```rust,no_run
 /// use assert_cmd::prelude::*;
 ///
 /// use std::process::Command;
 ///
-/// let err = Command::main_binary()
-///     .unwrap()
-///     .env("exit", "42")
+/// let err = Command::new("a-command")
+///     .args(&["--will-fail"])
 ///     .unwrap_err();
 /// ```
 ///
@@ -221,14 +219,13 @@ impl OutputError {
     ///
     /// # Examples
     ///
-    /// ```rust
+    /// ```rust,no_run
     /// use assert_cmd::prelude::*;
     ///
     /// use std::process::Command;
     ///
-    /// let err = Command::main_binary()
-    ///     .unwrap()
-    ///     .env("exit", "42")
+    /// let err = Command::new("a-command")
+    ///     .args(&["--will-fail"])
     ///     .unwrap_err();
     /// let output = err
     ///     .as_output()
diff --git a/src/lib.rs b/src/lib.rs
index 96071d9..b024b23 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -13,9 +13,7 @@
 //!
 //! Create a [`Command`]:
 //! - `Command::new(path)`, see [`Command`]
-//! - `Command::main_binary()`, see [`CommandCargoExt`]
 //! - `Command::cargo_bin(name)`, see [`CommandCargoExt`]
-//! - `Command::cargo_example(name)`, see [`CommandCargoExt`]
 //!
 //! Configure a [`Command`]:
 //! - `arg` / `args`, see [`Command`]
@@ -36,27 +34,27 @@
 //! ## Examples
 //!
 //! Here's a trivial example:
-//! ```rust
+//! ```rust,no_run
 //! extern crate assert_cmd;
 //!
 //! use std::process::Command;
 //! use assert_cmd::prelude::*;
 //!
 //! fn main() {
-//!     let mut cmd = Command::main_binary().unwrap();
+//!     let mut cmd = Command::cargo_bin("bin_fixture").unwrap();
 //!     cmd.assert().success();
 //! }
 //! ```
 //!
 //! And a little of everything:
-//! ```rust
+//! ```rust,no_run
 //! extern crate assert_cmd;
 //!
 //! use std::process::Command;
 //! use assert_cmd::prelude::*;
 //!
 //! fn main() {
-//!     let mut cmd = Command::main_binary().unwrap();
+//!     let mut cmd = Command::cargo_bin("bin_fixture").unwrap();
 //!     cmd
 //!         .arg("-A")
 //!         .env("stdout", "hello")
@@ -114,6 +112,40 @@ extern crate predicates;
 extern crate predicates_core;
 extern crate predicates_tree;
 
+/// Allows you to pull the name from your Cargo.toml at compile time.
+///
+/// # Examples
+///
+/// ```should_panic
+/// #[macro_use]
+/// extern crate assert_cmd;
+///
+/// use std::process::Command;
+/// use assert_cmd::prelude::*;
+///
+/// fn main() {
+///     let mut cmd = Command::cargo_bin(crate_name!()).unwrap();
+///     cmd
+///         .arg("-A")
+///         .env("stdout", "hello")
+///         .env("exit", "42")
+///         .with_stdin()
+///         .buffer("42");
+///     let assert = cmd.assert();
+///     assert
+///         .failure()
+///         .code(42)
+///         .stdout("hello\n");
+/// }
+/// ```
+#[cfg(not(feature = "no_cargo"))]
+#[macro_export]
+macro_rules! crate_name {
+    () => {
+        env!("CARGO_PKG_NAME")
+    };
+}
+
 pub mod assert;
 pub mod cargo;
 pub mod cmd;


Test patch:
diff --git a/tests/assert.rs b/tests/assert.rs
index fac4124..24b579f 100644
--- a/tests/assert.rs
+++ b/tests/assert.rs
@@ -6,33 +6,106 @@ use std::process::Command;
 use assert_cmd::prelude::*;
 use predicates::prelude::*;
 
+#[test]
+fn stdout_string() {
+    let expected = "hello\n".to_owned();
+    Command::cargo_bin("bin_fixture")
+        .unwrap()
+        .env("stdout", "hello")
+        .env("stderr", "world")
+        .assert()
+        .stdout(expected);
+}
+
+#[test]
+fn trait_example() {
+    let mut cmd = Command::cargo_bin("bin_fixture").unwrap();
+    cmd.assert().success();
+}
+
+#[test]
+fn trait_assert_example() {
+    let mut cmd = Command::cargo_bin("bin_fixture").unwrap();
+    cmd.assert().success();
+}
+
+#[test]
+fn struct_example() {
+    let mut cmd = Command::cargo_bin("bin_fixture").unwrap();
+    cmd.assert().success();
+}
+
+#[test]
+fn append_context_example() {
+    Command::cargo_bin("bin_fixture")
+        .unwrap()
+        .assert()
+        .append_context("main", "no args")
+        .success();
+}
+
+#[test]
+fn success_example() {
+    Command::cargo_bin("bin_fixture")
+        .unwrap()
+        .assert()
+        .success();
+}
+
+#[test]
+fn failure_example() {
+    Command::cargo_bin("bin_fixture")
+        .unwrap()
+        .env("exit", "1")
+        .assert()
+        .failure();
+}
+
 #[test]
 fn code_example() {
-    Command::main_binary()
+    Command::cargo_bin("bin_fixture")
         .unwrap()
         .env("exit", "42")
         .assert()
         .code(predicate::eq(42));
 
-    // which can be shortened to:
-    Command::main_binary()
+    Command::cargo_bin("bin_fixture")
         .unwrap()
         .env("exit", "42")
         .assert()
         .code(42);
+
+    Command::cargo_bin("bin_fixture")
+        .unwrap()
+        .env("exit", "42")
+        .assert()
+        .code(&[2, 42] as &[i32]);
 }
 
 #[test]
 fn stdout_example() {
-    Command::main_binary()
+    Command::cargo_bin("bin_fixture")
+        .unwrap()
+        .env("stdout", "hello")
+        .env("stderr", "world")
+        .assert()
+        .stdout(predicate::eq(b"hello\n" as &[u8]));
+
+    Command::cargo_bin("bin_fixture")
+        .unwrap()
+        .env("stdout", "hello")
+        .env("stderr", "world")
+        .assert()
+        .stdout(predicate::str::similar("hello\n"));
+
+    Command::cargo_bin("bin_fixture")
         .unwrap()
         .env("stdout", "hello")
         .env("stderr", "world")
         .assert()
-        .stdout(predicate::str::similar("hello\n").from_utf8());
+        .stdout(b"hello\n" as &[u8]);
 
-    // which can be shortened to:
-    Command::main_binary()
+    Command::cargo_bin("bin_fixture")
         .unwrap()
         .env("stdout", "hello")
         .env("stderr", "world")
@@ -42,29 +115,31 @@ fn stdout_example() {
 
 #[test]
 fn stderr_example() {
-    Command::main_binary()
+    Command::cargo_bin("bin_fixture")
         .unwrap()
         .env("stdout", "hello")
         .env("stderr", "world")
         .assert()
-        .stderr(predicate::str::similar("world\n").from_utf8());
+        .stderr(predicate::eq(b"world\n" as &[u8]));
 
-    // which can be shortened to:
-    Command::main_binary()
+    Command::cargo_bin("bin_fixture")
         .unwrap()
         .env("stdout", "hello")
         .env("stderr", "world")
         .assert()
-        .stderr("world\n");
-}
+        .stderr(predicate::str::similar("world\n"));
 
-#[test]
-fn stdout_string() {
-    let expected = "hello\n".to_owned();
-    Command::main_binary()
+    Command::cargo_bin("bin_fixture")
         .unwrap()
         .env("stdout", "hello")
         .env("stderr", "world")
         .assert()
-        .stdout(expected);
+        .stderr(b"world\n" as &[u8]);
+
+    Command::cargo_bin("bin_fixture")
+        .unwrap()
+        .env("stdout", "hello")
+        .env("stderr", "world")
+        .assert()
+        .stderr("world\n");
 }
diff --git a/tests/cargo.rs b/tests/cargo.rs
index 61a435f..7d166a8 100644
--- a/tests/cargo.rs
+++ b/tests/cargo.rs
@@ -3,23 +3,10 @@ extern crate escargot;
 extern crate predicates;
 
 use std::process;
+use std::process::Command;
 
 use assert_cmd::prelude::*;
 
-#[test]
-fn main_binary() {
-    let mut cmd = process::Command::main_binary().unwrap();
-    cmd.env("stdout", "42");
-    cmd.assert().success().stdout("42\n");
-}
-
-#[test]
-fn main_binary_with_empty_env() {
-    let mut cmd = process::Command::main_binary().unwrap();
-    cmd.env_clear().env("stdout", "42");
-    cmd.assert().success().stdout("42\n");
-}
-
 #[test]
 fn cargo_binary() {
     let mut cmd = process::Command::cargo_bin("bin_fixture").unwrap();
@@ -35,26 +22,37 @@ fn cargo_binary_with_empty_env() {
 }
 
 #[test]
-fn cargo_example() {
-    let mut cmd = process::Command::cargo_example("example_fixture").unwrap();
-    cmd.env("stdout", "42");
-    cmd.assert().success().stdout("42\n");
-}
-
-#[test]
-fn cargo_example_with_empty_env() {
-    let mut cmd = process::Command::cargo_example("example_fixture").unwrap();
-    cmd.env_clear().env("stdout", "42");
-    cmd.assert().success().stdout("42\n");
-}
-
-#[test]
-fn cargo_example_cache() {
+fn mod_example() {
     let bin_under_test = escargot::CargoBuild::new()
         .bin("bin_fixture")
         .current_release()
         .current_target()
         .run()
         .unwrap();
-    bin_under_test.command().unwrap();
+    let mut cmd = bin_under_test.command();
+    let output = cmd.unwrap();
+    println!("{:?}", output);
+}
+
+#[test]
+#[should_panic] // No bin named `assert_cmd
+fn trait_example() {
+    let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
+    let output = cmd.unwrap();
+    println!("{:?}", output);
+}
+
+#[test]
+#[should_panic] // No bin named `assert_cmd
+fn cargo_bin_example_1() {
+    let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
+    let output = cmd.unwrap();
+    println!("{:?}", output);
+}
+
+#[test]
+fn cargo_bin_example_2() {
+    let mut cmd = Command::cargo_bin("bin_fixture").unwrap();
+    let output = cmd.unwrap();
+    println!("{:?}", output);
 }
diff --git a/tests/examples.rs b/tests/examples.rs
new file mode 100644
index 0000000..cfe3cf2
--- /dev/null
+++ b/tests/examples.rs
@@ -0,0 +1,22 @@
+extern crate assert_cmd;
+extern crate escargot;
+extern crate predicates;
+
+use std::process::Command;
+
+use assert_cmd::prelude::*;
+
+#[test]
+fn lib_example() {
+    let mut cmd = Command::cargo_bin("bin_fixture").unwrap();
+    cmd.assert().success();
+
+    let mut cmd = Command::cargo_bin("bin_fixture").unwrap();
+    cmd.arg("-A")
+        .env("stdout", "hello")
+        .env("exit", "42")
+        .with_stdin()
+        .buffer("42");
+    let assert = cmd.assert();
+    assert.failure().code(42).stdout("hello\n");
+}