use std::path::PathBuf;
use std::process::Command;
use super::probe::{ProbeError, probe_first};
pub(crate) fn run_export(
test: String,
output: Option<PathBuf>,
package: Option<String>,
release: bool,
) -> Result<(), String> {
let test_flag = format!("--ktstr-export-test={test}");
let output_flag = match output.as_deref() {
Some(o) => {
let abs = if o.is_absolute() {
o.to_path_buf()
} else {
std::env::current_dir()
.map_err(|e| format!("resolve cwd for --output: {e}"))?
.join(o)
};
Some(format!("--ktstr-export-output={}", abs.display()))
}
None => None,
};
let configure_cmd = |bin: &std::path::Path| {
let mut cmd = Command::new(bin);
cmd.arg(&test_flag);
if let Some(o) = &output_flag {
cmd.arg(o);
}
cmd.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::piped());
cmd
};
let on_success = |_bin: &std::path::Path, out: &std::process::Output| -> Result<(), String> {
std::io::Write::write_all(&mut std::io::stderr(), &out.stderr)
.map_err(|e| format!("forward winner stderr: {e}"))?;
Ok(())
};
match probe_first(package.as_deref(), release, configure_cmd, on_success) {
Ok(()) => Ok(()),
Err(ProbeError::Setup(msg)) => Err(msg),
Err(ProbeError::Miss(miss)) => Err(miss.render(&test, "cannot be exported")),
}
}
fn build_test_binaries_argv(package: Option<&str>, release: bool) -> Vec<String> {
let mut argv = vec![
"build".to_string(),
"--tests".to_string(),
"--message-format=json".to_string(),
];
let mut forwarded: Vec<&str> = Vec::new();
if cfg!(feature = "wprof") {
forwarded.push("wprof");
}
if cfg!(feature = "integration") {
forwarded.push("integration");
}
if !forwarded.is_empty() {
argv.push("--features".to_string());
argv.push(forwarded.join(","));
}
if let Some(p) = package {
argv.push("--package".to_string());
argv.push(p.to_string());
}
if release {
argv.push("--release".to_string());
}
argv
}
pub(crate) fn build_test_binaries(
package: Option<&str>,
release: bool,
) -> Result<Vec<PathBuf>, String> {
let mut cmd = Command::new("cargo");
cmd.args(build_test_binaries_argv(package, release));
cmd.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::inherit());
let out = cmd
.output()
.map_err(|e| format!("spawn cargo build --tests: {e}"))?;
if !out.status.success() {
return Err(format!(
"cargo build --tests failed (exit {})",
out.status.code().unwrap_or(-1),
));
}
let stdout = String::from_utf8_lossy(&out.stdout);
let mut bins: Vec<PathBuf> = Vec::new();
for line in stdout.lines() {
let Ok(msg) = serde_json::from_str::<serde_json::Value>(line) else {
continue;
};
if msg.get("reason").and_then(|r| r.as_str()) != Some("compiler-artifact") {
continue;
}
let Some(exe) = msg.get("executable").and_then(|e| e.as_str()) else {
continue;
};
let kinds: Vec<&str> = msg
.get("target")
.and_then(|t| t.get("kind"))
.and_then(|k| k.as_array())
.map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
.unwrap_or_default();
let is_test_target = kinds.contains(&"test");
let is_unit_test = msg
.get("profile")
.and_then(|p| p.get("test"))
.and_then(|t| t.as_bool())
== Some(true);
if is_test_target || is_unit_test {
bins.push(PathBuf::from(exe));
}
}
bins.sort();
bins.dedup();
Ok(bins)
}
#[cfg(test)]
mod tests {
use super::build_test_binaries_argv;
#[test]
fn argv_forwards_running_binary_features() {
let argv = build_test_binaries_argv(None, false);
assert_eq!(argv[0], "build");
assert_eq!(argv[1], "--tests");
assert_eq!(argv[2], "--message-format=json");
let features = argv
.iter()
.position(|a| a == "--features")
.map(|i| argv[i + 1].clone());
match (cfg!(feature = "wprof"), cfg!(feature = "integration")) {
(false, false) => assert!(
features.is_none(),
"a no-feature build must not pass --features (got {features:?})"
),
(want_wprof, want_integration) => {
let f = features.expect("a feature build must forward --features");
let set: Vec<&str> = f.split(',').collect();
assert_eq!(
set.contains(&"wprof"),
want_wprof,
"wprof forwarding (got {f:?})"
);
assert_eq!(
set.contains(&"integration"),
want_integration,
"integration forwarding (got {f:?})"
);
}
}
}
#[test]
fn argv_threads_package_and_release() {
let argv = build_test_binaries_argv(Some("scx-ktstr"), true);
assert!(
argv.windows(2)
.any(|w| w[0] == "--package" && w[1] == "scx-ktstr"),
"argv: {argv:?}"
);
assert!(argv.iter().any(|a| a == "--release"), "argv: {argv:?}");
}
}