Merge pull request #4520 from mockydevs/fix/gpu-start-tests-spawn-race

test(app): keep a child's start from holding the GPU start file's lock
This commit is contained in:
BFlat
2026-10-29 13:46:29 +07:00
committed by GitHub
3 changed files with 39 additions and 11 deletions
+11 -8
View File
@@ -548,11 +548,14 @@ fn a_normal_exit_or_another_failure_does_not_restart() {
assert_eq!(finish(Err("a bug".into()), &startup), Err("a bug".into()));
}
/// A log folder of its own for each test.
fn start_dir(tag: &str) -> std::path::PathBuf {
/// A log folder of its own for each test, and the guard that keeps other tests from starting a
/// program while it runs: the test releases the start file's lock and takes it again, and a child
/// starting meanwhile would keep it held (`crate::no_spawns`).
fn start_dir(tag: &str) -> (std::path::PathBuf, std::sync::RwLockReadGuard<'static, ()>) {
let spawns = crate::no_spawns();
let d = std::env::temp_dir().join(format!("vectorcraft-gpu-start-{tag}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&d);
d
(d, spawns)
}
/// What a start in `dir` leaves out; it then ends without crashing.
@@ -578,7 +581,7 @@ fn issue_2243() -> Vec<Candidate> {
/// it, without a status-bar note.
#[test]
fn a_start_that_never_showed_a_frame_leaves_its_adapter_out_of_the_next() {
let dir = start_dir("crash");
let (dir, _spawns) = start_dir("crash");
let c = issue_2243();
let first = Startup::default();
assert_eq!(first.track(&dir, None, false), LeftOut::default());
@@ -609,7 +612,7 @@ fn a_start_that_never_showed_a_frame_leaves_its_adapter_out_of_the_next() {
/// an error, a restart on another adapter), leaves nothing out of the next.
#[test]
fn a_start_that_showed_its_first_frame_or_ended_is_cleared() {
let dir = start_dir("shown");
let (dir, _spawns) = start_dir("shown");
let c = issue_2243();
for end in [Startup::presented as fn(&Startup), Startup::ended] {
let first = Startup::default();
@@ -633,7 +636,7 @@ fn a_start_that_showed_its_first_frame_or_ended_is_cleared() {
/// macOS, where wgpu has none.
#[test]
fn a_crash_while_listing_lists_opengl_alone() {
let dir = start_dir("listing");
let (dir, _spawns) = start_dir("listing");
let first = Startup::default();
first.track(&dir, None, false);
drop(first);
@@ -660,7 +663,7 @@ fn a_crash_while_listing_lists_opengl_alone() {
/// start shows its first frame on the adapter that crashed, the adapter counts again.
#[test]
fn wgpu_backend_or_adapter_name_ignores_the_crashes() {
let dir = start_dir("explicit");
let (dir, _spawns) = start_dir("explicit");
let c = issue_2243();
let first = Startup::default();
first.track(&dir, None, false);
@@ -681,7 +684,7 @@ fn wgpu_backend_or_adapter_name_ignores_the_crashes() {
/// are ignored: the start goes on with nothing left out.
#[test]
fn a_damaged_or_unreadable_start_file_is_ignored() {
let dir = start_dir("damaged");
let (dir, _spawns) = start_dir("damaged");
std::fs::create_dir_all(&dir).unwrap();
let damaged: [&[u8]; 9] = [
b"",
+23
View File
@@ -483,5 +483,28 @@ fn main() -> std::process::ExitCode {
}
}
/// Tests only: while a test starts another program, the child briefly holds a copy of every file
/// this process has open, until it runs that program. A file lock held through such a copy outlives
/// its own handle, so a GPU start test that released the start file and opened it again found it
/// still locked, as if another copy of the app were running, and failed. A test that starts a
/// program holds [`spawning`]; a test that depends on the start file's lock holds [`no_spawns`].
#[cfg(all(
test,
any(feature = "wgpu", any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))
))]
static CHILD_SPAWN: std::sync::RwLock<()> = std::sync::RwLock::new(());
/// Held while a test starts another program (see [`CHILD_SPAWN`]).
#[cfg(all(test, any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd")))]
fn spawning() -> std::sync::RwLockWriteGuard<'static, ()> {
CHILD_SPAWN.write().unwrap_or_else(std::sync::PoisonError::into_inner)
}
/// Held by a test that releases a locked file and opens it again (see [`CHILD_SPAWN`]).
#[cfg(all(test, feature = "wgpu"))]
fn no_spawns() -> std::sync::RwLockReadGuard<'static, ()> {
CHILD_SPAWN.read().unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[cfg(all(test, feature = "wgpu"))]
mod tests;
+5 -3
View File
@@ -450,11 +450,13 @@ mod tests {
use std::io::BufRead as _;
use zbus::zvariant::OwnedObjectPath;
let mut daemon = std::process::Command::new("dbus-daemon")
let spawning = crate::spawning();
let daemon = std::process::Command::new("dbus-daemon")
.args(["--session", "--nofork", "--print-address=1"])
.stdout(std::process::Stdio::piped())
.spawn()
.ok()?;
.spawn();
drop(spawning);
let mut daemon = daemon.ok()?;
let mut address = String::new();
std::io::BufReader::new(daemon.stdout.take()?).read_line(&mut address).ok()?;
let address = address.trim().to_string();