Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
170 lines
3.5 KiB
Go
170 lines
3.5 KiB
Go
package sound
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"log/slog"
|
|
"math"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func resetRecent(t *testing.T) {
|
|
mu.Lock()
|
|
recent = nil
|
|
mu.Unlock()
|
|
t.Cleanup(func() {
|
|
mu.Lock()
|
|
recent = nil
|
|
mu.Unlock()
|
|
})
|
|
}
|
|
|
|
func TestTestBinaryStartsMuted(t *testing.T) {
|
|
if Enabled() {
|
|
t.Fatal("sound enabled in a test binary")
|
|
}
|
|
}
|
|
|
|
func TestCueNames(t *testing.T) {
|
|
seen := map[string]bool{}
|
|
for _, c := range Cues() {
|
|
s := c.String()
|
|
if seen[s] {
|
|
t.Errorf("duplicate name %q", s)
|
|
}
|
|
seen[s] = true
|
|
}
|
|
if len(seen) != 15 {
|
|
t.Errorf("cues = %d, want 15", len(seen))
|
|
}
|
|
if got := Cue(99).String(); got != "cue(99)" {
|
|
t.Errorf("unknown cue name = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestSynthBounds(t *testing.T) {
|
|
for _, c := range Cues() {
|
|
s := synth(c)
|
|
if d := float64(len(s)) / rate; d < 0.005 || d > 0.5 {
|
|
t.Errorf("%v lasts %.3f s", c, d)
|
|
}
|
|
peak := 0.0
|
|
for _, v := range s {
|
|
peak = max(peak, math.Abs(float64(v)))
|
|
}
|
|
if peak < 0.05 || peak > 1 {
|
|
t.Errorf("%v peak %.3f", c, peak)
|
|
}
|
|
if a, z := math.Abs(float64(s[0])), math.Abs(float64(s[len(s)-1])); a > 0.01 || z > 0.01 {
|
|
t.Errorf("%v clicks: first %.3f last %.3f", c, a, z)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCuesDiffer(t *testing.T) {
|
|
cues := Cues()
|
|
for i, a := range cues {
|
|
for _, b := range cues[i+1:] {
|
|
if slices.Equal(synth(a), synth(b)) {
|
|
t.Errorf("%v and %v sound the same", a, b)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRecentKeepsLast32(t *testing.T) {
|
|
resetRecent(t)
|
|
var played []Cue
|
|
for i := range 40 {
|
|
c := Cue(i % int(cueCount))
|
|
Play(c)
|
|
played = append(played, c)
|
|
}
|
|
if got, want := Recent(), played[8:]; !slices.Equal(got, want) {
|
|
t.Errorf("recent = %v, want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestRecentIsACopy(t *testing.T) {
|
|
resetRecent(t)
|
|
Play(Tap)
|
|
r := Recent()
|
|
r[0] = Error
|
|
if Recent()[0] != Tap {
|
|
t.Error("Recent exposed its buffer")
|
|
}
|
|
}
|
|
|
|
func TestSetEnabled(t *testing.T) {
|
|
t.Cleanup(func() { SetEnabled(false) })
|
|
SetEnabled(true)
|
|
if !Enabled() {
|
|
t.Error("SetEnabled(true) ignored")
|
|
}
|
|
SetEnabled(false)
|
|
if Enabled() {
|
|
t.Error("SetEnabled(false) ignored")
|
|
}
|
|
}
|
|
|
|
func TestSetVolume(t *testing.T) {
|
|
old := Volume()
|
|
t.Cleanup(func() { SetVolume(old) })
|
|
SetVolume(0.8)
|
|
if Volume() != 0.8 {
|
|
t.Errorf("volume = %v", Volume())
|
|
}
|
|
for _, v := range []float64{-0.1, 1.1, math.NaN()} {
|
|
func() {
|
|
defer func() {
|
|
if recover() == nil {
|
|
t.Errorf("SetVolume(%v) did not panic", v)
|
|
}
|
|
}()
|
|
SetVolume(v)
|
|
}()
|
|
}
|
|
}
|
|
|
|
func TestPlayPanicsOnUnknownCue(t *testing.T) {
|
|
defer func() {
|
|
if recover() == nil {
|
|
t.Error("Play(cueCount) did not panic")
|
|
}
|
|
}()
|
|
Play(cueCount)
|
|
}
|
|
|
|
func TestNoteErrLogsChanges(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
old := slog.Default()
|
|
slog.SetDefault(slog.New(slog.NewTextHandler(&buf, nil)))
|
|
t.Cleanup(func() { slog.SetDefault(old) })
|
|
down, other := errors.New("no device"), errors.New("device lost")
|
|
steps := []struct {
|
|
err error
|
|
want string
|
|
}{
|
|
{nil, ""},
|
|
{down, "level=WARN msg=\"crychic: sound output unavailable\" err=\"no device\""},
|
|
{down, ""},
|
|
{other, "level=WARN msg=\"crychic: sound output unavailable\" err=\"device lost\""},
|
|
{nil, "level=INFO msg=\"crychic: sound output back\""},
|
|
{nil, ""},
|
|
}
|
|
var last error
|
|
for i, s := range steps {
|
|
buf.Reset()
|
|
last = noteErr(last, s.err)
|
|
got := strings.TrimSpace(buf.String())
|
|
if s.want == "" && got != "" || s.want != "" && !strings.Contains(got, s.want) {
|
|
t.Errorf("step %d logged %q, want %q", i, got, s.want)
|
|
}
|
|
if last != s.err {
|
|
t.Errorf("step %d kept %v, want %v", i, last, s.err)
|
|
}
|
|
}
|
|
}
|