1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
use crate::ops::forc_build;
use anyhow::{bail, Result};
use clap::Parser;
use std::io::{BufRead, BufReader};
use std::process;
use std::thread;
use tracing::{error, info};
/// Run Rust-based tests on current project.
/// As of now, `forc test` is a simple wrapper on
/// `cargo test`; `forc new` also creates a rust
/// package under your project, named `tests`.
/// You can opt to either run these Rust tests by
/// using `forc test` or going inside the package
/// and using `cargo test`.
#[derive(Debug, Parser)]
pub(crate) struct Command {
/// If specified, only run tests containing this string in their names
pub test_name: Option<String>,
/// Options passed through to the `cargo test` invocation.
///
/// E.g. Given the following:
///
/// `forc test --cargo-test-opts="--color always"`
///
/// The `--color always` option is forwarded to `cargo test` like so:
///
/// `cargo test --color always`
#[clap(long)]
pub cargo_test_opts: Option<String>,
/// All trailing arguments following `--` are collected within this argument.
///
/// E.g. Given the following:
///
/// `forc test -- foo bar baz`
///
/// The arguments `foo`, `bar` and `baz` are forwarded on to `cargo test` like so:
///
/// `cargo test -- foo bar baz`
#[clap(raw = true)]
pub cargo_test_args: Vec<String>,
}
pub(crate) fn exec(command: Command) -> Result<()> {
// Ensure the project builds before running tests.
forc_build::build(Default::default())?;
let mut cmd = process::Command::new("cargo");
cmd.arg("test");
// Pass through cargo test options.
let mut user_specified_color_opt = false;
if let Some(opts) = command.cargo_test_opts {
user_specified_color_opt = opts.contains("--color");
for opt in opts.split_whitespace() {
cmd.arg(&opt);
}
}
// If the coloring option wasn't specified by the user, enable it ourselves. This is useful as
// `cargo test`'s coloring is disabled by default when run as a child process.
if !user_specified_color_opt {
cmd.args(&["--color", "always"]);
}
// Pass through test name.
if let Some(ref name) = command.test_name {
cmd.arg(name);
}
// Pass through cargo test args.
if !command.cargo_test_args.is_empty() {
cmd.arg("--");
cmd.args(&command.cargo_test_args);
}
let mut child = cmd
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.spawn()
.unwrap();
let out = BufReader::new(child.stdout.take().unwrap());
let err = BufReader::new(child.stderr.take().unwrap());
// Reading stderr on a separate thread so we keep things non-blocking
let thread = thread::spawn(move || {
err.lines().for_each(|line| error!("{}", line.unwrap()));
});
out.lines().for_each(|line| info!("{}", line.unwrap()));
thread.join().unwrap();
let child_success = match child.try_wait() {
Ok(Some(returned_status)) => returned_status.success(),
Ok(None) => child.wait().unwrap().success(),
Err(_) => false,
};
match child_success {
true => Ok(()),
false => bail!("child test process failed"),
}
}