From 20a9111c899e3b8df664f1f719ba7f513330c2d7 Mon Sep 17 00:00:00 2001 From: AI Agent Bot Date: Sat, 21 Feb 2026 11:00:47 -0600 Subject: [PATCH 1/2] fix: resolve UE5 audio test race condition and unused variable warning The `unload_track_fires_callback` test was flaky due to shared global static atomics (`LAST_EVENT`, `LAST_TRACK`, `LAST_VALUE`) being raced by parallel test threads. One test's `reset_globals()` could clobber another test's callback values mid-assertion. Fix: add a `Mutex` to serialize all tests that use the shared callback globals via `init_backend()`. The guard is held for the test's lifetime, preventing interleaving. Also prefix unused `port` variable with `_`. Co-Authored-By: Claude Opus 4.6 --- crates/oasis-app/src/commands.rs | 2 +- crates/oasis-backend-ue5/src/audio.rs | 42 +++++++++++++++------------ 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/crates/oasis-app/src/commands.rs b/crates/oasis-app/src/commands.rs index f902d06..98f7216 100644 --- a/crates/oasis-app/src/commands.rs +++ b/crates/oasis-app/src/commands.rs @@ -596,7 +596,7 @@ mod tests { fn process_listen_already_running() { let mut state = make_test_state(); // Start a listener first. - let port = 0; // Cannot actually bind, but simulate the state. + let _port = 0; // Cannot actually bind, but simulate the state. let cfg = oasis_core::net::ListenerConfig { port: 19999, psk: String::new(), diff --git a/crates/oasis-backend-ue5/src/audio.rs b/crates/oasis-backend-ue5/src/audio.rs index 03db011..0a23b73 100644 --- a/crates/oasis-backend-ue5/src/audio.rs +++ b/crates/oasis-backend-ue5/src/audio.rs @@ -151,6 +151,11 @@ impl AudioBackend for Ue5AudioBackend { mod tests { use super::*; use std::sync::atomic::{AtomicU32, Ordering}; + use std::sync::{Mutex, MutexGuard}; + + /// Serializes tests that use the shared global callback atomics. + /// Without this, parallel tests race on `LAST_EVENT`/`LAST_TRACK`/`LAST_VALUE`. + static TEST_LOCK: Mutex<()> = Mutex::new(()); static LAST_EVENT: AtomicU32 = AtomicU32::new(u32::MAX); static LAST_TRACK: AtomicU32 = AtomicU32::new(u32::MAX); @@ -168,12 +173,13 @@ mod tests { LAST_VALUE.store(u32::MAX, Ordering::SeqCst); } - fn init_backend() -> Ue5AudioBackend { + fn init_backend() -> (MutexGuard<'static, ()>, Ue5AudioBackend) { + let guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner()); let mut b = Ue5AudioBackend::new(); b.init().unwrap(); b.set_callback(test_cb); reset_globals(); - b + (guard, b) } #[test] @@ -187,7 +193,7 @@ mod tests { #[test] fn play_fires_callback() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); let track = b.load_track(b"data").unwrap(); reset_globals(); b.play(track).unwrap(); @@ -197,7 +203,7 @@ mod tests { #[test] fn pause_fires_callback() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); let track = b.load_track(b"data").unwrap(); b.play(track).unwrap(); reset_globals(); @@ -207,7 +213,7 @@ mod tests { #[test] fn resume_fires_callback() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); let track = b.load_track(b"data").unwrap(); b.play(track).unwrap(); b.pause().unwrap(); @@ -218,7 +224,7 @@ mod tests { #[test] fn stop_fires_callback() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); let track = b.load_track(b"data").unwrap(); b.play(track).unwrap(); reset_globals(); @@ -228,7 +234,7 @@ mod tests { #[test] fn volume_fires_callback() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); b.set_volume(42).unwrap(); assert_eq!( LAST_EVENT.load(Ordering::SeqCst), @@ -240,7 +246,7 @@ mod tests { #[test] fn load_track_fires_callback() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); let data = b"fake audio"; let track = b.load_track(data).unwrap(); assert_eq!( @@ -253,7 +259,7 @@ mod tests { #[test] fn unload_track_fires_callback() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); let track = b.load_track(b"data").unwrap(); reset_globals(); b.unload_track(track).unwrap(); @@ -266,7 +272,7 @@ mod tests { #[test] fn shutdown_fires_callback() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); b.shutdown().unwrap(); assert_eq!( LAST_EVENT.load(Ordering::SeqCst), @@ -290,20 +296,20 @@ mod tests { #[test] fn streaming_works() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); let track = b.load_streaming().unwrap(); b.feed_data(track, b"streaming chunk").unwrap(); } #[test] fn play_missing_track_fails() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); assert!(b.play(AudioTrackId(999)).is_err()); } #[test] fn lifecycle() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); let t = b.load_track(b"mp3 data").unwrap(); b.play(t).unwrap(); assert!(b.is_playing()); @@ -338,14 +344,14 @@ mod tests { #[test] fn stop_without_play_succeeds() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); // Stopping when nothing is playing should succeed gracefully. assert!(b.stop().is_ok()); } #[test] fn position_always_zero() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); assert_eq!(b.position_ms(), 0); let t = b.load_track(b"data").unwrap(); b.play(t).unwrap(); @@ -354,7 +360,7 @@ mod tests { #[test] fn duration_always_zero() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); assert_eq!(b.duration_ms(), 0); let t = b.load_track(b"data").unwrap(); b.play(t).unwrap(); @@ -363,7 +369,7 @@ mod tests { #[test] fn multiple_tracks_load_and_unload() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); let t1 = b.load_track(b"track1").unwrap(); let t2 = b.load_track(b"track2").unwrap(); let t3 = b.load_track(b"track3").unwrap(); @@ -379,7 +385,7 @@ mod tests { #[test] fn streaming_feed_data_succeeds() { - let mut b = init_backend(); + let (_guard, mut b) = init_backend(); let t = b.load_streaming().unwrap(); assert!(b.feed_data(t, b"chunk 1").is_ok()); assert!(b.feed_data(t, b"chunk 2").is_ok()); From d74c6054c7a7e7e5bef054f4dbfdeba0d1d30830 Mon Sep 17 00:00:00 2001 From: AI Agent Bot Date: Sat, 21 Feb 2026 11:36:32 -0600 Subject: [PATCH 2/2] fix: remove dead variable per Codex review suggestion Remove the entirely unused `let _port = 0;` binding rather than just suppressing the warning with an underscore prefix. The listener config constructs its own port value independently. Co-Authored-By: Claude Opus 4.6 --- crates/oasis-app/src/commands.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/oasis-app/src/commands.rs b/crates/oasis-app/src/commands.rs index 98f7216..c4b30b0 100644 --- a/crates/oasis-app/src/commands.rs +++ b/crates/oasis-app/src/commands.rs @@ -596,7 +596,6 @@ mod tests { fn process_listen_already_running() { let mut state = make_test_state(); // Start a listener first. - let _port = 0; // Cannot actually bind, but simulate the state. let cfg = oasis_core::net::ListenerConfig { port: 19999, psk: String::new(),