diff --git a/apps/vectorcraft/src/gpu/tests.rs b/apps/vectorcraft/src/gpu/tests.rs index 906e35fb6..d13a1c417 100644 --- a/apps/vectorcraft/src/gpu/tests.rs +++ b/apps/vectorcraft/src/gpu/tests.rs @@ -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 { /// 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"", diff --git a/apps/vectorcraft/src/main.rs b/apps/vectorcraft/src/main.rs index c37aebe96..926f30b25 100644 --- a/apps/vectorcraft/src/main.rs +++ b/apps/vectorcraft/src/main.rs @@ -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; diff --git a/apps/vectorcraft/src/portal_save.rs b/apps/vectorcraft/src/portal_save.rs index 10bc2d8a8..b6dd3cc8e 100644 --- a/apps/vectorcraft/src/portal_save.rs +++ b/apps/vectorcraft/src/portal_save.rs @@ -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();