Simplify tests

This commit is contained in:
Bjarne Øverli
2026-04-02 19:49:41 +02:00
parent 197e6d5d76
commit 4f731c3ac7
7 changed files with 96 additions and 118 deletions
+52 -43
View File
@@ -176,26 +176,28 @@ func TestClampResampleQuality(t *testing.T) {
}
func TestClampPadding(t *testing.T) {
cfg := defaultConfig()
cfg.PaddingH = -1
cfg.PaddingV = -1
cfg.clamp()
if cfg.PaddingH != 0 {
t.Errorf("PaddingH = %d, want 0", cfg.PaddingH)
tests := []struct {
name string
inH, inV int
wantH, wantV int
}{
{"negative clamped to 0", -1, -1, 0, 0},
{"over max clamped", 20, 10, 10, 5},
{"within range", 3, 1, 3, 1},
}
if cfg.PaddingV != 0 {
t.Errorf("PaddingV = %d, want 0", cfg.PaddingV)
}
cfg.PaddingH = 20
cfg.PaddingV = 10
cfg.clamp()
if cfg.PaddingH != 10 {
t.Errorf("PaddingH = %d, want 10", cfg.PaddingH)
}
if cfg.PaddingV != 5 {
t.Errorf("PaddingV = %d, want 5", cfg.PaddingV)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := defaultConfig()
cfg.PaddingH = tt.inH
cfg.PaddingV = tt.inV
cfg.clamp()
if cfg.PaddingH != tt.wantH {
t.Errorf("PaddingH = %d, want %d", cfg.PaddingH, tt.wantH)
}
if cfg.PaddingV != tt.wantV {
t.Errorf("PaddingV = %d, want %d", cfg.PaddingV, tt.wantV)
}
})
}
}
@@ -284,14 +286,21 @@ func TestNavidromeIsSet(t *testing.T) {
}
func TestNavidromeScrobbleEnabled(t *testing.T) {
cfg := NavidromeConfig{}
if !cfg.ScrobbleEnabled() {
t.Error("ScrobbleEnabled() should be true by default")
tests := []struct {
name string
disabled bool
want bool
}{
{"default enabled", false, true},
{"explicitly disabled", true, false},
}
cfg.ScrobbleDisabled = true
if cfg.ScrobbleEnabled() {
t.Error("ScrobbleEnabled() should be false when disabled")
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := NavidromeConfig{ScrobbleDisabled: tt.disabled}
if got := cfg.ScrobbleEnabled(); got != tt.want {
t.Errorf("ScrobbleEnabled() = %v, want %v", got, tt.want)
}
})
}
}
@@ -384,24 +393,24 @@ func TestYouTubeMusicIsSetOrFallback(t *testing.T) {
func TestYouTubeMusicResolveCredentials(t *testing.T) {
fallback := func() (string, string) { return "fb_id", "fb_secret" }
// User credentials take priority
cfg := YouTubeMusicConfig{ClientID: "my_id", ClientSecret: "my_secret"}
id, secret := cfg.ResolveCredentials(fallback)
if id != "my_id" || secret != "my_secret" {
t.Errorf("got (%q, %q), want (my_id, my_secret)", id, secret)
tests := []struct {
name string
cfg YouTubeMusicConfig
fallbackFn func() (string, string)
wantID string
wantSecret string
}{
{"user credentials take priority", YouTubeMusicConfig{ClientID: "my_id", ClientSecret: "my_secret"}, fallback, "my_id", "my_secret"},
{"falls back when empty", YouTubeMusicConfig{}, fallback, "fb_id", "fb_secret"},
{"nil fallback returns empty", YouTubeMusicConfig{}, nil, "", ""},
}
// Falls back when user credentials empty
cfg = YouTubeMusicConfig{}
id, secret = cfg.ResolveCredentials(fallback)
if id != "fb_id" || secret != "fb_secret" {
t.Errorf("got (%q, %q), want (fb_id, fb_secret)", id, secret)
}
// Nil fallback returns empty
id, secret = cfg.ResolveCredentials(nil)
if id != "" || secret != "" {
t.Errorf("got (%q, %q), want empty", id, secret)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
id, secret := tt.cfg.ResolveCredentials(tt.fallbackFn)
if id != tt.wantID || secret != tt.wantSecret {
t.Errorf("got (%q, %q), want (%q, %q)", id, secret, tt.wantID, tt.wantSecret)
}
})
}
}
+11 -1
View File
@@ -2,7 +2,17 @@
set -e
REPO="bjarneo/cliamp"
INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}"
# Determine install directory: prefer ~/.local/bin (no sudo), fall back to /usr/local/bin
if [ -z "$INSTALL_DIR" ]; then
LOCAL_BIN="$HOME/.local/bin"
if echo "$PATH" | tr ':' '\n' | grep -qx "$LOCAL_BIN"; then
mkdir -p "$LOCAL_BIN"
INSTALL_DIR="$LOCAL_BIN"
else
INSTALL_DIR="/usr/local/bin"
fi
fi
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
-1
View File
@@ -44,7 +44,6 @@ func TestRequestOmitsEmptyFields(t *testing.T) {
t.Fatalf("Marshal error: %v", err)
}
// Value, Playlist, Path, Name should be omitted
var raw map[string]interface{}
if err := json.Unmarshal(data, &raw); err != nil {
t.Fatalf("Unmarshal error: %v", err)
+6 -39
View File
@@ -7,34 +7,31 @@ import (
)
func TestBiquadPassthroughAtZeroDB(t *testing.T) {
// At 0 dB (or within ±0.1 dB), the biquad should pass through unchanged
src := &fakeStreamer{val: [2]float64{0.7, -0.3}, count: 64}
src := &fakeStreamer{val: [2]float64{0.7, -0.3}, count: 4}
var gain atomic.Uint64
gain.Store(math.Float64bits(0.0))
b := newBiquad(src, 1000, 0.707, &gain, 44100)
samples := make([][2]float64, 64)
samples := make([][2]float64, 4)
n, _ := b.Stream(samples)
for i := range n {
if math.Abs(samples[i][0]-0.7) > 1e-9 {
t.Errorf("at 0dB: samples[%d][0] = %f, want 0.7", i, samples[i][0])
t.Errorf("samples[%d][0] = %f, want 0.7", i, samples[i][0])
}
if math.Abs(samples[i][1]-(-0.3)) > 1e-9 {
t.Errorf("at 0dB: samples[%d][1] = %f, want -0.3", i, samples[i][1])
t.Errorf("samples[%d][1] = %f, want -0.3", i, samples[i][1])
}
}
}
func TestBiquadNonZeroGainModifiesSamples(t *testing.T) {
// Use a sine wave at the filter's center frequency (1000 Hz).
// A peaking EQ only boosts energy near its center frequency, so a
// constant (DC) input would pass through unchanged.
// Sine wave at center frequency — DC input would pass through unchanged.
const sr = 44100
const freq = 1000.0
const nSamples = 512
src := &sineStreamerEQ{freq: freq, sr: sr, count: nSamples}
src := &sineStreamer{freq: freq, sr: sr, count: nSamples}
var gain atomic.Uint64
gain.Store(math.Float64bits(12.0)) // +12 dB boost
@@ -43,44 +40,17 @@ func TestBiquadNonZeroGainModifiesSamples(t *testing.T) {
samples := make([][2]float64, nSamples)
n, _ := b.Stream(samples)
// Compare peak amplitude in later samples (after transient settles)
// against the original sine amplitude of 1.0.
maxAmp := 0.0
for i := 256; i < n; i++ {
if a := math.Abs(samples[i][0]); a > maxAmp {
maxAmp = a
}
}
// +12 dB should boost amplitude by ~4x; even accounting for filter shape
// the peak should exceed the original 1.0 amplitude.
if maxAmp <= 1.05 {
t.Errorf("biquad at +12dB: max amplitude = %f, expected > 1.05", maxAmp)
}
}
// sineStreamerEQ generates a sine wave for EQ testing.
type sineStreamerEQ struct {
freq float64
sr float64
pos int
count int
}
func (s *sineStreamerEQ) Stream(samples [][2]float64) (int, bool) {
n := min(len(samples), s.count-s.pos)
if n <= 0 {
return 0, false
}
for i := range n {
val := math.Sin(2 * math.Pi * s.freq * float64(s.pos+i) / s.sr)
samples[i] = [2]float64{val, val}
}
s.pos += n
return n, true
}
func (s *sineStreamerEQ) Err() error { return nil }
func TestBiquadCoeffCaching(t *testing.T) {
var gain atomic.Uint64
gain.Store(math.Float64bits(3.0))
@@ -93,13 +63,11 @@ func TestBiquadCoeffCaching(t *testing.T) {
t.Fatal("inited should be true after calcCoeffs")
}
// Same gain should not recompute
b.calcCoeffs(3.0)
if b.b0 != b0First {
t.Error("coefficients should be cached for same gain")
}
// Different gain should recompute
b.calcCoeffs(6.0)
if b.b0 == b0First {
t.Error("coefficients should be recomputed for different gain")
@@ -121,7 +89,6 @@ func TestBiquadErr(t *testing.T) {
}
func TestEqFreqs(t *testing.T) {
// Verify the 10-band EQ center frequencies are in ascending order
for i := 1; i < len(eqFreqs); i++ {
if eqFreqs[i] <= eqFreqs[i-1] {
t.Errorf("eqFreqs[%d] (%f) <= eqFreqs[%d] (%f)", i, eqFreqs[i], i-1, eqFreqs[i-1])
+2 -5
View File
@@ -7,7 +7,6 @@ import (
)
func TestSpeedStreamerPassthroughAt1x(t *testing.T) {
// At speed 1.0, should pass through samples unchanged
src := &fakeStreamer{val: [2]float64{0.5, -0.5}, count: 1024}
var speed atomic.Uint64
speed.Store(math.Float64bits(1.0))
@@ -34,7 +33,6 @@ func TestSpeedStreamerPassthroughAt1x(t *testing.T) {
}
func TestSpeedStreamerPassthroughAtZero(t *testing.T) {
// Speed <= 0 should also pass through
src := &fakeStreamer{val: [2]float64{0.3, 0.3}, count: 64}
var speed atomic.Uint64
speed.Store(math.Float64bits(0.0))
@@ -50,8 +48,7 @@ func TestSpeedStreamerPassthroughAtZero(t *testing.T) {
}
func TestSpeedStreamer2xProducesOutput(t *testing.T) {
// At 2x speed, we should still get output (time-stretched)
src := &sineStreamer{freq: 440, sr: 44100, count: 44100}
src := &sineStreamer{freq: 440, sr: 44100, count: 8192}
var speed atomic.Uint64
speed.Store(math.Float64bits(2.0))
@@ -69,7 +66,7 @@ func TestSpeedStreamer2xProducesOutput(t *testing.T) {
}
func TestSpeedStreamerHalfSpeedProducesOutput(t *testing.T) {
src := &sineStreamer{freq: 440, sr: 44100, count: 44100}
src := &sineStreamer{freq: 440, sr: 44100, count: 8192}
var speed atomic.Uint64
speed.Store(math.Float64bits(0.5))
+1 -3
View File
@@ -26,7 +26,6 @@ func (f *fakeStreamer) Stream(samples [][2]float64) (int, bool) {
func (f *fakeStreamer) Err() error { return nil }
func TestVolumeStreamerZeroDB(t *testing.T) {
// 0 dB should pass through samples unchanged (gain = 1.0)
src := &fakeStreamer{val: [2]float64{0.5, -0.5}, count: 4}
var vol atomic.Uint64
vol.Store(math.Float64bits(0.0))
@@ -132,8 +131,7 @@ func TestVolumeStreamerEmptySource(t *testing.T) {
}
func TestVolumeStreamerGainCaching(t *testing.T) {
// Volume changes between Stream() calls should recompute gain
src := &fakeStreamer{val: [2]float64{1.0, 1.0}, count: 100}
src := &fakeStreamer{val: [2]float64{1.0, 1.0}, count: 8}
var vol atomic.Uint64
vol.Store(math.Float64bits(0.0))
var mono atomic.Bool
+24 -26
View File
@@ -15,23 +15,16 @@ func TestToggleShuffle(t *testing.T) {
t.Fatal("Shuffled() after toggle should be true")
}
// Current track should still be the same
cur, _ := p.Current()
if cur.Title != "C" {
t.Fatalf("Current after shuffle = %q, want C", cur.Title)
}
// Position should be 0 in shuffled order (current track moves to front)
if p.pos != 0 {
t.Fatalf("pos after shuffle = %d, want 0", p.pos)
}
}
func TestToggleShuffleOff(t *testing.T) {
p := makePlaylist(5, true) // start shuffled
p.SetIndex(p.order[0])
curTrack, curIdx := p.Current()
curTrack, _ := p.Current()
p.ToggleShuffle() // turn off
@@ -39,22 +32,19 @@ func TestToggleShuffleOff(t *testing.T) {
t.Fatal("Shuffled() after toggle off should be false")
}
// Order should be sequential again
for i, idx := range p.order {
if idx != i {
t.Fatalf("order[%d] = %d, want %d", i, idx, i)
}
}
// Position should track the same track
// Current track should be preserved
cur2, _ := p.Current()
if cur2.Title != curTrack.Title {
t.Fatalf("Current after unshuffle = %q, want %q", cur2.Title, curTrack.Title)
}
// pos should equal the original track index
if p.pos != curIdx {
t.Fatalf("pos after unshuffle = %d, want %d", p.pos, curIdx)
// Playback should follow sequential order from current track onward
for i := 0; i < 4; i++ {
next, ok := p.Next()
if !ok {
t.Fatalf("Next() returned false at step %d", i)
}
_ = next // just verify it advances without error
}
}
@@ -105,18 +95,26 @@ func TestSetRepeat(t *testing.T) {
func TestShufflePreservesAllTracks(t *testing.T) {
p := makePlaylist(10, false)
p.SetRepeat(RepeatAll)
p.ToggleShuffle()
// All track indices should appear exactly once in the order
seen := make(map[int]bool)
for _, idx := range p.order {
if seen[idx] {
t.Fatalf("duplicate index %d in shuffle order", idx)
// Walk through all tracks via Next() and verify each title appears exactly once
seen := make(map[string]bool)
cur, _ := p.Current()
seen[cur.Title] = true
for i := 0; i < 9; i++ {
next, ok := p.Next()
if !ok {
t.Fatalf("Next() returned false at step %d", i)
}
seen[idx] = true
if seen[next.Title] {
t.Fatalf("duplicate track %q at step %d", next.Title, i)
}
seen[next.Title] = true
}
if len(seen) != 10 {
t.Fatalf("shuffle order has %d entries, want 10", len(seen))
t.Fatalf("saw %d unique tracks, want 10", len(seen))
}
}