From 53c304579754184835d4313c9892370b71dc159e Mon Sep 17 00:00:00 2001 From: Mark Kremer Date: Thu, 2 Nov 2023 18:52:06 +0100 Subject: [PATCH 1/9] Test and benchmark the Mixer --- mixer_test.go | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 mixer_test.go diff --git a/mixer_test.go b/mixer_test.go new file mode 100644 index 0000000..42b30d5 --- /dev/null +++ b/mixer_test.go @@ -0,0 +1,61 @@ +package beep_test + +import ( + "testing" + + "github.com/gopxl/beep" + "github.com/gopxl/beep/internal/testtools" +) + +func TestMixer(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 BenchmarkMixer(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) +} From 198a5e18cd9f073dabd3f31867fc83c49430104d Mon Sep 17 00:00:00 2001 From: Mark Kremer Date: Thu, 2 Nov 2023 18:52:25 +0100 Subject: [PATCH 2/9] Test SineTone generator --- generators/sine_test.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 generators/sine_test.go diff --git a/generators/sine_test.go b/generators/sine_test.go new file mode 100644 index 0000000..c2b0aef --- /dev/null +++ b/generators/sine_test.go @@ -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) +} From 6e88eec2c9c8ebf7857a9b059693f69a873fd77f Mon Sep 17 00:00:00 2001 From: Mark Kremer Date: Thu, 2 Nov 2023 18:53:06 +0100 Subject: [PATCH 3/9] Benchmark the speaker conversion from samples to bytes --- speaker/speaker.go | 8 +++++--- speaker/speaker_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 speaker/speaker_test.go diff --git a/speaker/speaker.go b/speaker/speaker.go index fc83208..fc83102 100644 --- a/speaker/speaker.go +++ b/speaker/speaker.go @@ -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 diff --git a/speaker/speaker_test.go b/speaker/speaker_test.go new file mode 100644 index 0000000..6a41216 --- /dev/null +++ b/speaker/speaker_test.go @@ -0,0 +1,36 @@ +package speaker + +import ( + "fmt" + "io" + "testing" + + "github.com/gopxl/beep/internal/testtools" +) + +func BenchmarkSampleReader_Read(b *testing.B) { + // note: must be multiples of bytesPerSample + bufferSizes := []int{64, 512, 8192, 32768} + + for _, bs := range bufferSizes { + b.Run(fmt.Sprintf("with buffer size %d", bs), func(b *testing.B) { + s, _ := testtools.RandomDataStreamer(b.N) + r := newReaderFromStreamer(s) + buf := make([]byte, bs) + + b.StartTimer() + for { + n, err := r.Read(buf) + if err == io.EOF { + break + } + if err != nil { + panic(err) + } + if n != len(buf) { + break + } + } + }) + } +} From 4cc0ce88f1c8b246a41a0127700abdafcb65735e Mon Sep 17 00:00:00 2001 From: Mark Kremer Date: Sat, 4 Nov 2023 12:58:34 +0100 Subject: [PATCH 4/9] Test the Mixer plays silence after the streams have finished --- mixer_test.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/mixer_test.go b/mixer_test.go index 42b30d5..ef60eee 100644 --- a/mixer_test.go +++ b/mixer_test.go @@ -3,11 +3,13 @@ package beep_test import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/gopxl/beep" "github.com/gopxl/beep/internal/testtools" ) -func TestMixer(t *testing.T) { +func TestMixer_MixesSamples(t *testing.T) { epsilon := 0.000001 s1, data1 := testtools.RandomDataStreamer(200) @@ -47,6 +49,24 @@ func TestMixer(t *testing.T) { } } +func TestMixer_KeepsPlayingSilenceAfterStreamsHaveFinished(t *testing.T) { + s1, _ := testtools.RandomDataStreamer(50) + s2, _ := testtools.RandomDataStreamer(60) + + m := beep.Mixer{} + m.Add(s1) + m.Add(s2) + + samples := testtools.CollectNum(100, &m) + assert.Len(t, samples, 100) + + for _, s := range samples[60:] { + if s[0] != 0 || s[1] != 0 { + t.Fatalf("expected silence after input streams are finished, got (%f, %f)", s[0], s[1]) + } + } +} + func BenchmarkMixer(b *testing.B) { s1, _ := testtools.RandomDataStreamer(b.N) s2, _ := testtools.RandomDataStreamer(b.N) From 156535792a1c608b6689f86ff3b075316053fef4 Mon Sep 17 00:00:00 2001 From: Mark Kremer Date: Sat, 4 Nov 2023 13:31:22 +0100 Subject: [PATCH 5/9] Add/improve to Mixer tests and add more benchmarks --- mixer_test.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/mixer_test.go b/mixer_test.go index ef60eee..bbec30e 100644 --- a/mixer_test.go +++ b/mixer_test.go @@ -49,7 +49,7 @@ func TestMixer_MixesSamples(t *testing.T) { } } -func TestMixer_KeepsPlayingSilenceAfterStreamsHaveFinished(t *testing.T) { +func TestMixer_DrainedStreamersAreRemoved(t *testing.T) { s1, _ := testtools.RandomDataStreamer(50) s2, _ := testtools.RandomDataStreamer(60) @@ -57,17 +57,69 @@ func TestMixer_KeepsPlayingSilenceAfterStreamsHaveFinished(t *testing.T) { m.Add(s1) m.Add(s2) - samples := testtools.CollectNum(100, &m) + // 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) + for _, s := range samples { + if s[0] != 0 || s[1] != 0 { + t.Fatalf("expected silence after input streams are finished, got (%f, %f)", s[0], s[1]) + } + } + + // 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()) + for _, s := range samples[50:] { + if s[0] != 0 || s[1] != 0 { + t.Fatalf("expected silence after input streams are finished, got (%f, %f)", s[0], s[1]) + } + } + + // Test silence when streamer is fully drained. + samples = testtools.CollectNum(10, &m) + assert.Len(t, samples, 10) + assert.Equal(t, 0, m.Len()) + for _, s := range samples { + if s[0] != 0 || s[1] != 0 { + t.Fatalf("expected silence after input streams are finished, got (%f, %f)", s[0], s[1]) + } + } - for _, s := range samples[60:] { + // Test silence after streamer was fully drained. + samples = testtools.CollectNum(10, &m) + assert.Len(t, samples, 10) + for _, s := range samples { if s[0] != 0 || s[1] != 0 { t.Fatalf("expected silence after input streams are finished, got (%f, %f)", s[0], s[1]) } } } -func BenchmarkMixer(b *testing.B) { +func BenchmarkMixer_MultipleStreams(b *testing.B) { s1, _ := testtools.RandomDataStreamer(b.N) s2, _ := testtools.RandomDataStreamer(b.N) @@ -79,3 +131,21 @@ func BenchmarkMixer(b *testing.B) { 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) +} From 3194da946a96b5ce51a64f4ee2e18aae08158a83 Mon Sep 17 00:00:00 2001 From: Mark Kremer Date: Sat, 4 Nov 2023 14:51:17 +0100 Subject: [PATCH 6/9] Test Ctrl --- ctrl_test.go | 53 +++++++++++++++++++++++++++++++++ internal/testtools/streamers.go | 12 ++++++++ 2 files changed, 65 insertions(+) create mode 100644 ctrl_test.go diff --git a/ctrl_test.go b/ctrl_test.go new file mode 100644 index 0000000..c871f2d --- /dev/null +++ b/ctrl_test.go @@ -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()) +} diff --git a/internal/testtools/streamers.go b/internal/testtools/streamers.go index b59f873..b68cfc6 100644 --- a/internal/testtools/streamers.go +++ b/internal/testtools/streamers.go @@ -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 +} From fb9078bdd85b36637bb343ff06b58e55cc320626 Mon Sep 17 00:00:00 2001 From: Mark Kremer Date: Sat, 4 Nov 2023 14:55:01 +0100 Subject: [PATCH 7/9] Write Mixer test more compactly --- mixer_test.go | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/mixer_test.go b/mixer_test.go index bbec30e..e19218e 100644 --- a/mixer_test.go +++ b/mixer_test.go @@ -80,11 +80,7 @@ func TestMixer_PlaysSilenceWhenNoStreamersProduceSamples(t *testing.T) { // Test silence before streamers are added. samples := testtools.CollectNum(10, &m) assert.Len(t, samples, 10) - for _, s := range samples { - if s[0] != 0 || s[1] != 0 { - t.Fatalf("expected silence after input streams are finished, got (%f, %f)", s[0], s[1]) - } - } + assert.Equal(t, make([][2]float64, 10), samples) // Test silence after streamer is partly drained. s, _ := testtools.RandomDataStreamer(50) @@ -93,30 +89,18 @@ func TestMixer_PlaysSilenceWhenNoStreamersProduceSamples(t *testing.T) { samples = testtools.CollectNum(100, &m) assert.Len(t, samples, 100) assert.Equal(t, 1, m.Len()) - for _, s := range samples[50:] { - if s[0] != 0 || s[1] != 0 { - t.Fatalf("expected silence after input streams are finished, got (%f, %f)", s[0], s[1]) - } - } + 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()) - for _, s := range samples { - if s[0] != 0 || s[1] != 0 { - t.Fatalf("expected silence after input streams are finished, got (%f, %f)", s[0], s[1]) - } - } + 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) - for _, s := range samples { - if s[0] != 0 || s[1] != 0 { - t.Fatalf("expected silence after input streams are finished, got (%f, %f)", s[0], s[1]) - } - } + assert.Equal(t, make([][2]float64, 10), samples) } func BenchmarkMixer_MultipleStreams(b *testing.B) { From 84dd3d811a2ce7fe0d5f2d334b6ca2fba16c35ee Mon Sep 17 00:00:00 2001 From: Mark Kremer Date: Sat, 4 Nov 2023 17:50:00 +0100 Subject: [PATCH 8/9] Test Streamer return values of different audio format decoders --- flac/decode_test.go | 24 ++----- .../testdata/valid_44100hz_22050_samples.mp3 | Bin 0 -> 4615 bytes .../testdata/valid_44100hz_22050_samples.ogg | Bin 0 -> 5116 bytes .../testdata/valid_44100hz_22050_samples.wav | Bin 0 -> 44144 bytes internal/testtools/asserts.go | 59 ++++++++++++++++++ mp3/decode_test.go | 23 +++++++ vorbis/decode_test.go | 23 +++++++ wav/decode_test.go | 18 +++++- 8 files changed, 126 insertions(+), 21 deletions(-) create mode 100644 internal/testdata/valid_44100hz_22050_samples.mp3 create mode 100644 internal/testdata/valid_44100hz_22050_samples.ogg create mode 100644 internal/testdata/valid_44100hz_22050_samples.wav create mode 100644 internal/testtools/asserts.go create mode 100644 mp3/decode_test.go create mode 100644 vorbis/decode_test.go diff --git a/flac/decode_test.go b/flac/decode_test.go index 4dd5dbd..6fb16fe 100644 --- a/flac/decode_test.go +++ b/flac/decode_test.go @@ -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()) } diff --git a/internal/testdata/valid_44100hz_22050_samples.mp3 b/internal/testdata/valid_44100hz_22050_samples.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a58a9b0ac9a0896f0c9a9914dc1561523dfbaf44 GIT binary patch literal 4615 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%0Dn`WXnK)-tdT3LAeaO&ywmAn+74n0M&i)`XL% z#+c0v!vSyT#TqhY&@Ig zpn#dK842cdiG#9rB)B0%9NA>fH>c;2(?VRSv&8{y$ND``qpEZW1%LUX7)btp=Zg+C@kXe7F4;32Fu;a+T&dR;;jMWN+4y6Yf_ zTOP<0*pQHLsSSXe(@nt%qq#nxITCSHw`lz#L}aMO0(uUCpHCkv_rfCTGMMAk5z*8G zzGTt-qW}`%qXrTfVMMKAxa5fF{Hg!;Fx$#hn2UcIh zC_xWZeGf-L)uo?IF|0*%K?qSJGzu>mx3B53OLV(^-e}F%ZZ;5^1CVkxege^Ulhx(o zQh#x=5YJyco14$@JsIO?t0s=f=n6tGH(|V-Ul0^{B#KS%Y?Otvo%Zh?7B;qI^ueno z84FzSl%;z-F$7{a6eKoAK;?;!LHc@Q<9n8_X69yH;?&_dBY+oooA1wEXKrbw+*8f> z0PEIo>NXXb7Z#r0H)GtjV|wJxbh4OiOvzPa2*si-tEBCBW(J=95$EBfurS<&FAY)X?;S0!y>QRiK~F{- zEY@&UG1m9>H)Ufx7z)vX-jq=>9<5Qma~6E=>X}4?VHxQ}BtNGw6_y z?Wh=g{q40^Z*H4@y5b+(J?eqyIHNA7Ecr#=j{MzCzf2BIG|jAFZOEBF{8jGc+|kb) zOkcwLt-gp#f@cwdH~`^Y*VVl|v|{!yR)xt1?!kn67bdYenV8D=p9w;D-neE7xW}8S z-~6(u_j;mh1*_ux!7GTpS;bM~H!}fx&4;Dq^*83zI>aH{f*s~9JG=giRuiy`@#JQN zv_X9Q>e#(KyJi{Y0Wkjg56gg+>&iG0dNKIN!iFZ>Ri}2pQXd@+H$rD46+IE>{qK!o-##{aixIt>G<>Ikq;+A6;A>RX z*2_^a#yviM-2{$pK%P85x|`hd?V{x)l^ekyTiIS1TQU-z{$2lXwe4Wj)Acu0s0laN zKABvys`7TPjl_lg`+GIqdigXEs!nwC+NxC>ns>{9Qu17o7YBE@>ce_om{)OiTJ5o8^$o$u;?RVQUQ>8viO#PiJuGrzHVi=Lo&b+ouMVa}qJ+mnZl;N+i+0#{Al$bB}o zY2Pjk^MgW&8`tB&ts$?eeLuDLe3nt<8gZuYQ-SV6E5Yu^lbfJDX z9rhd8bBo#zeQN_x@#5`M%nw%uMN7_2^_oqY){Xaw22op~WxQyB4LbhuyO&Z%RWKhN zI`wp79jNG54FsO^QvIhS<-$5ZA$h)^T`+YDb3&){ujf+BT;BY0nlUmR>^cjzajuf~ zJTlj};P+`1_*OR*$D&!_-TC~+eCq(>jwuhpDPypqj5cumY5IZ{yE|!S#l~Wj4#5h8 zVk=?r`hMKqms!IEgP8fb95^pD{)pf5>fcr{yHQJcVxYD}I*k7NPdDSu^>f?re*y^L zr`W=xfxyKr{n^RhuXpS%#&q;fy{#|?E+{78g?SOB_vi8h3$5l`CJuskHrBn{R*AL- zUT+r!t+&o7^DkYp%?^{?d(0DiTAP1}3%+-3yfOdH)aW;X+bVXxyCXcS_9m8$%H}#9 z6l{+PQJzxx``X#=eB4#?I_0it8U%bPXp7JG0Gae>=-8Q8Sk#}fno2LM$6wEzGB literal 0 HcmV?d00001 diff --git a/internal/testdata/valid_44100hz_22050_samples.wav b/internal/testdata/valid_44100hz_22050_samples.wav new file mode 100644 index 0000000000000000000000000000000000000000..f1f696c34db530ba0a485c9594e7ceefa2e98225 GIT binary patch literal 44144 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 literal 0 HcmV?d00001 diff --git a/internal/testtools/asserts.go b/internal/testtools/asserts.go new file mode 100644 index 0000000..3f48dca --- /dev/null +++ b/internal/testtools/asserts.go @@ -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()) +} diff --git a/mp3/decode_test.go b/mp3/decode_test.go new file mode 100644 index 0000000..10e7c02 --- /dev/null +++ b/mp3/decode_test.go @@ -0,0 +1,23 @@ +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_22050_samples.mp3")) + assert.NoError(t, err) + defer f.Close() + + s, _, err := mp3.Decode(f) + assert.NoError(t, err) + //assert.Equal(t, 22050, s.Len()) // todo: mp3 seems to return more samples than there are in the file. Uncomment this when fixed. + + testtools.AssertStreamerHasCorrectReturnBehaviour(t, s, s.Len()) +} diff --git a/vorbis/decode_test.go b/vorbis/decode_test.go new file mode 100644 index 0000000..6b6e428 --- /dev/null +++ b/vorbis/decode_test.go @@ -0,0 +1,23 @@ +package vorbis_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/gopxl/beep/internal/testtools" + "github.com/gopxl/beep/vorbis" +) + +func TestDecoder_ReturnBehaviour(t *testing.T) { + f, err := os.Open(testtools.TestFilePath("valid_44100hz_22050_samples.ogg")) + assert.NoError(t, err) + defer f.Close() + + s, _, err := vorbis.Decode(f) + assert.NoError(t, err) + assert.Equal(t, 22050, s.Len()) + + testtools.AssertStreamerHasCorrectReturnBehaviour(t, s, s.Len()) +} diff --git a/wav/decode_test.go b/wav/decode_test.go index d85640f..ceff4a9 100644 --- a/wav/decode_test.go +++ b/wav/decode_test.go @@ -2,9 +2,13 @@ package wav import ( "bytes" + "os" + "testing" + "github.com/gopxl/beep" + "github.com/gopxl/beep/internal/testtools" + "github.com/stretchr/testify/assert" - "testing" ) func TestDecode(t *testing.T) { @@ -103,3 +107,15 @@ func TestDecode(t *testing.T) { DataSize: 20, // 5 samples * 2 bytes/sample precision * 2 channels = 20 bytes }, d.h) } + +func TestDecoder_ReturnBehaviour(t *testing.T) { + f, err := os.Open(testtools.TestFilePath("valid_44100hz_22050_samples.wav")) + assert.NoError(t, err) + defer f.Close() + + s, _, err := Decode(f) + assert.NoError(t, err) + assert.Equal(t, 22050, s.Len()) + + testtools.AssertStreamerHasCorrectReturnBehaviour(t, s, s.Len()) +} From 95ab036638421852bfd25fb1e2ceb34c24dbb461 Mon Sep 17 00:00:00 2001 From: Mark Kremer Date: Sat, 4 Nov 2023 19:16:46 +0100 Subject: [PATCH 9/9] Add explanatory comment to mp3 decoder test --- ...mples.mp3 => valid_44100hz_x_padded_samples.mp3} | Bin mp3/decode_test.go | 6 ++++-- 2 files changed, 4 insertions(+), 2 deletions(-) rename internal/testdata/{valid_44100hz_22050_samples.mp3 => valid_44100hz_x_padded_samples.mp3} (100%) diff --git a/internal/testdata/valid_44100hz_22050_samples.mp3 b/internal/testdata/valid_44100hz_x_padded_samples.mp3 similarity index 100% rename from internal/testdata/valid_44100hz_22050_samples.mp3 rename to internal/testdata/valid_44100hz_x_padded_samples.mp3 diff --git a/mp3/decode_test.go b/mp3/decode_test.go index 10e7c02..3384f7e 100644 --- a/mp3/decode_test.go +++ b/mp3/decode_test.go @@ -11,13 +11,15 @@ import ( ) func TestDecoder_ReturnBehaviour(t *testing.T) { - f, err := os.Open(testtools.TestFilePath("valid_44100hz_22050_samples.mp3")) + 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) - //assert.Equal(t, 22050, s.Len()) // todo: mp3 seems to return more samples than there are in the file. Uncomment this when fixed. + // 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()) }