Skip to content

Commit d892f96

Browse files
committed
fix(cargo): Avoid cargo run
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`.
1 parent 7a81c5f commit d892f96

File tree

6 files changed

+256
-104
lines changed

6 files changed

+256
-104
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ colored = "1.5"
1919
difference = "2.0"
2020
failure = "0.1"
2121
failure_derive = "0.1"
22+
serde = { version = "1.0", features = ["derive"] }
2223
serde_json = "1.0"
2324
environment = "0.1"
2425

build.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use std::env;
2+
use std::fs;
3+
use std::io::Write;
4+
use std::path;
5+
6+
fn main() {
7+
println!("cargo:rerun-if-changed=build.rs");
8+
9+
// env::ARCH doesn't include full triple, and AFAIK there isn't a nicer way of getting the full triple
10+
// (see lib.rs for the rest of this hack)
11+
let out = path::PathBuf::from(env::var_os("OUT_DIR").expect("run within cargo"))
12+
.join("current_target.txt");
13+
let default_target = env::var("TARGET").expect("run as cargo build script");
14+
let mut file = fs::File::create(out).unwrap();
15+
file.write_all(default_target.as_bytes()).unwrap();
16+
}

src/assert.rs

Lines changed: 83 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::default;
21
use std::ffi::{OsStr, OsString};
32
use std::io::Write;
43
use std::path::PathBuf;
@@ -11,6 +10,35 @@ use failure::Fail;
1110

1211
use errors::*;
1312
use output::{Content, Output, OutputKind, OutputPredicate};
13+
use cargo;
14+
15+
#[derive(Deserialize)]
16+
struct MessageTarget<'a> {
17+
#[serde(borrow)]
18+
crate_types: Vec<&'a str>,
19+
#[serde(borrow)]
20+
kind: Vec<&'a str>,
21+
}
22+
23+
#[derive(Deserialize)]
24+
struct MessageFilter<'a> {
25+
#[serde(borrow)]
26+
reason: &'a str,
27+
target: MessageTarget<'a>,
28+
#[serde(borrow)]
29+
filenames: Vec<&'a str>,
30+
}
31+
32+
fn filenames(msg: cargo::Message, kind: &str) -> Option<String> {
33+
let filter: MessageFilter = msg.convert().ok()?;
34+
if filter.reason != "compiler-artifact" || filter.target.crate_types != ["bin"]
35+
|| filter.target.kind != [kind]
36+
{
37+
None
38+
} else {
39+
Some(filter.filenames[0].to_owned())
40+
}
41+
}
1442

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

28-
impl default::Default for Assert {
29-
/// Construct an assert using `cargo run --` as command.
56+
impl Assert {
57+
/// Run the crate's main binary.
3058
///
3159
/// Defaults to asserting _successful_ execution.
32-
fn default() -> Self {
33-
Assert {
34-
cmd: vec![
35-
"cargo",
36-
"run",
37-
#[cfg(not(debug_assertions))]
38-
"--release",
39-
"--quiet",
40-
"--",
41-
].into_iter()
42-
.map(OsString::from)
43-
.collect(),
60+
pub fn main_binary() -> Result<Self, failure::Error> {
61+
let cargo = cargo::Cargo::new().build().current_release();
62+
let bins: Vec<_> = cargo.exec()?.filter_map(|m| filenames(m, "bin")).collect();
63+
if bins.is_empty() {
64+
bail!("No binaries in crate");
65+
} else if bins.len() != 1 {
66+
bail!("Ambiguous which binary is intended: {:?}", bins);
67+
}
68+
let bin = bins[0].as_str();
69+
let cmd = Self {
70+
cmd: vec![bin].into_iter().map(OsString::from).collect(),
4471
env: Environment::inherit(),
4572
current_dir: None,
4673
expect_success: Some(true),
4774
expect_exit_code: None,
4875
expect_output: vec![],
4976
stdin_contents: None,
50-
}
51-
}
52-
}
53-
54-
impl Assert {
55-
/// Run the crate's main binary.
56-
///
57-
/// Defaults to asserting _successful_ execution.
58-
pub fn main_binary() -> Self {
59-
Assert::default()
77+
};
78+
Ok(cmd)
6079
}
6180

6281
/// Run a specific binary of the current crate.
6382
///
6483
/// Defaults to asserting _successful_ execution.
65-
pub fn cargo_binary<S: AsRef<OsStr>>(name: S) -> Self {
66-
Assert {
67-
cmd: vec![
68-
OsStr::new("cargo"),
69-
OsStr::new("run"),
70-
#[cfg(not(debug_assertions))]
71-
OsStr::new("--release"),
72-
OsStr::new("--quiet"),
73-
OsStr::new("--bin"),
74-
name.as_ref(),
75-
OsStr::new("--"),
76-
].into_iter()
77-
.map(OsString::from)
78-
.collect(),
79-
..Self::default()
80-
}
84+
pub fn cargo_binary<S: AsRef<OsStr>>(name: S) -> Result<Self, failure::Error> {
85+
let cargo = cargo::Cargo::new().build().bin(name).current_release();
86+
let bins: Vec<_> = cargo.exec()?.filter_map(|m| filenames(m, "bin")).collect();
87+
assert_eq!(bins.len(), 1);
88+
let bin = bins[0].as_str();
89+
let cmd = Self {
90+
cmd: vec![bin].into_iter().map(OsString::from).collect(),
91+
env: Environment::inherit(),
92+
current_dir: None,
93+
expect_success: Some(true),
94+
expect_exit_code: None,
95+
expect_output: vec![],
96+
stdin_contents: None,
97+
};
98+
Ok(cmd)
8199
}
82100

83101
/// Run a specific example of the current crate.
84102
///
85103
/// Defaults to asserting _successful_ execution.
86-
pub fn example<S: AsRef<OsStr>>(name: S) -> Self {
87-
Assert {
88-
cmd: vec![
89-
OsStr::new("cargo"),
90-
OsStr::new("run"),
91-
#[cfg(not(debug_assertions))]
92-
OsStr::new("--release"),
93-
OsStr::new("--quiet"),
94-
OsStr::new("--example"),
95-
name.as_ref(),
96-
OsStr::new("--"),
97-
].into_iter()
98-
.map(OsString::from)
99-
.collect(),
100-
..Self::default()
101-
}
104+
pub fn example<S: AsRef<OsStr>>(name: S) -> Result<Self, failure::Error> {
105+
let cargo = cargo::Cargo::new().build().example(name).current_release();
106+
let bins: Vec<_> = cargo
107+
.exec()?
108+
.filter_map(|m| filenames(m, "example"))
109+
.collect();
110+
assert_eq!(bins.len(), 1);
111+
let bin = bins[0].as_str();
112+
let cmd = Self {
113+
cmd: vec![bin].into_iter().map(OsString::from).collect(),
114+
env: Environment::inherit(),
115+
current_dir: None,
116+
expect_success: Some(true),
117+
expect_exit_code: None,
118+
expect_output: vec![],
119+
stdin_contents: None,
120+
};
121+
Ok(cmd)
102122
}
103123

104124
/// Run a custom command.
@@ -116,7 +136,12 @@ impl Assert {
116136
pub fn command<S: AsRef<OsStr>>(cmd: &[S]) -> Self {
117137
Assert {
118138
cmd: cmd.into_iter().map(OsString::from).collect(),
119-
..Self::default()
139+
env: Environment::inherit(),
140+
current_dir: None,
141+
expect_success: Some(true),
142+
expect_exit_code: None,
143+
expect_output: vec![],
144+
stdin_contents: None,
120145
}
121146
}
122147

@@ -559,52 +584,6 @@ mod test {
559584
Assert::command(&["printenv"])
560585
}
561586

562-
#[test]
563-
fn main_binary_default_uses_active_profile() {
564-
let assert = Assert::main_binary();
565-
566-
let expected = if cfg!(debug_assertions) {
567-
OsString::from("cargo run --quiet -- ")
568-
} else {
569-
OsString::from("cargo run --release --quiet -- ")
570-
};
571-
572-
assert_eq!(
573-
expected,
574-
assert
575-
.cmd
576-
.into_iter()
577-
.fold(OsString::from(""), |mut cmd, token| {
578-
cmd.push(token);
579-
cmd.push(" ");
580-
cmd
581-
})
582-
);
583-
}
584-
585-
#[test]
586-
fn cargo_binary_default_uses_active_profile() {
587-
let assert = Assert::cargo_binary("hello");
588-
589-
let expected = if cfg!(debug_assertions) {
590-
OsString::from("cargo run --quiet --bin hello -- ")
591-
} else {
592-
OsString::from("cargo run --release --quiet --bin hello -- ")
593-
};
594-
595-
assert_eq!(
596-
expected,
597-
assert
598-
.cmd
599-
.into_iter()
600-
.fold(OsString::from(""), |mut cmd, token| {
601-
cmd.push(token);
602-
cmd.push(" ");
603-
cmd
604-
})
605-
);
606-
}
607-
608587
#[test]
609588
fn take_ownership() {
610589
let x = Environment::inherit();

src/cargo.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use std::ffi;
2+
use std::process;
3+
use std::str;
4+
use std::vec;
5+
6+
use failure;
7+
use serde;
8+
use serde_json;
9+
10+
const CURRENT_TARGET: &str = include_str!(concat!(env!("OUT_DIR"), "/current_target.txt"));
11+
12+
#[derive(Debug)]
13+
pub struct Cargo {
14+
cmd: process::Command,
15+
}
16+
17+
impl Cargo {
18+
pub fn new() -> Self {
19+
Self {
20+
cmd: process::Command::new("cargo"),
21+
}
22+
}
23+
24+
pub fn arg<S: AsRef<ffi::OsStr>>(mut self, arg: S) -> Self {
25+
self.cmd.arg(arg);
26+
self
27+
}
28+
29+
pub fn build(mut self) -> CargoBuild {
30+
self.cmd.arg("build").arg("--message-format=json");
31+
CargoBuild { cmd: self.cmd }
32+
}
33+
}
34+
35+
pub struct CargoBuild {
36+
cmd: process::Command,
37+
}
38+
39+
impl CargoBuild {
40+
pub fn new() -> Self {
41+
Cargo::new().build()
42+
}
43+
44+
pub fn quiet(self) -> Self {
45+
self.arg("--quiet")
46+
}
47+
48+
pub fn bin<S: AsRef<ffi::OsStr>>(self, name: S) -> Self {
49+
self.arg("--bin").arg(name)
50+
}
51+
52+
pub fn example<S: AsRef<ffi::OsStr>>(self, name: S) -> Self {
53+
self.arg("--example").arg(name)
54+
}
55+
56+
pub fn release(self) -> Self {
57+
self.arg("--release")
58+
}
59+
60+
#[cfg(debug_assertions)]
61+
pub fn current_release(self) -> Self {
62+
self
63+
}
64+
65+
#[cfg(not(debug_assertions))]
66+
pub fn current_release(self) -> Self {
67+
self.release()
68+
}
69+
70+
pub fn target<S: AsRef<ffi::OsStr>>(self, triplet: S) -> Self {
71+
self.arg("--target").arg(triplet)
72+
}
73+
74+
pub fn current_taget(self) -> Self {
75+
self.target(CURRENT_TARGET)
76+
}
77+
78+
pub fn arg<S: AsRef<ffi::OsStr>>(mut self, arg: S) -> Self {
79+
self.cmd.arg(arg);
80+
self
81+
}
82+
83+
pub fn exec(mut self) -> Result<MessageItr, failure::Error> {
84+
let output = self.cmd.output()?;
85+
if !output.status.success() {
86+
bail!("{}", String::from_utf8_lossy(&output.stderr));
87+
}
88+
89+
let messages: Vec<Message> = str::from_utf8(&output.stdout)
90+
.expect("json to be UTF-8")
91+
.split('\n')
92+
.map(|s| Message {
93+
content: s.to_owned(),
94+
})
95+
.collect();
96+
97+
Ok(messages.into_iter())
98+
}
99+
}
100+
101+
pub type MessageItr = vec::IntoIter<Message>;
102+
103+
pub struct Message {
104+
content: String,
105+
}
106+
107+
impl Message {
108+
pub fn convert<'a, T>(&'a self) -> Result<T, failure::Error>
109+
where
110+
T: serde::Deserialize<'a>,
111+
{
112+
let data = serde_json::from_str(self.content.as_str())?;
113+
Ok(data)
114+
}
115+
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ extern crate environment;
124124
extern crate failure;
125125
#[macro_use]
126126
extern crate failure_derive;
127+
#[macro_use]
128+
extern crate serde;
127129
extern crate serde_json;
128130

129131
mod errors;
@@ -134,6 +136,7 @@ mod macros;
134136
pub use macros::flatten_escaped_string;
135137

136138
mod assert;
139+
mod cargo;
137140
mod diff;
138141
mod output;
139142

0 commit comments

Comments
 (0)