integration_tests/
nextest_cli.rsuse camino::Utf8PathBuf;
use color_eyre::{
eyre::{bail, Context},
Result,
};
use nextest_metadata::TestListSummary;
use std::{
borrow::Cow,
collections::HashMap,
ffi::OsString,
fmt,
process::{Command, ExitStatus},
};
pub fn cargo_bin() -> String {
match std::env::var("CARGO") {
Ok(v) => v,
Err(std::env::VarError::NotPresent) => "cargo".to_owned(),
Err(err) => panic!("error obtaining CARGO env var: {err}"),
}
}
#[derive(Clone, Debug)]
pub struct CargoNextestCli {
bin: Utf8PathBuf,
args: Vec<String>,
envs: HashMap<OsString, OsString>,
unchecked: bool,
}
impl CargoNextestCli {
pub fn for_test() -> Self {
let bin = std::env::var("NEXTEST_BIN_EXE_cargo-nextest-dup")
.expect("unable to find cargo-nextest-dup");
Self {
bin: bin.into(),
args: vec!["nextest".to_owned()],
envs: HashMap::new(),
unchecked: false,
}
}
pub fn for_script() -> Result<Self> {
let cargo_bin = cargo_bin();
let mut command = std::process::Command::new(&cargo_bin);
command.args([
"run",
"--bin",
"cargo-nextest-dup",
"--",
"nextest",
"debug",
"current-exe",
]);
let output = command.output().wrap_err("failed to get current exe")?;
let output = CargoNextestOutput {
command,
exit_status: output.status,
stdout: output.stdout,
stderr: output.stderr,
};
if !output.exit_status.success() {
bail!("failed to get current exe:\n\n{output:?}");
}
let exe =
String::from_utf8(output.stdout).wrap_err("current exe output isn't valid UTF-8")?;
Ok(Self {
bin: Utf8PathBuf::from(exe.trim_end()),
args: vec!["nextest".to_owned()],
envs: HashMap::new(),
unchecked: false,
})
}
pub fn arg(&mut self, arg: impl Into<String>) -> &mut Self {
self.args.push(arg.into());
self
}
pub fn args(&mut self, arg: impl IntoIterator<Item = impl Into<String>>) -> &mut Self {
self.args.extend(arg.into_iter().map(Into::into));
self
}
pub fn env(&mut self, k: impl Into<OsString>, v: impl Into<OsString>) -> &mut Self {
self.envs.insert(k.into(), v.into());
self
}
pub fn envs(
&mut self,
envs: impl IntoIterator<Item = (impl Into<OsString>, impl Into<OsString>)>,
) -> &mut Self {
self.envs
.extend(envs.into_iter().map(|(k, v)| (k.into(), v.into())));
self
}
pub fn unchecked(&mut self, unchecked: bool) -> &mut Self {
self.unchecked = unchecked;
self
}
pub fn output(&self) -> CargoNextestOutput {
let mut command = std::process::Command::new(&self.bin);
command.args(&self.args);
command.envs(&self.envs);
let output = command.output().expect("failed to execute");
let ret = CargoNextestOutput {
command,
exit_status: output.status,
stdout: output.stdout,
stderr: output.stderr,
};
if !self.unchecked && !output.status.success() {
panic!("command failed:\n\n{ret}");
}
ret
}
}
pub struct CargoNextestOutput {
pub command: Command,
pub exit_status: ExitStatus,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
}
impl CargoNextestOutput {
pub fn stdout_as_str(&self) -> Cow<'_, str> {
String::from_utf8_lossy(&self.stdout)
}
pub fn stderr_as_str(&self) -> Cow<'_, str> {
String::from_utf8_lossy(&self.stderr)
}
pub fn decode_test_list_json(&self) -> Result<TestListSummary> {
Ok(serde_json::from_slice(&self.stdout)?)
}
}
impl fmt::Display for CargoNextestOutput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"command: {:?}\nexit code: {:?}\n\
--- stdout ---\n{}\n\n--- stderr ---\n{}\n\n",
self.command,
self.exit_status.code(),
String::from_utf8_lossy(&self.stdout),
String::from_utf8_lossy(&self.stderr)
)
}
}
impl fmt::Debug for CargoNextestOutput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}