Skip to content

Commit

Permalink
Merge pull request #130 from gopxl/tests
Browse files Browse the repository at this point in the history
Add more tests and benchmarks
  • Loading branch information
MarkKremer authored Nov 9, 2023
2 parents f20a09c + 95ab036 commit 0648543
Show file tree
Hide file tree
Showing 14 changed files with 405 additions and 24 deletions.
53 changes: 53 additions & 0 deletions ctrl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package beep_test

import (
"testing"

"github.com/pkg/errors"
"github.com/stretchr/testify/assert"

"github.com/gopxl/beep"
"github.com/gopxl/beep/internal/testtools"
)

func TestCtrl_CanBePausedAndUnpaused(t *testing.T) {
s, data := testtools.RandomDataStreamer(20)

ctrl := beep.Ctrl{
Streamer: s,
Paused: false,
}

got := testtools.CollectNum(10, &ctrl)
assert.Equal(t, data[:10], got)

ctrl.Paused = true
got = testtools.CollectNum(10, &ctrl)
assert.Equal(t, make([][2]float64, 10), got)

ctrl.Paused = false
got = testtools.CollectNum(10, &ctrl)
assert.Equal(t, data[10:20], got)
}

func TestCtrl_DoesNotStreamFromNilStreamer(t *testing.T) {
ctrl := beep.Ctrl{
Streamer: nil,
Paused: false,
}

buf := make([][2]float64, 10)
n, ok := ctrl.Stream(buf)
assert.Equal(t, 0, n)
assert.False(t, ok)
}

func TestCtrl_PropagatesErrors(t *testing.T) {
ctrl := beep.Ctrl{}

assert.NoError(t, ctrl.Err())

err := errors.New("oh no")
ctrl.Streamer = testtools.ErrorStreamer{Error: err}
assert.Equal(t, err, ctrl.Err())
}
24 changes: 4 additions & 20 deletions flac/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,14 @@ import (
"github.com/gopxl/beep/internal/testtools"
)

func TestDecoder_Stream(t *testing.T) {
func TestDecoder_ReturnBehaviour(t *testing.T) {
f, err := os.Open(testtools.TestFilePath("valid_44100hz_22050_samples.flac"))
assert.NoError(t, err)
defer f.Close()

streamer, _, err := flac.Decode(f)
s, _, err := flac.Decode(f)
assert.NoError(t, err)
assert.Equal(t, 22050, s.Len())

// Case 1: return ok with all requested samples
buf := testtools.CollectNum(22000, streamer)
assert.Lenf(t, buf, 22000, "streamer quit prematurely; expected %d samples, got %d", 22000, len(buf))
assert.NoError(t, streamer.Err())

buf = make([][2]float64, 512)

// Case 2: return ok with 0 < n < 512 samples
n, ok := streamer.Stream(buf[:])
assert.True(t, ok)
assert.Equal(t, 50, n)
assert.NoError(t, streamer.Err())

// Case 3: return !ok with n == 0
n, ok = streamer.Stream(buf[:])
assert.False(t, ok)
assert.Equal(t, 0, n)
assert.NoError(t, streamer.Err())
testtools.AssertStreamerHasCorrectReturnBehaviour(t, s, s.Len())
}
36 changes: 36 additions & 0 deletions generators/sine_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package generators_test

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/gopxl/beep"
"github.com/gopxl/beep/generators"
"github.com/gopxl/beep/internal/testtools"
)

func TestSineTone(t *testing.T) {
epsilon := 0.000001

s, err := generators.SineTone(beep.SampleRate(8000), 400)
assert.NoError(t, err)

// Get a full single phase including the last sample.
phaseLength := 8000 / 400
samples := testtools.CollectNum(phaseLength+1, s)

// The sine wave should be 0 at the start, half a phase and at the end of the phase.
assert.InDelta(t, 0, samples[phaseLength*0][0], epsilon)
assert.InDelta(t, 0, samples[phaseLength*0][1], epsilon)
assert.InDelta(t, 0, samples[phaseLength*1/2][0], epsilon)
assert.InDelta(t, 0, samples[phaseLength*1/2][1], epsilon)
assert.InDelta(t, 0, samples[phaseLength*1][0], epsilon)
assert.InDelta(t, 0, samples[phaseLength*1][1], epsilon)

// The sine wave should be in a peak and trough at 1/4th and 3/4th in the phase respectively.
assert.InDelta(t, 1, samples[phaseLength*1/4][0], epsilon)
assert.InDelta(t, 1, samples[phaseLength*1/4][1], epsilon)
assert.InDelta(t, -1, samples[phaseLength*3/4][0], epsilon)
assert.InDelta(t, -1, samples[phaseLength*3/4][1], epsilon)
}
Binary file not shown.
Binary file added internal/testdata/valid_44100hz_22050_samples.wav
Binary file not shown.
Binary file not shown.
59 changes: 59 additions & 0 deletions internal/testtools/asserts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package testtools

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"

"github.com/gopxl/beep"
)

// AssertStreamerHasCorrectReturnBehaviour tests whether the return values returned
// by the streamer s adhere to the description on the Streamer interface.
func AssertStreamerHasCorrectReturnBehaviour(t *testing.T, s beep.Streamer, expectedSamples int) {
const leaveUnreadInFirstCase = 50

if expectedSamples < leaveUnreadInFirstCase+1 {
panic(fmt.Sprintf("AssertStreamerHasCorrectReturnBehaviour must be called with at least %d samples.", leaveUnreadInFirstCase+1))
}

// 1. n == len(samples) && ok
buf := make([][2]float64, 512)
samplesLeft := expectedSamples - leaveUnreadInFirstCase
for samplesLeft > 0 {
toRead := len(buf)
if toRead > samplesLeft {
toRead = samplesLeft
}
n, ok := s.Stream(buf[:toRead])
if !ok {
t.Fatalf("streamer returned !ok before it was expected to be drained")
}
if n < toRead {
t.Fatalf("streamer didn't return all requested samples before it was expected to be drained")
}
if s.Err() != nil {
t.Fatalf("unexpected error in streamer: %v", s.Err())
}
samplesLeft -= n
}

// 2. 0 < n && n < len(samples) && ok
n, ok := s.Stream(buf)
assert.True(t, ok)
assert.Equal(t, leaveUnreadInFirstCase, n)
assert.NoError(t, s.Err())

// 3. n == 0 && !ok
n, ok = s.Stream(buf)
assert.False(t, ok)
assert.Equal(t, 0, n)
assert.NoError(t, s.Err())

// Repeat calls after case 3 must return the same result.
n, ok = s.Stream(buf)
assert.False(t, ok)
assert.Equal(t, 0, n)
assert.NoError(t, s.Err())
}
12 changes: 12 additions & 0 deletions internal/testtools/streamers.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,15 @@ func (ds *dataStreamer) Seek(p int) error {
ds.pos = p
return nil
}

type ErrorStreamer struct {
Error error
}

func (e ErrorStreamer) Stream(samples [][2]float64) (n int, ok bool) {
return 0, false
}

func (e ErrorStreamer) Err() error {
return e.Error
}
135 changes: 135 additions & 0 deletions mixer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package beep_test

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/gopxl/beep"
"github.com/gopxl/beep/internal/testtools"
)

func TestMixer_MixesSamples(t *testing.T) {
epsilon := 0.000001

s1, data1 := testtools.RandomDataStreamer(200)
s2, data2 := testtools.RandomDataStreamer(200)

m := beep.Mixer{}
m.Add(s1)
m.Add(s2)

samples := testtools.CollectNum(100, &m)
for i, s := range samples {
wantL := data1[i][0] + data2[i][0]
wantR := data1[i][1] + data2[i][1]

if s[0] < wantL-epsilon || s[0] > wantL+epsilon {
t.Fatalf("unexpected value for mixed samples; expected: %f, got: %f", wantL, s[0])
}
if s[1] < wantR-epsilon || s[1] > wantR+epsilon {
t.Fatalf("unexpected value for mixed samples; expected: %f, got: %f", wantR, s[1])
}
}

s3, data3 := testtools.RandomDataStreamer(100)
m.Add(s3)

samples = testtools.CollectNum(100, &m)
for i, s := range samples {
wantL := data1[100+i][0] + data2[100+i][0] + data3[i][0]
wantR := data1[100+i][1] + data2[100+i][1] + data3[i][1]

if s[0] < wantL-epsilon || s[0] > wantL+epsilon {
t.Fatalf("unexpected value for mixed samples; expected: %f, got: %f", wantL, s[0])
}
if s[1] < wantR-epsilon || s[1] > wantR+epsilon {
t.Fatalf("unexpected value for mixed samples; expected: %f, got: %f", wantR, s[1])
}
}
}

func TestMixer_DrainedStreamersAreRemoved(t *testing.T) {
s1, _ := testtools.RandomDataStreamer(50)
s2, _ := testtools.RandomDataStreamer(60)

m := beep.Mixer{}
m.Add(s1)
m.Add(s2)

// Drain s1 but not so far it returns false.
samples := testtools.CollectNum(50, &m)
assert.Len(t, samples, 50)
assert.Equal(t, 2, m.Len())

// Fully drain s1.
// Drain s2 but not so far it returns false.
samples = testtools.CollectNum(10, &m)
assert.Len(t, samples, 10)
assert.Equal(t, 1, m.Len())

// Fully drain s2.
samples = testtools.CollectNum(10, &m)
assert.Len(t, samples, 10)
assert.Equal(t, 0, m.Len())
}

func TestMixer_PlaysSilenceWhenNoStreamersProduceSamples(t *testing.T) {
m := beep.Mixer{}

// Test silence before streamers are added.
samples := testtools.CollectNum(10, &m)
assert.Len(t, samples, 10)
assert.Equal(t, make([][2]float64, 10), samples)

// Test silence after streamer is partly drained.
s, _ := testtools.RandomDataStreamer(50)
m.Add(s)

samples = testtools.CollectNum(100, &m)
assert.Len(t, samples, 100)
assert.Equal(t, 1, m.Len())
assert.Equal(t, make([][2]float64, 50), samples[50:])

// Test silence when streamer is fully drained.
samples = testtools.CollectNum(10, &m)
assert.Len(t, samples, 10)
assert.Equal(t, 0, m.Len())
assert.Equal(t, make([][2]float64, 10), samples)

// Test silence after streamer was fully drained.
samples = testtools.CollectNum(10, &m)
assert.Len(t, samples, 10)
assert.Equal(t, make([][2]float64, 10), samples)
}

func BenchmarkMixer_MultipleStreams(b *testing.B) {
s1, _ := testtools.RandomDataStreamer(b.N)
s2, _ := testtools.RandomDataStreamer(b.N)

m := beep.Mixer{}
m.Add(s1)
m.Add(s2)

b.StartTimer()

testtools.CollectNum(b.N, &m)
}

func BenchmarkMixer_OneStream(b *testing.B) {
s, _ := testtools.RandomDataStreamer(b.N)

m := beep.Mixer{}
m.Add(s)

b.StartTimer()
testtools.CollectNum(b.N, &m)
}

func BenchmarkMixer_Silence(b *testing.B) {
m := beep.Mixer{}
// Don't add any streamers

b.StartTimer()
testtools.CollectNum(b.N, &m)
}
25 changes: 25 additions & 0 deletions mp3/decode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package mp3_test

import (
"os"
"testing"

"github.com/stretchr/testify/assert"

"github.com/gopxl/beep/internal/testtools"
"github.com/gopxl/beep/mp3"
)

func TestDecoder_ReturnBehaviour(t *testing.T) {
f, err := os.Open(testtools.TestFilePath("valid_44100hz_x_padded_samples.mp3"))
assert.NoError(t, err)
defer f.Close()

s, _, err := mp3.Decode(f)
assert.NoError(t, err)
// The length of the streamer isn't tested because mp3 files have
// a different padding depending on the decoder used.
// https://superuser.com/a/1393775

testtools.AssertStreamerHasCorrectReturnBehaviour(t, s, s.Len())
}
8 changes: 5 additions & 3 deletions speaker/speaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
package speaker

import (
"github.com/ebitengine/oto/v3"
"github.com/gopxl/beep"
"github.com/pkg/errors"
"io"
"sync"

"github.com/ebitengine/oto/v3"
"github.com/pkg/errors"

"github.com/gopxl/beep"
)

const channelCount = 2
Expand Down
Loading

0 comments on commit 0648543

Please sign in to comment.