Skip to content

Commit 02c92e1

Browse files
committed
Allow to intercept binaries invoked by Cargo
If `CARGO_WRAP` environment variable is specified, cargo will just print what it should have executed otherwise during `cargo test` / `cargo run`.
1 parent e60ae3d commit 02c92e1

File tree

7 files changed

+190
-15
lines changed

7 files changed

+190
-15
lines changed

src/cargo/ops/cargo_run.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::path::Path;
22

33
use ops::{self, CompileFilter, Packages};
44
use util::{self, human, CargoResult, ProcessError};
5+
use util::machine_message::{self, RunProfile};
56
use core::Workspace;
67

78
pub fn run(ws: &Workspace,
@@ -64,6 +65,11 @@ pub fn run(ws: &Workspace,
6465
let mut process = compile.target_process(exe, &pkg)?;
6566
process.args(args).cwd(config.cwd());
6667

67-
config.shell().status("Running", process.to_string())?;
68-
Ok(process.exec_replace().err())
68+
if config.wrap_exe() {
69+
machine_message::emit(RunProfile::new(&process));
70+
Ok(None)
71+
} else {
72+
config.shell().status("Running", process.to_string())?;
73+
Ok(process.exec_replace().err())
74+
}
6975
}

src/cargo/ops/cargo_test.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::ffi::{OsString, OsStr};
22

33
use ops::{self, Compilation};
44
use util::{self, CargoResult, CargoTestError, Test, ProcessError};
5+
use util::machine_message::{self, RunProfile};
56
use core::Workspace;
67

78
pub struct TestOptions<'a> {
@@ -95,14 +96,19 @@ fn run_unit_tests(options: &TestOptions,
9596
config.shell().concise(|shell| {
9697
shell.status("Running", to_display.display().to_string())
9798
})?;
98-
config.shell().verbose(|shell| {
99-
shell.status("Running", cmd.to_string())
100-
})?;
10199

102-
if let Err(e) = cmd.exec() {
103-
errors.push(e);
104-
if !options.no_fail_fast {
105-
return Ok((Test::UnitTest(kind.clone(), test.clone()), errors))
100+
if config.wrap_exe() {
101+
machine_message::emit(RunProfile::new(&cmd));
102+
} else {
103+
config.shell().verbose(|shell| {
104+
shell.status("Running", cmd.to_string())
105+
})?;
106+
107+
if let Err(e) = cmd.exec() {
108+
errors.push(e);
109+
if !options.no_fail_fast {
110+
return Ok((Test::UnitTest(kind.clone(), test.clone()), errors))
111+
}
106112
}
107113
}
108114
}
@@ -116,8 +122,9 @@ fn run_doc_tests(options: &TestOptions,
116122
let mut errors = Vec::new();
117123
let config = options.compile_opts.config;
118124

119-
// We don't build/rust doctests if target != host
120-
if config.rustc()?.host != compilation.target {
125+
// We don't build/rust doctests if target != host or
126+
// if we are not supposed to run binaries.
127+
if config.rustc()?.host != compilation.target || config.wrap_exe() {
121128
return Ok((Test::Doc, errors));
122129
}
123130

src/cargo/util/config.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub struct Config {
3333
extra_verbose: Cell<bool>,
3434
frozen: Cell<bool>,
3535
locked: Cell<bool>,
36+
wrap_exe: Cell<bool>,
3637
}
3738

3839
impl Config {
@@ -50,6 +51,7 @@ impl Config {
5051
extra_verbose: Cell::new(false),
5152
frozen: Cell::new(false),
5253
locked: Cell::new(false),
54+
wrap_exe: Cell::new(false),
5355
}
5456
}
5557

@@ -361,6 +363,7 @@ impl Config {
361363
self.extra_verbose.set(extra_verbose);
362364
self.frozen.set(frozen);
363365
self.locked.set(locked);
366+
self.wrap_exe.set(env::var("CARGO_WRAP").is_ok());
364367

365368
Ok(())
366369
}
@@ -377,6 +380,10 @@ impl Config {
377380
!self.frozen.get() && !self.locked.get()
378381
}
379382

383+
pub fn wrap_exe(&self) -> bool {
384+
self.wrap_exe.get()
385+
}
386+
380387
fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
381388
let mut cfg = CV::Table(HashMap::new(), PathBuf::from("."));
382389

src/cargo/util/machine_message.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
use std::path::Path;
2+
use std::collections::HashMap;
3+
use std::borrow::Cow;
4+
15
use serde::ser;
26
use serde_json::{self, Value};
37

48
use core::{PackageId, Target, Profile};
9+
use util::ProcessBuilder;
510

611
pub trait Message: ser::Serialize {
712
fn reason(&self) -> &str;
@@ -55,3 +60,30 @@ impl<'a> Message for BuildScript<'a> {
5560
"build-script-executed"
5661
}
5762
}
63+
64+
#[derive(Serialize)]
65+
pub struct RunProfile<'a> {
66+
pub program: Cow<'a, str>,
67+
pub args: Vec<Cow<'a, str>>,
68+
pub env: HashMap<&'a str, Option<Cow<'a, str>>>,
69+
pub cwd: Option<&'a Path>,
70+
}
71+
72+
impl<'a> RunProfile<'a> {
73+
pub fn new(process: &ProcessBuilder) -> RunProfile {
74+
RunProfile {
75+
program: process.get_program().to_string_lossy(),
76+
args: process.get_args().iter().map(|s| s.to_string_lossy()).collect(),
77+
env: process.get_envs().iter().map(|(k, v)| {
78+
(k.as_str(), v.as_ref().map(|s| s.to_string_lossy()))
79+
}).collect(),
80+
cwd: process.get_cwd(),
81+
}
82+
}
83+
}
84+
85+
impl<'a> Message for RunProfile<'a> {
86+
fn reason(&self) -> &str {
87+
"run-profile"
88+
}
89+
}

src/cargo/util/process_builder.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ impl ProcessBuilder {
5757
self
5858
}
5959

60+
pub fn get_program(&self) -> &OsString {
61+
&self.program
62+
}
63+
6064
pub fn get_args(&self) -> &[OsString] {
6165
&self.args
6266
}

tests/cargotest/support/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -426,13 +426,14 @@ impl Execs {
426426
}
427427

428428
if let Some(ref objects) = self.expect_json {
429-
let lines = match str::from_utf8(&actual.stdout) {
429+
let stdout = match str::from_utf8(&actual.stdout) {
430430
Err(..) => return Err("stdout was not utf8 encoded".to_owned()),
431-
Ok(stdout) => stdout.lines().collect::<Vec<_>>(),
431+
Ok(stdout) => stdout,
432432
};
433+
let lines = stdout.lines().collect::<Vec<_>>();;
433434
if lines.len() != objects.len() {
434-
return Err(format!("expected {} json lines, got {}",
435-
objects.len(), lines.len()));
435+
return Err(format!("expected {} json lines, got {}:\n {}",
436+
objects.len(), lines.len(), stdout));
436437
}
437438
for (obj, line) in objects.iter().zip(lines) {
438439
self.match_json(obj, line)?;

tests/intercept_exe.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
extern crate cargo;
2+
extern crate cargotest;
3+
extern crate hamcrest;
4+
5+
use cargotest::support::{project, execs};
6+
use hamcrest::{assert_that, existing_file};
7+
8+
#[test]
9+
fn single_bin() {
10+
let p = project("foo")
11+
.file("Cargo.toml", r#"
12+
[project]
13+
name = "foo"
14+
version = "0.0.1"
15+
authors = []
16+
"#)
17+
.file("src/main.rs", r#"
18+
fn main() { println!("hello"); }
19+
"#);
20+
21+
assert_that(p.cargo_process("run").env("CARGO_WRAP", "1"),
22+
execs().with_status(0)
23+
.with_stderr(&"\
24+
[COMPILING] foo v0.0.1 ([..])
25+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
26+
.with_json(r#"
27+
{
28+
"reason": "run-profile",
29+
"program": "target\/debug\/foo",
30+
"env": {
31+
"LD_LIBRARY_PATH": "[..]",
32+
"CARGO_PKG_VERSION_PRE": "",
33+
"CARGO_PKG_VERSION_PATCH": "1",
34+
"CARGO_PKG_VERSION_MINOR": "0",
35+
"CARGO_PKG_VERSION_MAJOR": "0",
36+
"CARGO_PKG_VERSION": "0.0.1",
37+
"CARGO_PKG_NAME": "foo",
38+
"CARGO_PKG_HOMEPAGE": "",
39+
"CARGO_PKG_DESCRIPTION": "",
40+
"CARGO_PKG_AUTHORS": "",
41+
"CARGO_MANIFEST_DIR": "[..]",
42+
"CARGO": "[..]"
43+
},
44+
"cwd": "[..]",
45+
"args": []
46+
}
47+
"#));
48+
assert_that(&p.bin("foo"), existing_file());
49+
}
50+
51+
#[test]
52+
fn several_tests() {
53+
let p = project("foo")
54+
.file("Cargo.toml", r#"
55+
[project]
56+
name = "foo"
57+
version = "0.0.1"
58+
authors = []
59+
"#)
60+
.file("src/lib.rs", r#"
61+
/// Doctest are not executed with wrap
62+
/// ```
63+
/// assert!(false);
64+
/// ```
65+
pub fn f() { }
66+
67+
#[test] fn test_in_lib() {}
68+
"#)
69+
.file("tests/bar.rs", r#"
70+
#[test] fn test_bar() {}
71+
"#)
72+
.file("tests/baz.rs", r#"
73+
#[test] fn test_baz() {}
74+
"#);
75+
76+
let env = r#"{
77+
"LD_LIBRARY_PATH": "[..]",
78+
"CARGO_PKG_VERSION_PRE": "",
79+
"CARGO_PKG_VERSION_PATCH": "1",
80+
"CARGO_PKG_VERSION_MINOR": "0",
81+
"CARGO_PKG_VERSION_MAJOR": "0",
82+
"CARGO_PKG_VERSION": "0.0.1",
83+
"CARGO_PKG_NAME": "foo",
84+
"CARGO_PKG_HOMEPAGE": "",
85+
"CARGO_PKG_DESCRIPTION": "",
86+
"CARGO_PKG_AUTHORS": "",
87+
"CARGO_MANIFEST_DIR": "[..]",
88+
"CARGO": "[..]"
89+
}"#;
90+
assert_that(p.cargo_process("test").env("CARGO_WRAP", "1"),
91+
execs().with_status(0)
92+
.with_json(&format!(r#"
93+
{{
94+
"reason": "run-profile",
95+
"program": "[..]bar-[..]",
96+
"env": {env},
97+
"cwd": "[..]",
98+
"args": []
99+
}}
100+
101+
{{
102+
"reason": "run-profile",
103+
"program": "[..]baz-[..]",
104+
"env": {env},
105+
"cwd": "[..]",
106+
"args": []
107+
}}
108+
109+
{{
110+
"reason": "run-profile",
111+
"program": "[..]foo-[..]",
112+
"env": {env},
113+
"cwd": "[..]",
114+
"args": []
115+
}}
116+
"#, env=env)));
117+
}
118+

0 commit comments

Comments
 (0)