Skip to content

Commit d01b855

Browse files
committed
[nextest-runner] make_test_command -> TestCommand::new
Add a test_command module with a new TestCommand struct. This is going to carry double-spawn context in the future.
1 parent 1972ae8 commit d01b855

File tree

4 files changed

+234
-188
lines changed

4 files changed

+234
-188
lines changed

nextest-runner/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub mod runner;
2323
pub mod signal;
2424
mod stopwatch;
2525
pub mod target_runner;
26+
mod test_command;
2627
pub mod test_filter;
2728
#[cfg(feature = "self-update")]
2829
pub mod update;

nextest-runner/src/list/test_list.rs

+46-181
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::{
99
list::{BinaryList, OutputFormat, RustBuildMeta, Styles, TestListState},
1010
reuse_build::PathMapper,
1111
target_runner::{PlatformRunner, TargetRunner},
12+
test_command::{LocalExecuteContext, TestCommand},
1213
test_filter::TestFilterBuilder,
1314
};
1415
use camino::{Utf8Path, Utf8PathBuf};
@@ -24,7 +25,7 @@ use nextest_metadata::{
2425
use once_cell::sync::{Lazy, OnceCell};
2526
use owo_colors::OwoColorize;
2627
use std::{
27-
collections::{BTreeMap, BTreeSet, HashMap},
28+
collections::{BTreeMap, BTreeSet},
2829
ffi::{OsStr, OsString},
2930
io,
3031
io::Write,
@@ -681,47 +682,58 @@ impl<'g> RustTestArtifact<'g> {
681682
argv.push("--ignored");
682683
}
683684

684-
let cmd = make_test_command(
685+
let mut cmd = TestCommand::new(
685686
ctx,
686687
program.clone(),
687688
&argv,
688689
&self.cwd,
689690
&self.package,
690691
&self.non_test_binaries,
691692
);
692-
let mut cmd = tokio::process::Command::from(cmd);
693-
match cmd.output().await {
694-
Ok(output) => {
695-
if output.status.success() {
696-
String::from_utf8(output.stdout).map_err(|err| {
697-
CreateTestListError::CommandNonUtf8 {
698-
binary_id: self.binary_id.clone(),
699-
command: std::iter::once(program)
700-
.chain(argv.iter().map(|&s| s.to_owned()))
701-
.collect(),
702-
stdout: err.into_bytes(),
703-
stderr: output.stderr,
704-
}
705-
})
706-
} else {
707-
Err(CreateTestListError::CommandFail {
708-
binary_id: self.binary_id.clone(),
709-
command: std::iter::once(program)
710-
.chain(argv.iter().map(|&s| s.to_owned()))
711-
.collect(),
712-
exit_status: output.status,
713-
stdout: output.stdout,
714-
stderr: output.stderr,
715-
})
716-
}
693+
// Capture stdout and stderr.
694+
cmd.command_mut()
695+
.stdout(std::process::Stdio::piped())
696+
.stderr(std::process::Stdio::piped());
697+
698+
let child = cmd
699+
.spawn()
700+
.map_err(|error| CreateTestListError::CommandExecFail {
701+
binary_id: self.binary_id.clone(),
702+
command: std::iter::once(program.clone())
703+
.chain(argv.iter().map(|&s| s.to_owned()))
704+
.collect(),
705+
error,
706+
})?;
707+
708+
let output = child.wait_with_output().await.map_err(|error| {
709+
CreateTestListError::CommandExecFail {
710+
binary_id: self.binary_id.clone(),
711+
command: std::iter::once(program.clone())
712+
.chain(argv.iter().map(|&s| s.to_owned()))
713+
.collect(),
714+
error,
717715
}
718-
Err(error) => Err(CreateTestListError::CommandExecFail {
716+
})?;
717+
718+
if output.status.success() {
719+
String::from_utf8(output.stdout).map_err(|err| CreateTestListError::CommandNonUtf8 {
719720
binary_id: self.binary_id.clone(),
720721
command: std::iter::once(program)
721722
.chain(argv.iter().map(|&s| s.to_owned()))
722723
.collect(),
723-
error,
724-
}),
724+
stdout: err.into_bytes(),
725+
stderr: output.stderr,
726+
})
727+
} else {
728+
Err(CreateTestListError::CommandFail {
729+
binary_id: self.binary_id.clone(),
730+
command: std::iter::once(program)
731+
.chain(argv.iter().map(|&s| s.to_owned()))
732+
.collect(),
733+
exit_status: output.status,
734+
stdout: output.stdout,
735+
stderr: output.stderr,
736+
})
725737
}
726738
}
727739
}
@@ -817,12 +829,12 @@ impl<'a> TestInstance<'a> {
817829
(&self.suite_info.binary_id, self.name)
818830
}
819831

820-
/// Creates the command expression for this test instance.
821-
pub(crate) fn make_expression(
832+
/// Creates the command for this test instance.
833+
pub(crate) fn make_command(
822834
&self,
823835
ctx: &TestExecuteContext<'_>,
824836
test_list: &TestList<'_>,
825-
) -> std::process::Command {
837+
) -> TestCommand {
826838
let platform_runner = ctx
827839
.target_runner
828840
.for_build_platform(self.suite_info.build_platform);
@@ -851,7 +863,7 @@ impl<'a> TestInstance<'a> {
851863
env: &test_list.env,
852864
};
853865

854-
make_test_command(
866+
TestCommand::new(
855867
&ctx,
856868
program,
857869
&args,
@@ -872,153 +884,6 @@ pub struct TestExecuteContext<'a> {
872884
pub target_runner: &'a TargetRunner,
873885
}
874886

875-
#[derive(Clone, Debug)]
876-
struct LocalExecuteContext<'a> {
877-
double_spawn: &'a DoubleSpawnInfo,
878-
runner: &'a TargetRunner,
879-
dylib_path: &'a OsStr,
880-
env: &'a EnvironmentMap,
881-
}
882-
883-
/// Create a duct Expression for a test binary with the given arguments, using the specified [`PackageMetadata`].
884-
fn make_test_command(
885-
ctx: &LocalExecuteContext<'_>,
886-
program: String,
887-
args: &[&str],
888-
cwd: &Utf8PathBuf,
889-
package: &PackageMetadata<'_>,
890-
non_test_binaries: &BTreeSet<(String, Utf8PathBuf)>,
891-
) -> std::process::Command {
892-
// This is a workaround for a macOS SIP issue:
893-
// https://github.com/nextest-rs/nextest/pull/84
894-
//
895-
// Basically, if SIP is enabled, macOS removes any environment variables that start with
896-
// "LD_" or "DYLD_" when spawning system-protected processes. This unfortunately includes
897-
// processes like bash -- this means that if nextest invokes a shell script, paths might
898-
// end up getting sanitized.
899-
//
900-
// This is particularly relevant for target runners, which are often shell scripts.
901-
//
902-
// To work around this, re-export any variables that begin with LD_ or DYLD_ as "NEXTEST_LD_"
903-
// or "NEXTEST_DYLD_". Do this on all platforms for uniformity.
904-
//
905-
// Nextest never changes these environment variables within its own process, so caching them is
906-
// valid.
907-
fn is_sip_sanitized(var: &str) -> bool {
908-
// Look for variables starting with LD_ or DYLD_.
909-
// https://briandfoy.github.io/macos-s-system-integrity-protection-sanitizes-your-environment/
910-
var.starts_with("LD_") || var.starts_with("DYLD_")
911-
}
912-
913-
static LD_DYLD_ENV_VARS: Lazy<HashMap<String, OsString>> = Lazy::new(|| {
914-
std::env::vars_os()
915-
.filter_map(|(k, v)| match k.into_string() {
916-
Ok(k) => is_sip_sanitized(&k).then_some((k, v)),
917-
Err(_) => None,
918-
})
919-
.collect()
920-
});
921-
922-
// TODO: pass in double spawn option
923-
let mut cmd = if let Some(current_exe) = ctx.double_spawn.current_exe() {
924-
let mut cmd = std::process::Command::new(current_exe);
925-
cmd.args([DoubleSpawnInfo::SUBCOMMAND_NAME, "--", program.as_str()]);
926-
cmd.arg(&shell_words::join(args));
927-
cmd
928-
} else {
929-
let mut cmd = std::process::Command::new(program);
930-
cmd.args(args);
931-
cmd
932-
};
933-
934-
// NB: we will always override user-provided environment variables with the
935-
// `CARGO_*` and `NEXTEST_*` variables set directly on `cmd` below.
936-
ctx.env.apply_env(&mut cmd);
937-
938-
cmd.current_dir(cwd)
939-
// This environment variable is set to indicate that tests are being run under nextest.
940-
.env("NEXTEST", "1")
941-
// This environment variable is set to indicate that each test is being run in its own process.
942-
.env("NEXTEST_EXECUTION_MODE", "process-per-test")
943-
// These environment variables are set at runtime by cargo test:
944-
// https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
945-
.env(
946-
"CARGO_MANIFEST_DIR",
947-
// CARGO_MANIFEST_DIR is set to the *new* cwd after path mapping.
948-
cwd,
949-
)
950-
.env(
951-
"__NEXTEST_ORIGINAL_CARGO_MANIFEST_DIR",
952-
// This is a test-only environment variable set to the *old* cwd. Not part of the
953-
// public API.
954-
package.manifest_path().parent().unwrap(),
955-
)
956-
.env("CARGO_PKG_VERSION", format!("{}", package.version()))
957-
.env(
958-
"CARGO_PKG_VERSION_MAJOR",
959-
format!("{}", package.version().major),
960-
)
961-
.env(
962-
"CARGO_PKG_VERSION_MINOR",
963-
format!("{}", package.version().minor),
964-
)
965-
.env(
966-
"CARGO_PKG_VERSION_PATCH",
967-
format!("{}", package.version().patch),
968-
)
969-
.env(
970-
"CARGO_PKG_VERSION_PRE",
971-
format!("{}", package.version().pre),
972-
)
973-
.env("CARGO_PKG_AUTHORS", package.authors().join(":"))
974-
.env("CARGO_PKG_NAME", package.name())
975-
.env(
976-
"CARGO_PKG_DESCRIPTION",
977-
package.description().unwrap_or_default(),
978-
)
979-
.env("CARGO_PKG_HOMEPAGE", package.homepage().unwrap_or_default())
980-
.env("CARGO_PKG_LICENSE", package.license().unwrap_or_default())
981-
.env(
982-
"CARGO_PKG_LICENSE_FILE",
983-
package.license_file().unwrap_or_else(|| "".as_ref()),
984-
)
985-
.env(
986-
"CARGO_PKG_REPOSITORY",
987-
package.repository().unwrap_or_default(),
988-
)
989-
.env(
990-
"CARGO_PKG_RUST_VERSION",
991-
package.rust_version().map_or(String::new(), |v| {
992-
// A Rust version e.g. "1.58" has v.to_string() generated as "^1.58".
993-
// Remove the prefix ^.
994-
let s = v.to_string();
995-
match s.strip_prefix('^') {
996-
Some(suffix) => suffix.to_owned(),
997-
None => s,
998-
}
999-
}),
1000-
)
1001-
.env(dylib_path_envvar(), ctx.dylib_path);
1002-
1003-
for (k, v) in &*LD_DYLD_ENV_VARS {
1004-
if k != dylib_path_envvar() {
1005-
cmd.env("NEXTEST_".to_owned() + k, v);
1006-
}
1007-
}
1008-
// Also add the dylib path envvar under the NEXTEST_ prefix.
1009-
if is_sip_sanitized(dylib_path_envvar()) {
1010-
cmd.env("NEXTEST_".to_owned() + dylib_path_envvar(), ctx.dylib_path);
1011-
}
1012-
1013-
// Expose paths to non-test binaries at runtime so that relocated paths work.
1014-
// These paths aren't exposed by Cargo at runtime, so use a NEXTEST_BIN_EXE prefix.
1015-
for (name, path) in non_test_binaries {
1016-
cmd.env(format!("NEXTEST_BIN_EXE_{}", name), path);
1017-
}
1018-
1019-
cmd
1020-
}
1021-
1022887
#[cfg(test)]
1023888
mod tests {
1024889
use super::*;

nextest-runner/src/runner.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -589,25 +589,26 @@ impl<'a> TestRunnerInner<'a> {
589589
double_spawn: &self.double_spawn,
590590
target_runner: &self.target_runner,
591591
};
592-
let mut cmd = test.make_expression(&ctx, self.test_list);
592+
let mut cmd = test.make_command(&ctx, self.test_list);
593+
let command_mut = cmd.command_mut();
593594

594595
// Debug environment variable for testing.
595-
cmd.env("__NEXTEST_ATTEMPT", format!("{}", retry_data.attempt));
596-
cmd.env("NEXTEST_RUN_ID", format!("{}", self.run_id));
597-
cmd.stdin(Stdio::null());
598-
imp::cmd_pre_exec(&mut cmd);
596+
command_mut.env("__NEXTEST_ATTEMPT", format!("{}", retry_data.attempt));
597+
command_mut.env("NEXTEST_RUN_ID", format!("{}", self.run_id));
598+
command_mut.stdin(Stdio::null());
599+
imp::cmd_pre_exec(command_mut);
599600

600601
// If creating a job fails, we might be on an old system. Ignore this -- job objects are a
601602
// best-effort thing.
602603
let job = imp::Job::create().ok();
603604

604605
if !self.no_capture {
605606
// Capture stdout and stderr.
606-
cmd.stdout(std::process::Stdio::piped())
607+
command_mut
608+
.stdout(std::process::Stdio::piped())
607609
.stderr(std::process::Stdio::piped());
608610
};
609611

610-
let mut cmd = tokio::process::Command::from(cmd);
611612
let mut child = cmd.spawn()?;
612613

613614
// If assigning the child to the job fails, ignore this. This can happen if the process has

0 commit comments

Comments
 (0)