package mixer import ( "testing" "time" "rhemawavetech.com/rhemawave/internal/db" ) // Regression tests for critical audio bugs (v0.5) // Regression: Stop() used to return early when AudioEnabled was false, // leaving music playing after the user toggled audio off mid-playback. func TestStopCleansUpWhenAudioDisabled(t *testing.T) { mu.Lock() wasInited := inited inited = true deviceOpen = false currentPath = "/some/track.wav" currentLoops = -1 mu.Unlock() defer func() { mu.Lock() inited = wasInited currentPath = "" currentLoops = 0 mu.Unlock() }() cfg := &db.AppConfig{AudioEnabled: false} Stop(0, cfg) mu.Lock() defer mu.Unlock() if currentPath != "" { t.Errorf("currentPath = %q, want empty after Stop with AudioEnabled=false", currentPath) } if currentLoops != 0 { t.Errorf("currentLoops = %d, want 0 after Stop with AudioEnabled=false", currentLoops) } } // Regression: SwitchDevice used to convert oldDevice="" to "system default" // and pass that string to openDeviceLocked, which SDL does not recognize. func TestSwitchDeviceReturnsErrorWhenUninitialized(t *testing.T) { mu.Lock() wasInited := inited inited = false deviceOpen = false mu.Unlock() defer func() { mu.Lock() inited = wasInited mu.Unlock() }() cfg := &db.AppConfig{AudioEnabled: true, SavedVolume: 0.5} err := SwitchDevice("Some Device", cfg) if err == nil { t.Fatal("SwitchDevice on uninitialized mixer should return error") } } // Regression: SwitchDevice with nil config must not panic when attempting // to restore volume after reopening the device. func TestSwitchDeviceNilConfigDoesNotPanic(t *testing.T) { mu.Lock() wasInited := inited inited = false deviceOpen = false mu.Unlock() defer func() { mu.Lock() inited = wasInited mu.Unlock() }() err := SwitchDevice("Some Device", nil) if err == nil { t.Fatal("SwitchDevice on uninitialized mixer should return error") } } // Regression: SwitchDevice with empty device name (system default) should // not be converted to "system default" before passing to openDeviceLocked. func TestSwitchDeviceAcceptsEmptyDeviceName(t *testing.T) { mu.Lock() wasInited := inited inited = false deviceOpen = false mu.Unlock() defer func() { mu.Lock() inited = wasInited mu.Unlock() }() cfg := &db.AppConfig{SavedVolume: 0.75} err := SwitchDevice("", cfg) // Returns error because mixer is not initialized, but should not panic // and should handle the empty name gracefully. if err == nil { t.Fatal("SwitchDevice on uninitialized mixer should return error") } } // TestPlayDoesNotDeadlockWhenUninitialized verifies that Play() returns an error // (not a deadlock) when the mixer has not been initialized. // The old code would deadlock because Play() held mu then called SwitchDevice() // which also tried to acquire mu. func TestPlayDoesNotDeadlockWhenUninitialized(t *testing.T) { // Ensure mixer is in uninitialized state mu.Lock() wasInited := inited inited = false deviceOpen = false mu.Unlock() defer func() { mu.Lock() inited = wasInited mu.Unlock() }() cfg := &db.AppConfig{AudioEnabled: true} done := make(chan error, 1) go func() { done <- Play("nonexistent.wav", false, cfg) }() select { case err := <-done: if err == nil { t.Fatal("Play() returned nil error on uninitialized mixer, want error") } case <-time.After(5 * time.Second): t.Fatal("Play() deadlocked on uninitialized mixer (did not return within 5s)") } }