Skip to content

perf(cargo): Faster bin lookup #69

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 27, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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();
106 changes: 53 additions & 53 deletions src/assert.rs

Large diffs are not rendered by default.

163 changes: 84 additions & 79 deletions src/cargo.rs
Original file line number Diff line number Diff line change
@@ -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,22 +43,28 @@
//! .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
//! [`escargot`]: https://docs.rs/escargot/
//! [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))
}
21 changes: 9 additions & 12 deletions src/cmd.rs
Original file line number Diff line number Diff line change
@@ -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()
44 changes: 38 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
111 changes: 93 additions & 18 deletions tests/assert.rs
Original file line number Diff line number Diff line change
@@ -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");
}
58 changes: 28 additions & 30 deletions tests/cargo.rs
Original file line number Diff line number Diff line change
@@ -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);
}
22 changes: 22 additions & 0 deletions tests/examples.rs
Original file line number Diff line number Diff line change
@@ -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");
}