266 lines
5.7 KiB
Go
266 lines
5.7 KiB
Go
package player
|
|
|
|
import (
|
|
"math"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
// newTestPlayer returns a Player with only the atomic accessors wired up.
|
|
// We deliberately skip speaker.Init (which would open a real audio device)
|
|
// by constructing the struct directly — this limits us to testing the
|
|
// lock-free getters/setters, not the audio pipeline itself.
|
|
func newTestPlayer() *Player {
|
|
p := &Player{}
|
|
p.volMin.Store(math.Float64bits(-50))
|
|
p.speed.Store(math.Float64bits(1.0))
|
|
return p
|
|
}
|
|
|
|
func TestSetVolumeClamps(t *testing.T) {
|
|
p := newTestPlayer()
|
|
|
|
tests := []struct {
|
|
in float64
|
|
want float64
|
|
}{
|
|
{-60, -50}, // below min
|
|
{-50, -50},
|
|
{0, 0},
|
|
{6, 6},
|
|
{12, 6}, // above max
|
|
}
|
|
for _, tt := range tests {
|
|
p.SetVolume(tt.in)
|
|
if got := p.Volume(); got != tt.want {
|
|
t.Errorf("SetVolume(%v) → Volume() = %v, want %v", tt.in, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSetVolumeMinClamps(t *testing.T) {
|
|
p := newTestPlayer()
|
|
|
|
tests := []struct {
|
|
in float64
|
|
want float64
|
|
}{
|
|
{-100, -90}, // below absolute floor
|
|
{-90, -90},
|
|
{-50, -50},
|
|
{0, 0},
|
|
{5, 0}, // above max (must be ≤ 0)
|
|
}
|
|
for _, tt := range tests {
|
|
p.SetVolumeMin(tt.in)
|
|
if got := p.VolumeMin(); got != tt.want {
|
|
t.Errorf("SetVolumeMin(%v) → VolumeMin() = %v, want %v", tt.in, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSetVolumeMinReconcilesCurrent(t *testing.T) {
|
|
p := newTestPlayer()
|
|
p.SetVolume(-40)
|
|
p.SetVolumeMin(-30) // raise floor above current volume
|
|
if got := p.Volume(); got != -30 {
|
|
t.Errorf("Volume after SetVolumeMin raise = %v, want -30", got)
|
|
}
|
|
}
|
|
|
|
func TestVolumeClampedToCustomMin(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
volumeMin float64
|
|
volume float64
|
|
want float64
|
|
}{
|
|
{"below floor", -30, -40, -30},
|
|
{"at floor", -30, -30, -30},
|
|
{"above floor", -30, -10, -10},
|
|
{"custom floor -60", -60, -70, -60},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
p := newTestPlayer()
|
|
p.SetVolumeMin(tt.volumeMin)
|
|
p.SetVolume(tt.volume)
|
|
if got := p.Volume(); got != tt.want {
|
|
t.Errorf("Volume = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetSpeedClamps(t *testing.T) {
|
|
p := newTestPlayer()
|
|
|
|
tests := []struct {
|
|
in float64
|
|
want float64
|
|
}{
|
|
{0.1, 0.25},
|
|
{0.25, 0.25},
|
|
{1.0, 1.0},
|
|
{2.0, 2.0},
|
|
{3.0, 2.0},
|
|
}
|
|
for _, tt := range tests {
|
|
p.SetSpeed(tt.in)
|
|
if got := p.Speed(); got != tt.want {
|
|
t.Errorf("SetSpeed(%v) → Speed() = %v, want %v", tt.in, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestToggleMono(t *testing.T) {
|
|
p := newTestPlayer()
|
|
|
|
if p.Mono() {
|
|
t.Fatal("Mono() should start false")
|
|
}
|
|
p.ToggleMono()
|
|
if !p.Mono() {
|
|
t.Error("ToggleMono should flip to true")
|
|
}
|
|
p.ToggleMono()
|
|
if p.Mono() {
|
|
t.Error("ToggleMono should flip back to false")
|
|
}
|
|
}
|
|
|
|
func TestSetEQBandClamps(t *testing.T) {
|
|
p := newTestPlayer()
|
|
|
|
tests := []struct {
|
|
band int
|
|
in float64
|
|
want float64
|
|
}{
|
|
{0, 20.0, 12.0},
|
|
{0, -20.0, -12.0},
|
|
{5, 6.5, 6.5},
|
|
{9, 0.0, 0.0},
|
|
}
|
|
for _, tt := range tests {
|
|
p.SetEQBand(tt.band, tt.in)
|
|
bands := p.EQBands()
|
|
if bands[tt.band] != tt.want {
|
|
t.Errorf("SetEQBand(%d, %v) → %v, want %v", tt.band, tt.in, bands[tt.band], tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSetEQBandIgnoresInvalidIndex(t *testing.T) {
|
|
p := newTestPlayer()
|
|
// Setting an out-of-range band should be a no-op, not panic.
|
|
p.SetEQBand(-1, 5)
|
|
p.SetEQBand(10, 5)
|
|
p.SetEQBand(100, 5)
|
|
|
|
for i, b := range p.EQBands() {
|
|
if b != 0 {
|
|
t.Errorf("EQBands[%d] = %v, want 0 after invalid writes", i, b)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEQBandsReturnsCopy(t *testing.T) {
|
|
p := newTestPlayer()
|
|
p.SetEQBand(0, 6)
|
|
|
|
bands := p.EQBands()
|
|
bands[0] = 999 // mutate local copy
|
|
|
|
if p.EQBands()[0] != 6 {
|
|
t.Error("EQBands() should return a copy — mutation leaked back")
|
|
}
|
|
}
|
|
|
|
func TestIsPlayingDefaultsFalse(t *testing.T) {
|
|
p := newTestPlayer()
|
|
if p.IsPlaying() {
|
|
t.Error("IsPlaying() should start false")
|
|
}
|
|
if p.IsPaused() {
|
|
t.Error("IsPaused() should start false")
|
|
}
|
|
}
|
|
|
|
func TestSampleRate(t *testing.T) {
|
|
p := &Player{sr: 44100}
|
|
if p.SampleRate() != 44100 {
|
|
t.Errorf("SampleRate() = %d, want 44100", p.SampleRate())
|
|
}
|
|
}
|
|
|
|
func TestStreamTitleEmpty(t *testing.T) {
|
|
p := newTestPlayer()
|
|
if got := p.StreamTitle(); got != "" {
|
|
t.Errorf("StreamTitle() on fresh player = %q, want empty", got)
|
|
}
|
|
}
|
|
|
|
func TestSetStreamTitle(t *testing.T) {
|
|
p := newTestPlayer()
|
|
p.setStreamTitle("Artist - Song")
|
|
if got := p.StreamTitle(); got != "Artist - Song" {
|
|
t.Errorf("StreamTitle() = %q, want 'Artist - Song'", got)
|
|
}
|
|
}
|
|
|
|
func TestRegisterStreamerFactory(t *testing.T) {
|
|
p := newTestPlayer()
|
|
var noop StreamerFactory // nil factory is fine for this storage-only test
|
|
p.RegisterStreamerFactory("spotify:", noop)
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
if len(p.customFactories) != 1 {
|
|
t.Errorf("len(customFactories) = %d, want 1", len(p.customFactories))
|
|
}
|
|
if _, ok := p.customFactories["spotify:"]; !ok {
|
|
t.Error("factory not stored under 'spotify:'")
|
|
}
|
|
}
|
|
|
|
func TestRegisterBufferedURLMatcher(t *testing.T) {
|
|
p := newTestPlayer()
|
|
match := func(u string) bool { return u == "foo" }
|
|
p.RegisterBufferedURLMatcher(match)
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
if p.bufferedURLMatch == nil {
|
|
t.Error("matcher not stored")
|
|
}
|
|
}
|
|
|
|
func TestConcurrentVolumeSetRead(t *testing.T) {
|
|
p := newTestPlayer()
|
|
var wg sync.WaitGroup
|
|
const N = 100
|
|
|
|
wg.Add(N * 2)
|
|
for i := 0; i < N; i++ {
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
p.SetVolume(float64(-i % 30))
|
|
}(i)
|
|
go func() {
|
|
defer wg.Done()
|
|
_ = p.Volume()
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
// If the race detector didn't flag anything, we're happy.
|
|
}
|
|
|
|
// Ensure the atomic uint64 storage actually wraps dB as expected.
|
|
func TestVolumeStoragePrecision(t *testing.T) {
|
|
p := newTestPlayer()
|
|
p.SetVolume(-3.14)
|
|
v := math.Float64frombits(p.volume.Load())
|
|
if math.Abs(v-(-3.14)) > 1e-12 {
|
|
t.Errorf("stored volume = %v, want -3.14", v)
|
|
}
|
|
}
|