#![cfg(feature = "integration")]
#![cfg(unix)]
mod common;
use std::time::Duration;
use common::init_tracing;
use serial_test::serial;
use viewpoint_core::Browser;
fn count_zombie_chromium_processes() -> usize {
use std::process::Command;
let output = Command::new("ps")
.args(["-eo", "pid,ppid,stat,comm"])
.output();
match output {
Ok(output) => {
let stdout = String::from_utf8_lossy(&output.stdout);
let our_pid = std::process::id();
stdout
.lines()
.filter(|line| {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 4 {
let ppid = parts[1].parse::<u32>().unwrap_or(0);
let stat = parts[2];
let comm = parts[3];
ppid == our_pid
&& stat.starts_with('Z')
&& (comm.contains("chrom") || comm.contains("Chrom"))
} else {
false
}
})
.count()
}
Err(_) => 0,
}
}
#[tokio::test]
#[serial]
async fn test_no_zombie_after_close() {
init_tracing();
let zombies_before = count_zombie_chromium_processes();
let browser = Browser::launch()
.headless(true)
.timeout(Duration::from_secs(30))
.launch()
.await
.expect("Failed to launch browser");
browser.close().await.expect("Failed to close browser");
tokio::time::sleep(Duration::from_millis(200)).await;
let zombies_after = count_zombie_chromium_processes();
assert!(
zombies_after <= zombies_before,
"New zombie processes detected after close: before={zombies_before}, after={zombies_after}"
);
}
#[tokio::test]
#[serial]
async fn test_no_zombie_after_drop() {
init_tracing();
let zombies_before = count_zombie_chromium_processes();
{
let browser = Browser::launch()
.headless(true)
.timeout(Duration::from_secs(30))
.launch()
.await
.expect("Failed to launch browser");
assert!(browser.is_owned());
}
tokio::time::sleep(Duration::from_millis(200)).await;
let zombies_after = count_zombie_chromium_processes();
assert!(
zombies_after <= zombies_before,
"New zombie processes detected after drop: before={zombies_before}, after={zombies_after}"
);
}
#[tokio::test]
#[serial]
async fn test_no_zombie_when_process_dies_before_close() {
use nix::sys::signal::{Signal, kill};
use nix::unistd::Pid;
init_tracing();
let zombies_before = count_zombie_chromium_processes();
let browser = Browser::launch()
.headless(true)
.timeout(Duration::from_secs(30))
.launch()
.await
.expect("Failed to launch browser");
let our_pid = std::process::id();
let output = std::process::Command::new("ps")
.args(["-eo", "pid,ppid,comm"])
.output()
.expect("Failed to run ps");
let stdout = String::from_utf8_lossy(&output.stdout);
let chromium_pid: Option<i32> = stdout.lines().find_map(|line| {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 3 {
let pid = parts[0].parse::<i32>().ok()?;
let ppid = parts[1].parse::<u32>().ok()?;
let comm = parts[2];
if ppid == our_pid && (comm.contains("chrom") || comm.contains("Chrom")) {
return Some(pid);
}
}
None
});
if let Some(pid) = chromium_pid {
let _ = kill(Pid::from_raw(pid), Signal::SIGKILL);
tokio::time::sleep(Duration::from_millis(200)).await;
}
browser.close().await.expect("Failed to close browser");
tokio::time::sleep(Duration::from_millis(200)).await;
let zombies_after = count_zombie_chromium_processes();
assert!(
zombies_after <= zombies_before,
"New zombie processes detected after close of killed browser: before={zombies_before}, after={zombies_after}"
);
}