115 lines
4.5 KiB
Rust
115 lines
4.5 KiB
Rust
use super::{TemporaryProject, process};
|
|
use std::fs;
|
|
use std::process::Command;
|
|
use std::time::{Duration, Instant};
|
|
|
|
const DESCENDANTS: &str = r#"
|
|
/bin/sh -c 'echo $$ > descendant; /bin/sleep 30 & echo $! > grandchild; wait' &
|
|
while [ ! -s grandchild ]; do :; done
|
|
"#;
|
|
|
|
// subreaper 是进程级状态;此单例测试顺序覆盖所有真实进程场景。
|
|
#[test]
|
|
fn case_process_captures_output_times_out_and_reaps_only_owned_descendants() {
|
|
let original_reaper = rustix::process::child_subreaper().unwrap();
|
|
let mut unrelated = Command::new("/bin/sleep").arg("30").spawn().unwrap();
|
|
let result = std::panic::catch_unwind(|| {
|
|
for (body, descendants, expected) in [
|
|
("printf hidden; printf hidden-error >&2; exit 0", false, None),
|
|
(
|
|
"printf output; printf diagnostic >&2; exit 7",
|
|
false,
|
|
Some("exit status: 7"),
|
|
),
|
|
(
|
|
"printf before-timeout; /bin/sleep 30",
|
|
false,
|
|
Some("timed out after 100 ms"),
|
|
),
|
|
("exit 0", true, None),
|
|
("printf owned-error >&2; exit 7", true, Some("exit status: 7")),
|
|
("printf started; wait", true, Some("timed out after 100 ms")),
|
|
] {
|
|
let root = TemporaryProject::new().unwrap();
|
|
let started = Instant::now();
|
|
let script = if descendants {
|
|
format!("{DESCENDANTS}\n{body}")
|
|
} else {
|
|
body.to_owned()
|
|
};
|
|
let result = process::run(
|
|
Command::new("/bin/sh")
|
|
.arg("-c")
|
|
.arg(&script)
|
|
.env_clear()
|
|
.current_dir(root.path()),
|
|
root.path(),
|
|
Duration::from_millis(100),
|
|
);
|
|
assert!(started.elapsed() < Duration::from_secs(5));
|
|
match expected {
|
|
None => result.unwrap(),
|
|
Some(message) => {
|
|
let failure = result.unwrap_err();
|
|
assert!(failure.message.contains(message), "{failure:?}");
|
|
if script.contains("printf output") {
|
|
assert_eq!(failure.stdout, b"output");
|
|
assert_eq!(failure.stderr, b"diagnostic");
|
|
}
|
|
if script.contains("printf before-timeout") {
|
|
assert_eq!(failure.stdout, b"before-timeout");
|
|
}
|
|
if script.contains("printf owned-error") {
|
|
assert_eq!(failure.stderr, b"owned-error");
|
|
}
|
|
}
|
|
}
|
|
for name in ["descendant", "grandchild"] {
|
|
let path = root.path().join(name);
|
|
if descendants {
|
|
let pid = fs::read_to_string(path)
|
|
.unwrap()
|
|
.trim()
|
|
.parse::<u32>()
|
|
.unwrap();
|
|
assert!(
|
|
!std::path::Path::new(&format!("/proc/{pid}")).exists(),
|
|
"unreaped {name} {pid}"
|
|
);
|
|
}
|
|
}
|
|
assert_eq!(rustix::process::child_subreaper().unwrap(), original_reaper);
|
|
}
|
|
|
|
let root = TemporaryProject::new().unwrap();
|
|
let failure = process::run(
|
|
Command::new("/bin/sh")
|
|
.arg("-c")
|
|
.arg("/bin/dd if=/dev/zero bs=131072 count=2 2>/dev/null; printf end >&2; exit 9")
|
|
.env_clear(),
|
|
root.path(),
|
|
Duration::from_secs(5),
|
|
)
|
|
.unwrap_err();
|
|
assert!(failure.message.contains("exit status: 9"));
|
|
assert_eq!(failure.stdout.len(), 262_144);
|
|
assert_eq!(failure.stderr, b"end");
|
|
|
|
let root = TemporaryProject::new().unwrap();
|
|
let failure = process::run(
|
|
&mut Command::new(root.path().join("missing-executable")),
|
|
root.path(),
|
|
Duration::from_millis(100),
|
|
)
|
|
.unwrap_err();
|
|
assert!(failure.message.contains("cannot run test process"));
|
|
assert_eq!(rustix::process::child_subreaper().unwrap(), original_reaper);
|
|
});
|
|
// 即使断言失败也回收此对照进程,不能让测试自身遗留资源。
|
|
let unrelated_running = unrelated.try_wait().unwrap().is_none();
|
|
let _ = unrelated.kill();
|
|
unrelated.wait().unwrap();
|
|
result.unwrap();
|
|
assert!(unrelated_running, "case cleanup affected an unrelated child");
|
|
}
|