Skip to content

Commit e43ee0f

Browse files
authored
Add support for target runners (#61)
1 parent 1d23c4a commit e43ee0f

File tree

13 files changed

+833
-29
lines changed

13 files changed

+833
-29
lines changed

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cargo-nextest/src/cargo_cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub(crate) struct CargoOptions {
8989

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

9494
/// Directory for all generated artifacts
9595
#[clap(long, value_name = "DIR")]

cargo-nextest/src/dispatch.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use nextest_runner::{
1717
reporter::{StatusLevel, TestOutputDisplay, TestReporterBuilder},
1818
runner::TestRunnerBuilder,
1919
signal::SignalHandler,
20+
target_runner::TargetRunner,
2021
test_filter::{RunIgnored, TestFilterBuilder},
2122
test_list::{OutputFormat, RustTestArtifact, SerializableFormat, TestList},
2223
};
@@ -183,6 +184,7 @@ impl TestBuildFilter {
183184
manifest_path: Option<&'g Utf8Path>,
184185
graph: &'g PackageGraph,
185186
output: OutputContext,
187+
runner: Option<&TargetRunner>,
186188
) -> Result<TestList<'g>> {
187189
// Don't use the manifest path from the graph to ensure that if the user cd's into a
188190
// particular crate and runs cargo nextest, then it behaves identically to cargo test.
@@ -209,7 +211,7 @@ impl TestBuildFilter {
209211

210212
let test_filter =
211213
TestFilterBuilder::new(self.run_ignored, self.partition.clone(), &self.filter);
212-
TestList::new(test_artifacts, &test_filter).wrap_err("error building test list")
214+
TestList::new(test_artifacts, &test_filter, runner).wrap_err("error building test list")
213215
}
214216
}
215217

@@ -316,8 +318,15 @@ impl AppImpl {
316318
build_filter,
317319
message_format,
318320
} => {
319-
let mut test_list =
320-
build_filter.compute(self.manifest_path.as_deref(), &graph, output)?;
321+
let target_runner =
322+
TargetRunner::for_target(build_filter.cargo_options.target.as_deref())?;
323+
324+
let mut test_list = build_filter.compute(
325+
self.manifest_path.as_deref(),
326+
&graph,
327+
output,
328+
target_runner.as_ref(),
329+
)?;
321330
if output.color.should_colorize(Stream::Stdout) {
322331
test_list.colorize();
323332
}
@@ -343,8 +352,15 @@ impl AppImpl {
343352
std::fs::create_dir_all(&store_dir)
344353
.wrap_err_with(|| format!("failed to create store dir '{}'", store_dir))?;
345354

346-
let test_list =
347-
build_filter.compute(self.manifest_path.as_deref(), &graph, output)?;
355+
let target_runner =
356+
TargetRunner::for_target(build_filter.cargo_options.target.as_deref())?;
357+
358+
let test_list = build_filter.compute(
359+
self.manifest_path.as_deref(),
360+
&graph,
361+
output,
362+
target_runner.as_ref(),
363+
)?;
348364

349365
let mut reporter = reporter_opts
350366
.to_builder(no_capture)
@@ -355,9 +371,10 @@ impl AppImpl {
355371
}
356372

357373
let handler = SignalHandler::new().wrap_err("failed to set up Ctrl-C handler")?;
358-
let runner = runner_opts
359-
.to_builder(no_capture)
360-
.build(&test_list, &profile, handler);
374+
let mut runner_builder = runner_opts.to_builder(no_capture);
375+
runner_builder.set_target_runner(target_runner);
376+
377+
let runner = runner_builder.build(&test_list, &profile, handler);
361378
let stderr = std::io::stderr();
362379
let mut writer = BufWriter::new(stderr);
363380
let run_stats = runner.try_execute(|event| {

fixtures/nextest-tests/.cargo/config

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[target.x86_64-pc-windows-gnu]
2+
runner = "wine"
3+
4+
[target.'cfg(target_os = "android")']
5+
runner = "android-runner -x"
6+
7+
[target.'cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "musl"))']
8+
runner = "passthrough --ensure-this-arg-is-sent"

fixtures/passthrough

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
if [[ "$1" != "--ensure-this-arg-is-sent" ]] ; then
3+
exit 1
4+
fi
5+
6+
bin="$2"
7+
shift 2
8+
9+
$bin "$@"

nextest-runner/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ ctrlc = { version = "3.2.1", features = ["termination"] }
2020
debug-ignore = "1.0.1"
2121
duct = "0.13.5"
2222
guppy = "0.13.0"
23+
# Used to find the cargo root directory, which is needed in case the user has
24+
# added a config.toml there
25+
home = "0.5.3"
2326
humantime-serde = "1.0.1"
2427
indent_write = "2.2.0"
2528
once_cell = "1.9.0"
@@ -29,6 +32,10 @@ rayon = "1.5.1"
2932
serde = { version = "1.0.136", features = ["derive"] }
3033
serde_json = "1.0.79"
3134
strip-ansi-escapes = "0.1.1"
35+
# For cfg expression evaluation for [target.'cfg()'] expressions
36+
target-spec = "1.0"
37+
# For parsing of .cargo/config.toml files
38+
toml = "0.5.8"
3239
twox-hash = { version = "1.6.2", default-features = false }
3340

3441
nextest-metadata = { version = "0.1.0", path = "../nextest-metadata" }

nextest-runner/src/errors.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,3 +405,105 @@ impl error::Error for JunitError {
405405
Some(&self.err)
406406
}
407407
}
408+
409+
/// An error occurred determining the target runner
410+
#[derive(Debug)]
411+
pub enum TargetRunnerError {
412+
/// Failed to determine the host triple, which is needed to determine the
413+
/// default target triple when a target is not explicitly specified
414+
UnknownHostPlatform(target_spec::Error),
415+
/// An environment variable contained non-utf8 content
416+
InvalidEnvironmentVar(String),
417+
/// An environment variable or config key was found that matches the target
418+
/// triple, but it didn't actually contain a binary
419+
BinaryNotSpecified {
420+
/// The environment variable or config key path
421+
key: String,
422+
/// The value that was read from the key
423+
value: String,
424+
},
425+
/// Failed to retrieve a directory
426+
UnableToReadDir(std::io::Error),
427+
/// Failed to canonicalize a path
428+
FailedPathCanonicalization {
429+
/// The path that failed to canonicalize
430+
path: Utf8PathBuf,
431+
/// The error the occurred during canonicalization
432+
error: std::io::Error,
433+
},
434+
/// A path was non-utf8
435+
NonUtf8Path(std::path::PathBuf),
436+
/// Failed to read config file
437+
FailedToReadConfig {
438+
/// The path of the config file
439+
path: Utf8PathBuf,
440+
/// The error that occurred trying to read the config file
441+
error: std::io::Error,
442+
},
443+
/// Failed to deserialize config file
444+
FailedToParseConfig {
445+
/// The path of the config file
446+
path: Utf8PathBuf,
447+
/// The error that occurred trying to deserialize the config file
448+
error: toml::de::Error,
449+
},
450+
/// Failed to parse the specified target triple
451+
FailedToParseTargetTriple {
452+
/// The triple that failed to parse
453+
triple: String,
454+
/// The error that occurred parsing the triple
455+
error: target_spec::errors::TripleParseError,
456+
},
457+
}
458+
459+
impl fmt::Display for TargetRunnerError {
460+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
461+
match self {
462+
Self::UnknownHostPlatform(error) => {
463+
write!(f, "unable to determine host triple: {}", error)
464+
}
465+
Self::InvalidEnvironmentVar(key) => {
466+
write!(f, "environment variable '{}' contained non-utf8 data", key)
467+
}
468+
Self::BinaryNotSpecified { key, value } => {
469+
write!(
470+
f,
471+
"runner '{}' = '{}' did not contain a runner binary",
472+
key, value
473+
)
474+
}
475+
Self::UnableToReadDir(io) => {
476+
write!(f, "unable to read directory: {}", io)
477+
}
478+
Self::FailedPathCanonicalization { path, error } => {
479+
write!(f, "failed to canonicalize path '{}': {}", path, error)
480+
}
481+
Self::NonUtf8Path(path) => {
482+
write!(f, "path '{}' is non-utf8", path.display())
483+
}
484+
Self::FailedToReadConfig { path, error } => {
485+
write!(f, "failed to read '{}': {}", path, error)
486+
}
487+
Self::FailedToParseConfig { path, error } => {
488+
write!(f, "failed to parse config '{}': {}", path, error)
489+
}
490+
Self::FailedToParseTargetTriple { triple, error } => {
491+
write!(f, "failed to parse triple '{}': {}", triple, error)
492+
}
493+
}
494+
}
495+
}
496+
497+
impl error::Error for TargetRunnerError {
498+
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
499+
match self {
500+
Self::UnknownHostPlatform(error) => Some(error),
501+
Self::UnableToReadDir(io) => Some(io),
502+
Self::FailedPathCanonicalization { error, .. } => Some(error),
503+
Self::FailedToReadConfig { error, .. } => Some(error),
504+
Self::FailedToParseConfig { error, .. } => Some(error),
505+
Self::FailedToParseTargetTriple { error, .. } => Some(error),
506+
_ => None,
507+
}
508+
}
509+
}

nextest-runner/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,6 @@ pub mod reporter;
4848
pub mod runner;
4949
pub mod signal;
5050
mod stopwatch;
51+
pub mod target_runner;
5152
pub mod test_filter;
5253
pub mod test_list;

nextest-runner/src/runner.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{
1010
reporter::{CancelReason, StatusLevel, TestEvent},
1111
signal::{SignalEvent, SignalHandler},
1212
stopwatch::{StopwatchEnd, StopwatchStart},
13+
target_runner::TargetRunner,
1314
test_list::{TestInstance, TestList},
1415
};
1516
use crossbeam_channel::{RecvTimeoutError, Sender};
@@ -32,6 +33,7 @@ pub struct TestRunnerBuilder {
3233
retries: Option<usize>,
3334
fail_fast: Option<bool>,
3435
test_threads: Option<usize>,
36+
target_runner: Option<TargetRunner>,
3537
}
3638

3739
impl TestRunnerBuilder {
@@ -61,9 +63,16 @@ impl TestRunnerBuilder {
6163
self
6264
}
6365

66+
/// Sets the target specific runner to use, instead of trying to execute
67+
/// the binary natively
68+
pub fn set_target_runner(&mut self, target_runner: Option<TargetRunner>) -> &mut Self {
69+
self.target_runner = target_runner;
70+
self
71+
}
72+
6473
/// Creates a new test runner.
6574
pub fn build<'a>(
66-
&self,
75+
self,
6776
test_list: &'a TestList,
6877
profile: &NextestProfile<'_>,
6978
handler: SignalHandler,
@@ -75,13 +84,16 @@ impl TestRunnerBuilder {
7584
let retries = self.retries.unwrap_or_else(|| profile.retries());
7685
let fail_fast = self.fail_fast.unwrap_or_else(|| profile.fail_fast());
7786
let slow_timeout = profile.slow_timeout();
87+
let target_runner = self.target_runner;
88+
7889
TestRunner {
7990
no_capture: self.no_capture,
8091
// The number of tries = retries + 1.
8192
tries: retries + 1,
8293
fail_fast,
8394
slow_timeout,
8495
test_list,
96+
target_runner,
8597
run_pool: ThreadPoolBuilder::new()
8698
// The main run_pool closure will need its own thread.
8799
.num_threads(test_threads + 1)
@@ -107,6 +119,7 @@ pub struct TestRunner<'a> {
107119
fail_fast: bool,
108120
slow_timeout: Duration,
109121
test_list: &'a TestList<'a>,
122+
target_runner: Option<TargetRunner>,
110123
run_pool: ThreadPool,
111124
wait_pool: ThreadPool,
112125
handler: SignalHandler,
@@ -338,7 +351,7 @@ impl<'a> TestRunner<'a> {
338351
run_sender: &Sender<InternalTestEvent<'a>>,
339352
) -> std::io::Result<InternalExecuteStatus> {
340353
let cmd = test
341-
.make_expression()
354+
.make_expression(self.target_runner.as_ref())
342355
.unchecked()
343356
// Debug environment variable for testing.
344357
.env("__NEXTEST_ATTEMPT", format!("{}", attempt));

0 commit comments

Comments
 (0)