makepad/studio/hub/tests/build_manager_test.rs
2026-04-27 14:51:24 +02:00

162 lines
4.4 KiB
Rust

use makepad_studio_hub::build_manager::BuildManager;
use makepad_studio_hub::HubEvent;
use makepad_studio_protocol::hub_protocol::{ClientId, QueryId};
use std::collections::HashMap;
use std::fs;
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, Instant};
#[test]
fn build_manager_emits_output_and_exit_for_cargo() {
let (tx, rx) = mpsc::channel::<HubEvent>();
let mut manager = BuildManager::default();
let tmp = makepad_studio_hub::test_support::tempdir().unwrap();
let build_id = QueryId::new(ClientId(7), 1);
manager
.start_cargo_run(
build_id,
"repo".to_string(),
tmp.path(),
vec!["--version".to_string()],
HashMap::new(),
None,
tx,
)
.expect("start cargo");
assert_eq!(manager.list_builds().len(), 1);
let deadline = Instant::now() + Duration::from_secs(10);
let mut saw_output = false;
let mut saw_exit = false;
while Instant::now() < deadline {
let Ok(event) = rx.recv_timeout(Duration::from_millis(100)) else {
continue;
};
match event {
HubEvent::ProcessOutput {
build_id: id, line, ..
} if id == build_id => {
if line.contains("cargo") {
saw_output = true;
}
}
HubEvent::ProcessExited {
build_id: id,
exit_code,
} if id == build_id => {
assert_eq!(exit_code, Some(0));
manager.mark_exited(id, exit_code);
saw_exit = true;
break;
}
_ => {}
}
}
assert!(saw_output, "did not observe cargo output");
assert!(saw_exit, "did not observe process exit");
assert!(manager.list_builds().is_empty());
}
#[cfg(unix)]
#[test]
fn build_manager_stop_build_kills_process_group() {
let (tx, rx) = mpsc::channel::<HubEvent>();
let mut manager = BuildManager::default();
let tmp = makepad_studio_hub::test_support::tempdir().unwrap();
let build_id = QueryId::new(ClientId(7), 2);
manager
.start_command_run(
build_id,
"repo".to_string(),
"group-test".to_string(),
tmp.path(),
"/bin/sh".to_string(),
vec![
"-c".to_string(),
"sleep 30 & echo $! > child.pid; wait".to_string(),
],
HashMap::new(),
false,
None,
tx,
)
.expect("start shell");
let pid_path = tmp.path().join("child.pid");
let deadline = Instant::now() + Duration::from_secs(5);
let child_pid = loop {
if let Ok(pid) = fs::read_to_string(&pid_path) {
break pid.trim().parse::<i32>().expect("parse child pid");
}
assert!(Instant::now() < deadline, "child pid file was not created");
thread::sleep(Duration::from_millis(20));
};
assert!(pid_exists(child_pid), "background child did not start");
manager.stop_build(build_id).expect("stop build");
let deadline = Instant::now() + Duration::from_secs(5);
let mut saw_exit = false;
while Instant::now() < deadline {
let Ok(event) = rx.recv_timeout(Duration::from_millis(100)) else {
continue;
};
if let HubEvent::ProcessExited {
build_id: id,
exit_code,
} = event
{
if id == build_id {
manager.mark_exited(id, exit_code);
saw_exit = true;
break;
}
}
}
assert!(saw_exit, "did not observe process exit after stop");
let deadline = Instant::now() + Duration::from_secs(2);
while Instant::now() < deadline {
if !pid_exists(child_pid) {
break;
}
thread::sleep(Duration::from_millis(20));
}
if pid_exists(child_pid) {
kill_pid(child_pid, 9);
}
assert!(
!pid_exists(child_pid),
"background child survived stop_build"
);
}
#[cfg(unix)]
fn pid_exists(pid: i32) -> bool {
unsafe {
if kill(pid, 0) == 0 {
return true;
}
}
std::io::Error::last_os_error().raw_os_error() != Some(3)
}
#[cfg(unix)]
fn kill_pid(pid: i32, sig: i32) {
unsafe {
let _ = kill(pid, sig);
}
}
#[cfg(unix)]
unsafe extern "C" {
fn kill(pid: i32, sig: i32) -> i32;
}