From 389636fda2665a7b640a169cfed2c67b78ca972c Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Mon, 30 Jun 2014 21:24:29 -0400 Subject: [PATCH] ports tests over to RunTest.jl --- test/REQUIRE | 1 + test/runtests.jl | 18 ++++ test/test.jl | 28 ------ test/test_AudioIO.jl | 150 +++++++++++++++++----------- test/test_nodes.jl | 226 ++++++++++++++++++++++++------------------- test/testhelpers.jl | 14 +++ 6 files changed, 253 insertions(+), 184 deletions(-) create mode 100644 test/REQUIRE create mode 100755 test/runtests.jl delete mode 100755 test/test.jl create mode 100644 test/testhelpers.jl diff --git a/test/REQUIRE b/test/REQUIRE new file mode 100644 index 0000000..bc3e234 --- /dev/null +++ b/test/REQUIRE @@ -0,0 +1 @@ +FactCheck diff --git a/test/runtests.jl b/test/runtests.jl new file mode 100755 index 0000000..8412017 --- /dev/null +++ b/test/runtests.jl @@ -0,0 +1,18 @@ +#!/usr/bin/env julia + +using FactCheck + +test_regex = r"^test_.*\.jl$" +test_dir = "test" + +test_files = filter(n -> ismatch(test_regex, n), readdir(test_dir)) +if length(test_files) == 0 + error("No test files found. Make sure you're running from the root directory") +end + +for test_file in test_files + include(test_file) +end + +# return the overall exit status +exitstatus() diff --git a/test/test.jl b/test/test.jl deleted file mode 100755 index bb2a104..0000000 --- a/test/test.jl +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env julia - -test_regex = r"^test_.*\.jl$" -test_dir = "test" - -test_files = filter(n -> ismatch(test_regex, n), readdir(test_dir)) -if length(test_files) == 0 - error("No test files found. Make sure you're running from the root directory") -end - -# convenience function to calculate the mean-squared error -function mse(arr1::Array, arr2::Array) - @assert length(arr1) == length(arr2) - N = length(arr1) - err = 0.0 - for i in 1:N - err += (arr2[i] - arr1[i])^2 - end - err /= N -end - -for test_file in test_files - info("") - info("Running tests from \"$(test_file)\"...") - info("===================================================================") - include(test_file) - info("===================================================================") -end diff --git a/test/test_AudioIO.jl b/test/test_AudioIO.jl index ce36f02..bdc67af 100644 --- a/test/test_AudioIO.jl +++ b/test/test_AudioIO.jl @@ -1,10 +1,33 @@ -using Base.Test +module TestAudioIO + +using FactCheck using AudioIO import AudioIO.AudioBuf const TEST_SAMPLERATE = 44100 const TEST_BUF_SIZE = 1024 +include("testhelpers.jl") + +# convenience function to calculate the mean-squared error +function mse(arr1::Array, arr2::Array) + @assert length(arr1) == length(arr2) + N = length(arr1) + err = 0.0 + for i in 1:N + err += (arr2[i] - arr1[i])^2 + end + err /= N +end + +mse(X::AbstractArray, thresh=1e-8) = Y::AbstractArray -> begin + if size(X) != size(Y) + return false + end + + return mse(X, Y) < thresh +end + type TestAudioStream <: AudioIO.AudioStream root::AudioIO.AudioMixer info::AudioIO.DeviceInfo @@ -28,69 +51,78 @@ end #### Test playing back various vector types #### -# data shared between tests, for convenience -t = linspace(0, 2, 2 * 44100) -phase = 2pi * 100 * t +facts("Array playback") do + # data shared between tests, for convenience + t = linspace(0, 2, 2 * 44100) + phase = 2pi * 100 * t -## Test Float32 arrays, this is currently the native audio playback format -info("Testing Playing Float32 arrays...") -f32 = convert(Array{Float32}, sin(phase)) -test_stream = TestAudioStream() -player = play(f32, test_stream) -@test process(test_stream) == f32[1:TEST_BUF_SIZE] + ## Test Float32 arrays, this is currently the native audio playback format + context("Playing Float32 arrays") do + f32 = convert(Array{Float32}, sin(phase)) + test_stream = TestAudioStream() + player = play(f32, test_stream) + @fact process(test_stream) => f32[1:TEST_BUF_SIZE] + end + context("Playing Float64 arrays") do + f64 = convert(Array{Float64}, sin(phase)) + test_stream = TestAudioStream() + player = play(f64, test_stream) + @fact process(test_stream) => convert(AudioBuf, f64[1:TEST_BUF_SIZE]) + end -info("Testing Playing Float64 arrays...") -f64 = convert(Array{Float64}, sin(phase)) -test_stream = TestAudioStream() -player = play(f64, test_stream) -@test process(test_stream) == convert(AudioBuf, f64[1:TEST_BUF_SIZE]) + context("Playing Int8(Signed) arrays") do + i8 = Int8[-127:127] + test_stream = TestAudioStream() + player = play(i8, test_stream) + @fact process(test_stream)[1:255] => + mse(convert(AudioBuf, linspace(-1.0, 1.0, 255))) + end -info("Testing Playing Int8(Signed) arrays...") -i8 = Int8[-127:127] -test_stream = TestAudioStream() -player = play(i8, test_stream) -@test_approx_eq(process(test_stream)[1:255], - convert(AudioBuf, linspace(-1.0, 1.0, 255))) - -info("Testing Playing Uint8(Unsigned) arrays...") -# for unsigned 8-bit audio silence is represented as 128, so the symmetric range -# is 1-255 -ui8 = Uint8[1:255] -test_stream = TestAudioStream() -player = play(ui8, test_stream) -@test_approx_eq(process(test_stream)[1:255], - convert(AudioBuf, linspace(-1.0, 1.0, 255))) - -info("Testing AudioNode Stopping...") -test_stream = TestAudioStream() -node = SinOsc(440) -play(node, test_stream) -process(test_stream) -stop(node) -@test process(test_stream) == zeros(AudioIO.AudioSample, TEST_BUF_SIZE) - -info("Testing wav file write/read") - -fname = "test/sinwave.wav" - -samplerate = 44100 -freq = 440 -t = [0 : 2 * samplerate - 1] / samplerate -phase = 2 * pi * freq * t -reference = int16((2 ^ 15 - 1) * sin(phase)) - -af_open(fname, "w") do f - write(f, reference) + context("Playing Uint8(Unsigned) arrays") do + # for unsigned 8-bit audio silence is represented as 128, so the symmetric range + # is 1-255 + ui8 = Uint8[1:255] + test_stream = TestAudioStream() + player = play(ui8, test_stream) + @fact process(test_stream)[1:255] => + mse(convert(AudioBuf, linspace(-1.0, 1.0, 255))) + end end -af_open(fname) do f - @test f.sfinfo.channels == 1 - @test f.sfinfo.frames == 2 * samplerate - actual = read(f, 2 * samplerate) - @test_approx_eq(reference, actual) +facts("AudioNode Stopping") do + test_stream = TestAudioStream() + node = SinOsc(440) + play(node, test_stream) + process(test_stream) + stop(node) + @fact process(test_stream) => zeros(AudioIO.AudioSample, TEST_BUF_SIZE) end -info("Testing Audio Device Listing...") -# there aren't any devices on the Travis machine so just test that this doesn't crash -get_audio_devices() +facts("WAV file write/read") do + fname = "test/sinwave.wav" + + samplerate = 44100 + freq = 440 + t = [0 : 2 * samplerate - 1] / samplerate + phase = 2 * pi * freq * t + reference = int16((2 ^ 15 - 1) * sin(phase)) + + af_open(fname, "w") do f + write(f, reference) + end + + af_open(fname) do f + @fact f.sfinfo.channels => 1 + @fact f.sfinfo.frames => 2 * samplerate + actual = read(f, 2 * samplerate) + @fact reference => mse(actual) + end +end + +facts("Audio Device Listing") do + # there aren't any devices on the Travis machine so just test that this doesn't crash + @fact get_audio_devices() => issubtype(Array) +end + +end # module TestAudioIO diff --git a/test/test_nodes.jl b/test/test_nodes.jl index a9bfe97..9cdc451 100644 --- a/test/test_nodes.jl +++ b/test/test_nodes.jl @@ -1,4 +1,6 @@ -using Base.Test +module TestAudioIONodes + +using FactCheck using AudioIO import AudioIO.AudioSample import AudioIO.AudioBuf @@ -7,6 +9,8 @@ import AudioIO.AudioNode import AudioIO.DeviceInfo import AudioIO.render +include("testhelpers.jl") + # A TestNode just renders out 1:buf_size each frame type TestRenderer <: AudioRenderer buf::AudioBuf @@ -25,117 +29,145 @@ end test_info = DeviceInfo(44100, 512) dev_input = zeros(AudioSample, test_info.buf_size) -# first validate that the TestNode doesn't allocate so it doesn't mess up our -# other tests -test = TestNode(test_info.buf_size) -# JIT -render(test, dev_input, test_info) -@test ((@allocated render(test, dev_input, test_info)) < 20) +facts("Validating TestNode allocation") do + # first validate that the TestNode doesn't allocate so it doesn't mess up our + # other tests + test = TestNode(test_info.buf_size) + # JIT + render(test, dev_input, test_info) + @fact (@allocated render(test, dev_input, test_info)) => lessthan(20) +end #### AudioMixer Tests #### # TODO: there should be a setup/teardown mechanism and some way to isolate # tests -info("Testing AudioMixer...") -mix = AudioMixer() -render_output = render(mix, dev_input, test_info) -@test render_output == AudioSample[] +facts("AudioMixer") do + context("0 Input Mixer") do + mix = AudioMixer() + render_output = render(mix, dev_input, test_info) + @fact render_output => AudioSample[] + @fact (@allocated render(mix, dev_input, test_info)) => lessthan(49) + end -testnode = TestNode(test_info.buf_size) -mix = AudioMixer([testnode]) -render_output = render(mix, dev_input, test_info) -@test render_output == AudioSample[1:test_info.buf_size] -@test 100 > (@allocated render(mix, dev_input, test_info)) + context("1 Input Mixer") do + testnode = TestNode(test_info.buf_size) + mix = AudioMixer([testnode]) + render_output = render(mix, dev_input, test_info) + @fact render_output => AudioSample[1:test_info.buf_size] + @fact (@allocated render(mix, dev_input, test_info)) => lessthan(65) + end -test1 = TestNode(test_info.buf_size) -test2 = TestNode(test_info.buf_size) -mix = AudioMixer([test1, test2]) -render_output = render(mix, dev_input, test_info) -# make sure the two inputs are being added together -@test render_output == 2 * AudioSample[1:test_info.buf_size] + context("2 Input Mixer") do + test1 = TestNode(test_info.buf_size) + test2 = TestNode(test_info.buf_size) + mix = AudioMixer([test1, test2]) + render_output = render(mix, dev_input, test_info) + # make sure the two inputs are being added together + @fact render_output => 2 * AudioSample[1:test_info.buf_size] + @fact (@allocated render(mix, dev_input, test_info)) => lessthan(97) + # now we'll stop one of the inputs and make sure it gets removed + stop(test1) + render_output = render(mix, dev_input, test_info) + # make sure the two inputs are being added together + @fact render_output => AudioSample[1:test_info.buf_size] -# now we'll stop one of the inputs and make sure it gets removed -stop(test1) -render_output = render(mix, dev_input, test_info) -# make sure the two inputs are being added together -@test render_output == AudioSample[1:test_info.buf_size] + stop(mix) + render_output = render(mix, dev_input, test_info) + @fact render_output => AudioSample[] + end +end -stop(mix) -render_output = render(mix, dev_input, test_info) -@test render_output == AudioSample[] +MSE_THRESH = 1e-7 -# TODO: I think we can do better than this -const MSE_THRESH = 1e-7 +facts("SinOSC") do + freq = 440 + # note that this range includes the end, which is why there are + # sample_rate+1 samples + t = linspace(0, 1, test_info.sample_rate+1) + test_vect = convert(AudioBuf, sin(2pi * t * freq)) + context("Fixed Frequency") do + osc = SinOsc(freq) + render_output = render(osc, dev_input, test_info) + @fact mse(render_output, test_vect[1:test_info.buf_size]) => + lessthan(MSE_THRESH) + render_output = render(osc, dev_input, test_info) + @fact mse(render_output, + test_vect[test_info.buf_size+1:2*test_info.buf_size]) => + lessthan(MSE_THRESH) + @fact (@allocated render(osc, dev_input, test_info)) => lessthan(200) + stop(osc) + render_output = render(osc, dev_input, test_info) + @fact render_output => AudioSample[] + end -info("Testing SinOSC...") -freq = 440 -# note that this range includes the end, which is why there are sample_rate+1 samples -t = linspace(0, 1, test_info.sample_rate+1) -test_vect = convert(AudioBuf, sin(2pi * t * freq)) -osc = SinOsc(freq) -render_output = render(osc, dev_input, test_info) -@test mse(render_output, test_vect[1:test_info.buf_size]) < MSE_THRESH -render_output = render(osc, dev_input, test_info) -@test mse(render_output, - test_vect[test_info.buf_size+1:2*test_info.buf_size]) < MSE_THRESH -@test 200 > (@allocated render(osc, dev_input, test_info)) -stop(osc) -render_output = render(osc, dev_input, test_info) -@test render_output == AudioSample[] + context("Testing SinOsc with signal input") do + t = linspace(0, 1, test_info.sample_rate+1) + f = 440 .- t .* (440-110) + dt = 1 / test_info.sample_rate + # NOTE - this treats the phase as constant at each sample, which isn't strictly + # true. Unfortunately doing this correctly requires knowing more about the + # modulating signal and doing the real integral + phase = cumsum(2pi * dt .* f) + unshift!(phase, 0) + expected = convert(AudioBuf, sin(phase)) -info("Testing SinOsc with signal input") -t = linspace(0, 1, test_info.sample_rate+1) -f = 440 .- t .* (440-110) -dt = 1 / test_info.sample_rate -# NOTE - this treats the phase as constant at each sample, which isn't strictly -# true. Unfortunately doing this correctly requires knowing more about the -# modulating signal and doing the real integral -phase = cumsum(2pi * dt .* f) -unshift!(phase, 0) -expected = convert(AudioBuf, sin(phase)) + freq = LinRamp(440, 110, 1) + osc = SinOsc(freq) + render_output = render(osc, dev_input, test_info) + @fact mse(render_output, expected[1:test_info.buf_size]) => + lessthan(MSE_THRESH) + render_output = render(osc, dev_input, test_info) + @fact mse(render_output, + expected[test_info.buf_size+1:2*test_info.buf_size]) => + lessthan(MSE_THRESH) + # give a bigger budget here because we're rendering 2 nodes + @fact (@allocated render(osc, dev_input, test_info)) => lessthan(500) + end +end -freq = LinRamp(440, 110, 1) -osc = SinOsc(freq) -render_output = render(osc, dev_input, test_info) -@test mse(render_output, expected[1:test_info.buf_size]) < MSE_THRESH -render_output = render(osc, dev_input, test_info) -@test mse(render_output, - expected[test_info.buf_size+1:2*test_info.buf_size]) < MSE_THRESH -# give a bigger budget here because we're rendering 2 nodes -@test 500 > (@allocated render(osc, dev_input, test_info)) +facts("ArrayPlayer") do + context("playing long sample") do + v = rand(AudioSample, 44100) + player = ArrayPlayer(v) + render_output = render(player, dev_input, test_info) + @fact render_output => v[1:test_info.buf_size] + render_output = render(player, dev_input, test_info) + @fact render_output => v[(test_info.buf_size + 1) : (2*test_info.buf_size)] + @fact (@allocated render(player, dev_input, test_info)) => lessthan(200) + stop(player) + render_output = render(player, dev_input, test_info) + @fact render_output => AudioSample[] + end -info("Testing ArrayPlayer...") -v = rand(AudioSample, 44100) -player = ArrayPlayer(v) -render_output = render(player, dev_input, test_info) -@test render_output == v[1:test_info.buf_size] -render_output = render(player, dev_input, test_info) -@test render_output == v[(test_info.buf_size + 1) : (2*test_info.buf_size)] -@test 200 > (@allocated render(player, dev_input, test_info)) -stop(player) -render_output = render(player, dev_input, test_info) -@test render_output == AudioSample[] + context("testing end of vector") do + # give a vector just a bit larger than 1 buffer size + v = rand(AudioSample, test_info.buf_size + 1) + player = ArrayPlayer(v) + render(player, dev_input, test_info) + render_output = render(player, dev_input, test_info) + @fact render_output => v[test_info.buf_size+1:end] + end +end -# give a vector just a bit larger than 1 buffer size -v = rand(AudioSample, test_info.buf_size + 1) -player = ArrayPlayer(v) -render(player, dev_input, test_info) -render_output = render(player, dev_input, test_info) -@test render_output == v[test_info.buf_size+1:end] +facts("Gain") do + gained = TestNode(test_info.buf_size) * 0.75 + render_output = render(gained, dev_input, test_info) + @fact render_output => 0.75 * AudioSample[1:test_info.buf_size] +end -info("Testing Gain...") +facts("LinRamp") do + ramp = LinRamp(0.25, 0.80, 1) + expected = convert(AudioBuf, linspace(0.25, 0.80, test_info.sample_rate+1)) + render_output = render(ramp, dev_input, test_info) + @fact mse(render_output, expected[1:test_info.buf_size]) => + lessthan(MSE_THRESH) + render_output = render(ramp, dev_input, test_info) + @fact mse(render_output, + expected[(test_info.buf_size+1):(2*test_info.buf_size)]) => + lessthan(MSE_THRESH) + @fact (@allocated render(ramp, dev_input, test_info)) => lessthan(300) +end -gained = TestNode(test_info.buf_size) * 0.75 -render_output = render(gained, dev_input, test_info) -@test render_output == 0.75 * AudioSample[1:test_info.buf_size] - -info("Testing LinRamp...") -ramp = LinRamp(0.25, 0.80, 1) -expected = convert(AudioBuf, linspace(0.25, 0.80, test_info.sample_rate+1)) -render_output = render(ramp, dev_input, test_info) -@test mse(render_output, expected[1:test_info.buf_size]) < MSE_THRESH -render_output = render(ramp, dev_input, test_info) -@test mse(render_output, - expected[(test_info.buf_size+1):(2*test_info.buf_size)]) < MSE_THRESH -@test 300 > (@allocated render(ramp, dev_input, test_info)) +end # module TestAudioIONodes diff --git a/test/testhelpers.jl b/test/testhelpers.jl new file mode 100644 index 0000000..1b56d8c --- /dev/null +++ b/test/testhelpers.jl @@ -0,0 +1,14 @@ +# convenience function to calculate the mean-squared error +function mse(arr1::Array, arr2::Array) + @assert length(arr1) == length(arr2) + N = length(arr1) + err = 0.0 + for i in 1:N + err += (arr2[i] - arr1[i])^2 + end + err /= N +end + +issubtype(T::Type) = x -> typeof(x) <: T +lessthan(rhs) = lhs -> lhs < rhs +greaterthan(rhs) = lhs -> lhs > rhs