mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 20:27:05 -06:00
test(app): keep a child's start from holding the GPU start file's lock
The GPU start tests release the start file's lock and take it again. portal_save's mock_portal starts dbus-daemon meanwhile, and until a child runs its program it holds a copy of every open descriptor, so the lock outlived the test's own handle: the next start found the file locked, took it as another copy of the app running and left nothing out. Parallel runs of the app's tests failed 12 times in 20. A test-only RwLock: mock_portal holds it exclusively while it spawns, and each GPU start test holds it shared for the whole test. 0 failures in 70 parallel runs.
This commit is contained in:
@@ -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"",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user