a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
158 lines
5.2 KiB
Rust
158 lines
5.2 KiB
Rust
use super::*;
|
|
|
|
#[test]
|
|
fn desired_nofile_soft_limit_only_raises_when_possible() {
|
|
assert_eq!(desired_nofile_soft_limit(1024, 524_288, 8192), Some(8192));
|
|
assert_eq!(desired_nofile_soft_limit(8192, 524_288, 8192), None);
|
|
assert_eq!(desired_nofile_soft_limit(1024, 4096, 8192), Some(4096));
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
fn spawn_detached_creates_new_session() {
|
|
use tempfile::NamedTempFile;
|
|
|
|
let output = NamedTempFile::new().expect("temp file");
|
|
let output_path = output.path().to_string_lossy().to_string();
|
|
let parent_sid = unsafe { libc::getsid(0) };
|
|
|
|
let mut cmd = std::process::Command::new("sh");
|
|
cmd.arg("-c")
|
|
.arg("ps -o sid= -p $$ > \"$JCODE_TEST_OUTPUT\"")
|
|
.env("JCODE_TEST_OUTPUT", &output_path)
|
|
.stdout(std::process::Stdio::null())
|
|
.stderr(std::process::Stdio::null());
|
|
|
|
let mut child = super::spawn_detached(&mut cmd).expect("spawn detached child");
|
|
let status = child.wait().expect("wait for child");
|
|
assert!(status.success(), "child should exit successfully");
|
|
|
|
let child_sid = std::fs::read_to_string(&output_path)
|
|
.expect("read child sid")
|
|
.trim()
|
|
.parse::<u32>()
|
|
.expect("parse child sid");
|
|
|
|
assert_eq!(
|
|
child_sid,
|
|
child.id(),
|
|
"detached child should lead its own session"
|
|
);
|
|
assert_ne!(
|
|
child_sid as i32, parent_sid,
|
|
"detached child should not share parent session"
|
|
);
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[test]
|
|
fn is_process_running_reports_exited_children_as_stopped() {
|
|
use std::process::{Command, Stdio};
|
|
use std::time::Duration;
|
|
|
|
let mut cmd = Command::new("cmd.exe");
|
|
cmd.args(["/C", "ping -n 3 127.0.0.1 >NUL"])
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null());
|
|
|
|
let mut child = cmd.spawn().expect("spawn child");
|
|
let pid = child.id();
|
|
assert!(
|
|
super::is_process_running(pid),
|
|
"child should initially be running"
|
|
);
|
|
|
|
let status = child.wait().expect("wait for child");
|
|
assert!(status.success(), "child should exit successfully");
|
|
std::thread::sleep(Duration::from_millis(100));
|
|
|
|
assert!(
|
|
!super::is_process_running(pid),
|
|
"exited child should not be reported as running"
|
|
);
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[test]
|
|
fn signal_detached_process_group_terminates_descendant_tree() {
|
|
use std::process::{Command, Stdio};
|
|
use std::time::{Duration, Instant};
|
|
|
|
let temp = tempfile::tempdir().expect("temp dir");
|
|
let ready_path = temp.path().join("child-ready.txt");
|
|
let survived_path = temp.path().join("child-survived.txt");
|
|
let child_script_path = temp.path().join("child.cmd");
|
|
let parent_script_path = temp.path().join("parent.cmd");
|
|
let child_script = concat!(
|
|
"@echo off\r\n",
|
|
"echo ready>\"%~dp0child-ready.txt\"\r\n",
|
|
"ping -n 6 127.0.0.1 >NUL\r\n",
|
|
"echo survived>\"%~dp0child-survived.txt\"\r\n"
|
|
);
|
|
let parent_script = concat!(
|
|
"@echo off\r\n",
|
|
"start \"\" /B cmd.exe /D /C \"\"%~dp0child.cmd\"\"\r\n",
|
|
"ping -n 30 127.0.0.1 >NUL\r\n"
|
|
);
|
|
std::fs::write(&child_script_path, child_script).expect("write child command script");
|
|
std::fs::write(&parent_script_path, parent_script).expect("write parent command script");
|
|
let mut cmd = Command::new("cmd.exe");
|
|
cmd.args(["/D", "/C"])
|
|
.arg(&parent_script_path)
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null());
|
|
|
|
let mut parent = super::spawn_detached(&mut cmd).expect("spawn detached process tree");
|
|
let parent_pid = parent.id();
|
|
let deadline = Instant::now() + Duration::from_secs(10);
|
|
while !ready_path.exists() && Instant::now() < deadline {
|
|
std::thread::sleep(Duration::from_millis(50));
|
|
}
|
|
assert!(ready_path.exists(), "descendant should report ready");
|
|
assert!(super::is_process_running(parent_pid));
|
|
|
|
super::signal_detached_process_group(parent_pid, 0).expect("terminate process tree");
|
|
let deadline = Instant::now() + Duration::from_secs(10);
|
|
while super::is_process_running(parent_pid) && Instant::now() < deadline {
|
|
std::thread::sleep(Duration::from_millis(50));
|
|
}
|
|
let _ = parent.wait();
|
|
|
|
assert!(!super::is_process_running(parent_pid), "parent should stop");
|
|
std::thread::sleep(Duration::from_secs(6));
|
|
assert!(
|
|
!survived_path.exists(),
|
|
"descendant should not survive termination of the detached process tree"
|
|
);
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[test]
|
|
fn spawn_replacement_process_returns_without_waiting_for_child_exit() {
|
|
use std::process::{Command, Stdio};
|
|
use std::time::{Duration, Instant};
|
|
|
|
let mut cmd = Command::new("cmd.exe");
|
|
cmd.args(["/C", "ping -n 4 127.0.0.1 >NUL"])
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null());
|
|
|
|
let start = Instant::now();
|
|
let mut child = super::spawn_replacement_process(&mut cmd)
|
|
.expect("spawn replacement process should succeed");
|
|
let elapsed = start.elapsed();
|
|
|
|
assert!(
|
|
elapsed < Duration::from_secs(1),
|
|
"replacement spawn should not block, took {:?}",
|
|
elapsed
|
|
);
|
|
assert!(
|
|
child.try_wait().expect("poll child status").is_none(),
|
|
"replacement child should still be running immediately after spawn"
|
|
);
|
|
|
|
child.kill().ok();
|
|
let _ = child.wait();
|
|
}
|