Skip to content
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

Add support for target runners #61

Merged
merged 9 commits into from
Feb 22, 2022
Merged
Show file tree
Hide file tree
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: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cargo-nextest/src/cargo_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ pub(crate) struct CargoOptions {

/// Build for the target triple
#[clap(long, value_name = "TRIPLE")]
target: Option<String>,
pub(crate) target: Option<String>,

/// Directory for all generated artifacts
#[clap(long, value_name = "DIR")]
Expand Down
33 changes: 25 additions & 8 deletions cargo-nextest/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use nextest_runner::{
reporter::{StatusLevel, TestOutputDisplay, TestReporterBuilder},
runner::TestRunnerBuilder,
signal::SignalHandler,
target_runner::TargetRunner,
test_filter::{RunIgnored, TestFilterBuilder},
test_list::{OutputFormat, RustTestArtifact, SerializableFormat, TestList},
};
Expand Down Expand Up @@ -183,6 +184,7 @@ impl TestBuildFilter {
manifest_path: Option<&'g Utf8Path>,
graph: &'g PackageGraph,
output: OutputContext,
runner: Option<&TargetRunner>,
) -> Result<TestList<'g>> {
// Don't use the manifest path from the graph to ensure that if the user cd's into a
// particular crate and runs cargo nextest, then it behaves identically to cargo test.
Expand All @@ -209,7 +211,7 @@ impl TestBuildFilter {

let test_filter =
TestFilterBuilder::new(self.run_ignored, self.partition.clone(), &self.filter);
TestList::new(test_artifacts, &test_filter).wrap_err("error building test list")
TestList::new(test_artifacts, &test_filter, runner).wrap_err("error building test list")
}
}

Expand Down Expand Up @@ -316,8 +318,15 @@ impl AppImpl {
build_filter,
message_format,
} => {
let mut test_list =
build_filter.compute(self.manifest_path.as_deref(), &graph, output)?;
let target_runner =
TargetRunner::for_target(build_filter.cargo_options.target.as_deref())?;

let mut test_list = build_filter.compute(
self.manifest_path.as_deref(),
&graph,
output,
target_runner.as_ref(),
)?;
if output.color.should_colorize(Stream::Stdout) {
test_list.colorize();
}
Expand All @@ -343,8 +352,15 @@ impl AppImpl {
std::fs::create_dir_all(&store_dir)
.wrap_err_with(|| format!("failed to create store dir '{}'", store_dir))?;

let test_list =
build_filter.compute(self.manifest_path.as_deref(), &graph, output)?;
let target_runner =
TargetRunner::for_target(build_filter.cargo_options.target.as_deref())?;

let test_list = build_filter.compute(
self.manifest_path.as_deref(),
&graph,
output,
target_runner.as_ref(),
)?;

let mut reporter = reporter_opts
.to_builder(no_capture)
Expand All @@ -355,9 +371,10 @@ impl AppImpl {
}

let handler = SignalHandler::new().wrap_err("failed to set up Ctrl-C handler")?;
let runner = runner_opts
.to_builder(no_capture)
.build(&test_list, &profile, handler);
let mut runner_builder = runner_opts.to_builder(no_capture);
runner_builder.set_target_runner(target_runner);

let runner = runner_builder.build(&test_list, &profile, handler);
let stderr = std::io::stderr();
let mut writer = BufWriter::new(stderr);
let run_stats = runner.try_execute(|event| {
Expand Down
8 changes: 8 additions & 0 deletions fixtures/nextest-tests/.cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[target.x86_64-pc-windows-gnu]
runner = "wine"

[target.'cfg(target_os = "android")']
runner = "android-runner -x"

[target.'cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "musl"))']
runner = "passthrough --ensure-this-arg-is-sent"
9 changes: 9 additions & 0 deletions fixtures/passthrough
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
if [[ "$1" != "--ensure-this-arg-is-sent" ]] ; then
exit 1
fi

bin="$2"
shift 2

$bin "$@"
7 changes: 7 additions & 0 deletions nextest-runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ ctrlc = { version = "3.2.1", features = ["termination"] }
debug-ignore = "1.0.1"
duct = "0.13.5"
guppy = "0.13.0"
# Used to find the cargo root directory, which is needed in case the user has
# added a config.toml there
home = "0.5.3"
humantime-serde = "1.0.1"
indent_write = "2.2.0"
once_cell = "1.9.0"
Expand All @@ -29,6 +32,10 @@ rayon = "1.5.1"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
strip-ansi-escapes = "0.1.1"
# For cfg expression evaluation for [target.'cfg()'] expressions
target-spec = "1.0"
# For parsing of .cargo/config.toml files
toml = "0.5.8"
twox-hash = { version = "1.6.2", default-features = false }

nextest-metadata = { version = "0.1.0", path = "../nextest-metadata" }
Expand Down
102 changes: 102 additions & 0 deletions nextest-runner/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,105 @@ impl error::Error for JunitError {
Some(&self.err)
}
}

/// An error occurred determining the target runner
#[derive(Debug)]
pub enum TargetRunnerError {
/// Failed to determine the host triple, which is needed to determine the
/// default target triple when a target is not explicitly specified
UnknownHostPlatform(target_spec::Error),
/// An environment variable contained non-utf8 content
InvalidEnvironmentVar(String),
/// An environment variable or config key was found that matches the target
/// triple, but it didn't actually contain a binary
BinaryNotSpecified {
/// The environment variable or config key path
key: String,
/// The value that was read from the key
value: String,
},
/// Failed to retrieve a directory
UnableToReadDir(std::io::Error),
/// Failed to canonicalize a path
FailedPathCanonicalization {
/// The path that failed to canonicalize
path: Utf8PathBuf,
/// The error the occurred during canonicalization
error: std::io::Error,
},
/// A path was non-utf8
NonUtf8Path(std::path::PathBuf),
/// Failed to read config file
FailedToReadConfig {
/// The path of the config file
path: Utf8PathBuf,
/// The error that occurred trying to read the config file
error: std::io::Error,
},
/// Failed to deserialize config file
FailedToParseConfig {
/// The path of the config file
path: Utf8PathBuf,
/// The error that occurred trying to deserialize the config file
error: toml::de::Error,
},
/// Failed to parse the specified target triple
FailedToParseTargetTriple {
/// The triple that failed to parse
triple: String,
/// The error that occurred parsing the triple
error: target_spec::errors::TripleParseError,
},
}

impl fmt::Display for TargetRunnerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnknownHostPlatform(error) => {
write!(f, "unable to determine host triple: {}", error)
}
Self::InvalidEnvironmentVar(key) => {
write!(f, "environment variable '{}' contained non-utf8 data", key)
}
Self::BinaryNotSpecified { key, value } => {
write!(
f,
"runner '{}' = '{}' did not contain a runner binary",
key, value
)
}
Self::UnableToReadDir(io) => {
write!(f, "unable to read directory: {}", io)
}
Self::FailedPathCanonicalization { path, error } => {
write!(f, "failed to canonicalize path '{}': {}", path, error)
}
Self::NonUtf8Path(path) => {
write!(f, "path '{}' is non-utf8", path.display())
}
Self::FailedToReadConfig { path, error } => {
write!(f, "failed to read '{}': {}", path, error)
}
Self::FailedToParseConfig { path, error } => {
write!(f, "failed to parse config '{}': {}", path, error)
}
Self::FailedToParseTargetTriple { triple, error } => {
write!(f, "failed to parse triple '{}': {}", triple, error)
}
}
}
}

impl error::Error for TargetRunnerError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Self::UnknownHostPlatform(error) => Some(error),
Self::UnableToReadDir(io) => Some(io),
Self::FailedPathCanonicalization { error, .. } => Some(error),
Self::FailedToReadConfig { error, .. } => Some(error),
Self::FailedToParseConfig { error, .. } => Some(error),
Self::FailedToParseTargetTriple { error, .. } => Some(error),
_ => None,
}
}
}
1 change: 1 addition & 0 deletions nextest-runner/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ pub mod reporter;
pub mod runner;
pub mod signal;
mod stopwatch;
pub mod target_runner;
pub mod test_filter;
pub mod test_list;
17 changes: 15 additions & 2 deletions nextest-runner/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
reporter::{CancelReason, StatusLevel, TestEvent},
signal::{SignalEvent, SignalHandler},
stopwatch::{StopwatchEnd, StopwatchStart},
target_runner::TargetRunner,
test_list::{TestInstance, TestList},
};
use crossbeam_channel::{RecvTimeoutError, Sender};
Expand All @@ -32,6 +33,7 @@ pub struct TestRunnerBuilder {
retries: Option<usize>,
fail_fast: Option<bool>,
test_threads: Option<usize>,
target_runner: Option<TargetRunner>,
}

impl TestRunnerBuilder {
Expand Down Expand Up @@ -61,9 +63,16 @@ impl TestRunnerBuilder {
self
}

/// Sets the target specific runner to use, instead of trying to execute
/// the binary natively
pub fn set_target_runner(&mut self, target_runner: Option<TargetRunner>) -> &mut Self {
self.target_runner = target_runner;
self
}

/// Creates a new test runner.
pub fn build<'a>(
&self,
self,
test_list: &'a TestList,
profile: &NextestProfile<'_>,
handler: SignalHandler,
Expand All @@ -75,13 +84,16 @@ impl TestRunnerBuilder {
let retries = self.retries.unwrap_or_else(|| profile.retries());
let fail_fast = self.fail_fast.unwrap_or_else(|| profile.fail_fast());
let slow_timeout = profile.slow_timeout();
let target_runner = self.target_runner;

TestRunner {
no_capture: self.no_capture,
// The number of tries = retries + 1.
tries: retries + 1,
fail_fast,
slow_timeout,
test_list,
target_runner,
run_pool: ThreadPoolBuilder::new()
// The main run_pool closure will need its own thread.
.num_threads(test_threads + 1)
Expand All @@ -107,6 +119,7 @@ pub struct TestRunner<'a> {
fail_fast: bool,
slow_timeout: Duration,
test_list: &'a TestList<'a>,
target_runner: Option<TargetRunner>,
run_pool: ThreadPool,
wait_pool: ThreadPool,
handler: SignalHandler,
Expand Down Expand Up @@ -338,7 +351,7 @@ impl<'a> TestRunner<'a> {
run_sender: &Sender<InternalTestEvent<'a>>,
) -> std::io::Result<InternalExecuteStatus> {
let cmd = test
.make_expression()
.make_expression(self.target_runner.as_ref())
.unchecked()
// Debug environment variable for testing.
.env("__NEXTEST_ATTEMPT", format!("{}", attempt));
Expand Down
Loading