From a44c9a2d0b4e093138717fd6238b302d9f024f13 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Mon, 26 Oct 2026 05:19:26 -0700 Subject: [PATCH] Tests: the --help tests wait for the app to exit on a loaded machine `apps/vectorcraft/tests/it/help.rs` gave the desktop binary 5 seconds to exit and otherwise reported that the app had started. While `cargo xtask ci` runs the workspace's tests in parallel, starting the binary can take longer than that, and both help tests failed although the binary printed its usage. The test now reads standard output and standard error to their end on threads and waits up to 120 seconds for them. It also sets `VECTORCRAFT_CONTROL_PORT` to a value the app rejects, so a binary that does not stop at `--help` exits with that error instead of opening a window, and the usage checks fail. --- apps/vectorcraft/tests/it/help.rs | 38 ++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/apps/vectorcraft/tests/it/help.rs b/apps/vectorcraft/tests/it/help.rs index 7c7624031..059b9c476 100644 --- a/apps/vectorcraft/tests/it/help.rs +++ b/apps/vectorcraft/tests/it/help.rs @@ -1,19 +1,22 @@ //! `vectorcraft --help` prints the usage and exits before the app starts (#2110): run from a //! terminal, SSH or an agent's shell it used to open the window (or hang without a display). +use std::io::Read; use std::process::{Command, Output, Stdio}; +use std::sync::mpsc; use std::time::{Duration, Instant}; const BIN: &str = env!("CARGO_BIN_EXE_vectorcraft"); -/// Runs the desktop binary without a display, so a regression fails instead of opening a window. +/// Runs the desktop binary without a display and with a control port it cannot parse, so a +/// regression exits with that error instead of opening a window. fn run(args: &[&str], env: &[(&str, &str)]) -> Output { let mut child = Command::new(BIN) .args(args) .env_remove("DISPLAY") .env_remove("WAYLAND_DISPLAY") .env_remove("WAYLAND_SOCKET") - .env_remove("VECTORCRAFT_CONTROL_PORT") + .env("VECTORCRAFT_CONTROL_PORT", "not-a-port") .env("VECTORCRAFT_NO_PREFS", "1") .envs(env.iter().copied()) .stdin(Stdio::null()) @@ -21,15 +24,28 @@ fn run(args: &[&str], env: &[(&str, &str)]) -> Output { .stderr(Stdio::piped()) .spawn() .unwrap(); - let deadline = Instant::now() + Duration::from_secs(5); - while child.try_wait().unwrap().is_none() { - if Instant::now() >= deadline { - child.kill().unwrap(); - panic!("{args:?}: the app started instead of printing its usage"); - } - std::thread::sleep(Duration::from_millis(10)); - } - child.wait_with_output().unwrap() + // Both pipes end when the binary exits, however long a loaded machine takes to start it. The + // deadline only stops a binary that is still running. + let deadline = Instant::now() + Duration::from_secs(120); + let stdout = read_to_end(child.stdout.take().unwrap()); + let stderr = read_to_end(child.stderr.take().unwrap()); + let recv = |rx: &mpsc::Receiver>| rx.recv_timeout(deadline.saturating_duration_since(Instant::now())); + let (Ok(stdout), Ok(stderr)) = (recv(&stdout), recv(&stderr)) else { + child.kill().unwrap(); + panic!("{args:?}: the app started instead of printing its usage"); + }; + Output { status: child.wait().unwrap(), stdout, stderr } +} + +/// Reads `pipe` to its end on another thread. +fn read_to_end(mut pipe: impl Read + Send + 'static) -> mpsc::Receiver> { + let (tx, rx) = mpsc::channel(); + std::thread::spawn(move || { + let mut buf = Vec::new(); + let _ = pipe.read_to_end(&mut buf); + let _ = tx.send(buf); + }); + rx } fn prints_usage(args: &[&str], env: &[(&str, &str)]) {