use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::sync::Once;
use std::time::{Duration, Instant};
fn examples_dir() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_mpiexec"))
.parent()
.unwrap()
.join("examples")
}
static BUILD: Once = Once::new();
fn ensure_examples_built() {
BUILD.call_once(|| {
let features = if cfg!(feature = "shm") {
"derive,shm"
} else {
"derive"
};
let status = Command::new(env!("CARGO"))
.args(["build", "--examples", "--features", features, "--quiet"])
.status()
.expect("failed to invoke cargo build");
assert!(status.success(), "cargo build --examples failed");
});
}
fn run_with_timeout(cmd: &mut Command, timeout: Duration) -> (bool, String) {
let mut child = cmd
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("failed to spawn");
let start = Instant::now();
loop {
if let Some(status) = child.try_wait().expect("try_wait failed") {
let out = child.wait_with_output().expect("wait_with_output");
let mut s = String::from_utf8_lossy(&out.stdout).into_owned();
s.push_str(&String::from_utf8_lossy(&out.stderr));
return (status.success(), s);
}
if start.elapsed() > timeout {
let _ = child.kill();
let _ = child.wait();
return (false, "TIMEOUT".to_string());
}
std::thread::sleep(Duration::from_millis(20));
}
}
fn run_example(example: &str, nprocs: usize, expect: &str) {
ensure_examples_built();
let bin = examples_dir().join(example);
assert!(bin.exists(), "example {example} not found at {bin:?}");
let mut cmd = Command::new(env!("CARGO_BIN_EXE_mpiexec"));
cmd.arg("-n").arg(nprocs.to_string()).arg(&bin);
let (ok, output) = run_with_timeout(&mut cmd, Duration::from_secs(30));
assert!(ok, "{example} n={nprocs} failed. Output:\n{output}");
assert!(
output.contains(expect),
"{example} n={nprocs} missing {expect:?}. Output:\n{output}"
);
}
#[test]
fn selftest_1() {
run_example("selftest", 1, "SELFTEST PASS");
}
#[test]
fn selftest_2() {
run_example("selftest", 2, "SELFTEST PASS");
}
#[test]
fn selftest_4() {
run_example("selftest", 4, "SELFTEST PASS");
}
#[test]
fn selftest_7() {
run_example("selftest", 7, "SELFTEST PASS");
}
#[test]
fn rma_2() {
run_example("rma", 2, "RMA PASS");
}
#[test]
fn rma_4() {
run_example("rma", 4, "RMA PASS");
}
#[test]
fn parallel_io_2() {
run_example("parallel_io", 2, "IO PASS");
}
#[test]
fn parallel_io_5() {
run_example("parallel_io", 5, "IO PASS");
}
#[test]
fn topo_inter_3() {
run_example("topo_inter", 3, "TOPO/INTER PASS");
}
#[test]
fn topo_inter_6() {
run_example("topo_inter", 6, "TOPO/INTER PASS");
}
#[test]
fn derive_2() {
run_example("derive", 2, "derive(Equivalence) round-trip OK");
}
#[test]
fn abort_does_not_hang() {
ensure_examples_built();
let bin = examples_dir().join("abort");
let mut cmd = Command::new(env!("CARGO_BIN_EXE_mpiexec"));
cmd.arg("-n").arg("2").arg(&bin);
let (ok, output) = run_with_timeout(&mut cmd, Duration::from_secs(15));
assert!(
!ok,
"abort example should exit non-zero, got success:\n{output}"
);
assert!(
!output.contains("TIMEOUT"),
"abort example hung (a blocked peer was not torn down)"
);
}
#[test]
fn spawn_singleton() {
run_example("spawn", 1, "SPAWN PASS");
}
#[test]
fn spawn_2() {
run_example("spawn", 2, "SPAWN PASS");
}
#[test]
fn thread_multiple_1() {
run_example("thread_multiple", 1, "THREAD PASS");
}
#[test]
fn thread_multiple_4() {
run_example("thread_multiple", 4, "THREAD PASS");
}
#[test]
fn bigmsg_2() {
run_example("bigmsg", 2, "BIGMSG PASS");
}
#[test]
fn bigmsg_4() {
run_example("bigmsg", 4, "BIGMSG PASS");
}