Skip to content

Commit

Permalink
fix(cargo): Avoid cargo run
Browse files Browse the repository at this point in the history
This switches us from using `cargo run` to `cargo build`, reading where
the binary is placed, and callin that instead.

Fixes assert-rs#95 because the user changing the `CWD` doesn't impact `cargo
build`.

Fixes assert-rs#79 because there is no `cargo` output when capturing the user's
stdout/stderr.

Fixes assert-rs#51 because the user changing the environment doesn't impact
`cargo build`.

This is a step towards working around assert-rs#100 because we can allow a user
to cache the result of `cargo build`.
  • Loading branch information
epage committed May 25, 2018
1 parent 7a81c5f commit d892f96
Show file tree
Hide file tree
Showing 6 changed files with 256 additions and 104 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ colored = "1.5"
difference = "2.0"
failure = "0.1"
failure_derive = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
environment = "0.1"

Expand Down
16 changes: 16 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use std::env;
use std::fs;
use std::io::Write;
use std::path;

fn main() {
println!("cargo:rerun-if-changed=build.rs");

// env::ARCH doesn't include full triple, and AFAIK there isn't a nicer way of getting the full triple
// (see lib.rs for the rest of this hack)
let out = path::PathBuf::from(env::var_os("OUT_DIR").expect("run within cargo"))
.join("current_target.txt");
let default_target = env::var("TARGET").expect("run as cargo build script");
let mut file = fs::File::create(out).unwrap();
file.write_all(default_target.as_bytes()).unwrap();
}
187 changes: 83 additions & 104 deletions src/assert.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::default;
use std::ffi::{OsStr, OsString};
use std::io::Write;
use std::path::PathBuf;
Expand All @@ -11,6 +10,35 @@ use failure::Fail;

use errors::*;
use output::{Content, Output, OutputKind, OutputPredicate};
use cargo;

#[derive(Deserialize)]
struct MessageTarget<'a> {
#[serde(borrow)]
crate_types: Vec<&'a str>,
#[serde(borrow)]
kind: Vec<&'a str>,
}

#[derive(Deserialize)]
struct MessageFilter<'a> {
#[serde(borrow)]
reason: &'a str,
target: MessageTarget<'a>,
#[serde(borrow)]
filenames: Vec<&'a str>,
}

fn filenames(msg: cargo::Message, kind: &str) -> Option<String> {
let filter: MessageFilter = msg.convert().ok()?;
if filter.reason != "compiler-artifact" || filter.target.crate_types != ["bin"]
|| filter.target.kind != [kind]
{
None
} else {
Some(filter.filenames[0].to_owned())
}
}

/// Assertions for a specific command.
#[derive(Debug)]
Expand All @@ -25,80 +53,72 @@ pub struct Assert {
stdin_contents: Option<Vec<u8>>,
}

impl default::Default for Assert {
/// Construct an assert using `cargo run --` as command.
impl Assert {
/// Run the crate's main binary.
///
/// Defaults to asserting _successful_ execution.
fn default() -> Self {
Assert {
cmd: vec![
"cargo",
"run",
#[cfg(not(debug_assertions))]
"--release",
"--quiet",
"--",
].into_iter()
.map(OsString::from)
.collect(),
pub fn main_binary() -> Result<Self, failure::Error> {
let cargo = cargo::Cargo::new().build().current_release();
let bins: Vec<_> = cargo.exec()?.filter_map(|m| filenames(m, "bin")).collect();
if bins.is_empty() {
bail!("No binaries in crate");
} else if bins.len() != 1 {
bail!("Ambiguous which binary is intended: {:?}", bins);
}
let bin = bins[0].as_str();
let cmd = Self {
cmd: vec![bin].into_iter().map(OsString::from).collect(),
env: Environment::inherit(),
current_dir: None,
expect_success: Some(true),
expect_exit_code: None,
expect_output: vec![],
stdin_contents: None,
}
}
}

impl Assert {
/// Run the crate's main binary.
///
/// Defaults to asserting _successful_ execution.
pub fn main_binary() -> Self {
Assert::default()
};
Ok(cmd)
}

/// Run a specific binary of the current crate.
///
/// Defaults to asserting _successful_ execution.
pub fn cargo_binary<S: AsRef<OsStr>>(name: S) -> Self {
Assert {
cmd: vec![
OsStr::new("cargo"),
OsStr::new("run"),
#[cfg(not(debug_assertions))]
OsStr::new("--release"),
OsStr::new("--quiet"),
OsStr::new("--bin"),
name.as_ref(),
OsStr::new("--"),
].into_iter()
.map(OsString::from)
.collect(),
..Self::default()
}
pub fn cargo_binary<S: AsRef<OsStr>>(name: S) -> Result<Self, failure::Error> {
let cargo = cargo::Cargo::new().build().bin(name).current_release();
let bins: Vec<_> = cargo.exec()?.filter_map(|m| filenames(m, "bin")).collect();
assert_eq!(bins.len(), 1);
let bin = bins[0].as_str();
let cmd = Self {
cmd: vec![bin].into_iter().map(OsString::from).collect(),
env: Environment::inherit(),
current_dir: None,
expect_success: Some(true),
expect_exit_code: None,
expect_output: vec![],
stdin_contents: None,
};
Ok(cmd)
}

/// Run a specific example of the current crate.
///
/// Defaults to asserting _successful_ execution.
pub fn example<S: AsRef<OsStr>>(name: S) -> Self {
Assert {
cmd: vec![
OsStr::new("cargo"),
OsStr::new("run"),
#[cfg(not(debug_assertions))]
OsStr::new("--release"),
OsStr::new("--quiet"),
OsStr::new("--example"),
name.as_ref(),
OsStr::new("--"),
].into_iter()
.map(OsString::from)
.collect(),
..Self::default()
}
pub fn example<S: AsRef<OsStr>>(name: S) -> Result<Self, failure::Error> {
let cargo = cargo::Cargo::new().build().example(name).current_release();
let bins: Vec<_> = cargo
.exec()?
.filter_map(|m| filenames(m, "example"))
.collect();
assert_eq!(bins.len(), 1);
let bin = bins[0].as_str();
let cmd = Self {
cmd: vec![bin].into_iter().map(OsString::from).collect(),
env: Environment::inherit(),
current_dir: None,
expect_success: Some(true),
expect_exit_code: None,
expect_output: vec![],
stdin_contents: None,
};
Ok(cmd)
}

/// Run a custom command.
Expand All @@ -116,7 +136,12 @@ impl Assert {
pub fn command<S: AsRef<OsStr>>(cmd: &[S]) -> Self {
Assert {
cmd: cmd.into_iter().map(OsString::from).collect(),
..Self::default()
env: Environment::inherit(),
current_dir: None,
expect_success: Some(true),
expect_exit_code: None,
expect_output: vec![],
stdin_contents: None,
}
}

Expand Down Expand Up @@ -559,52 +584,6 @@ mod test {
Assert::command(&["printenv"])
}

#[test]
fn main_binary_default_uses_active_profile() {
let assert = Assert::main_binary();

let expected = if cfg!(debug_assertions) {
OsString::from("cargo run --quiet -- ")
} else {
OsString::from("cargo run --release --quiet -- ")
};

assert_eq!(
expected,
assert
.cmd
.into_iter()
.fold(OsString::from(""), |mut cmd, token| {
cmd.push(token);
cmd.push(" ");
cmd
})
);
}

#[test]
fn cargo_binary_default_uses_active_profile() {
let assert = Assert::cargo_binary("hello");

let expected = if cfg!(debug_assertions) {
OsString::from("cargo run --quiet --bin hello -- ")
} else {
OsString::from("cargo run --release --quiet --bin hello -- ")
};

assert_eq!(
expected,
assert
.cmd
.into_iter()
.fold(OsString::from(""), |mut cmd, token| {
cmd.push(token);
cmd.push(" ");
cmd
})
);
}

#[test]
fn take_ownership() {
let x = Environment::inherit();
Expand Down
115 changes: 115 additions & 0 deletions src/cargo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use std::ffi;
use std::process;
use std::str;
use std::vec;

use failure;
use serde;
use serde_json;

const CURRENT_TARGET: &str = include_str!(concat!(env!("OUT_DIR"), "/current_target.txt"));

#[derive(Debug)]
pub struct Cargo {
cmd: process::Command,
}

impl Cargo {
pub fn new() -> Self {
Self {
cmd: process::Command::new("cargo"),
}
}

pub fn arg<S: AsRef<ffi::OsStr>>(mut self, arg: S) -> Self {
self.cmd.arg(arg);
self
}

pub fn build(mut self) -> CargoBuild {
self.cmd.arg("build").arg("--message-format=json");
CargoBuild { cmd: self.cmd }
}
}

pub struct CargoBuild {
cmd: process::Command,
}

impl CargoBuild {
pub fn new() -> Self {
Cargo::new().build()
}

pub fn quiet(self) -> Self {
self.arg("--quiet")
}

pub fn bin<S: AsRef<ffi::OsStr>>(self, name: S) -> Self {
self.arg("--bin").arg(name)
}

pub fn example<S: AsRef<ffi::OsStr>>(self, name: S) -> Self {
self.arg("--example").arg(name)
}

pub fn release(self) -> Self {
self.arg("--release")
}

#[cfg(debug_assertions)]
pub fn current_release(self) -> Self {
self
}

#[cfg(not(debug_assertions))]
pub fn current_release(self) -> Self {
self.release()
}

pub fn target<S: AsRef<ffi::OsStr>>(self, triplet: S) -> Self {
self.arg("--target").arg(triplet)
}

pub fn current_taget(self) -> Self {
self.target(CURRENT_TARGET)
}

pub fn arg<S: AsRef<ffi::OsStr>>(mut self, arg: S) -> Self {
self.cmd.arg(arg);
self
}

pub fn exec(mut self) -> Result<MessageItr, failure::Error> {
let output = self.cmd.output()?;
if !output.status.success() {
bail!("{}", String::from_utf8_lossy(&output.stderr));
}

let messages: Vec<Message> = str::from_utf8(&output.stdout)
.expect("json to be UTF-8")
.split('\n')
.map(|s| Message {
content: s.to_owned(),
})
.collect();

Ok(messages.into_iter())
}
}

pub type MessageItr = vec::IntoIter<Message>;

pub struct Message {
content: String,
}

impl Message {
pub fn convert<'a, T>(&'a self) -> Result<T, failure::Error>
where
T: serde::Deserialize<'a>,
{
let data = serde_json::from_str(self.content.as_str())?;
Ok(data)
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ extern crate environment;
extern crate failure;
#[macro_use]
extern crate failure_derive;
#[macro_use]
extern crate serde;
extern crate serde_json;

mod errors;
Expand All @@ -134,6 +136,7 @@ mod macros;
pub use macros::flatten_escaped_string;

mod assert;
mod cargo;
mod diff;
mod output;

Expand Down
Loading

0 comments on commit d892f96

Please sign in to comment.