ports tests over to RunTest.jl

This commit is contained in:
Spencer Russell 2014-06-30 21:24:29 -04:00
parent 201d2852c6
commit 389636fda2
6 changed files with 253 additions and 184 deletions

1
test/REQUIRE Normal file
View file

@ -0,0 +1 @@
FactCheck

18
test/runtests.jl Executable file
View file

@ -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()

View file

@ -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

View file

@ -1,10 +1,33 @@
using Base.Test module TestAudioIO
using FactCheck
using AudioIO using AudioIO
import AudioIO.AudioBuf import AudioIO.AudioBuf
const TEST_SAMPLERATE = 44100 const TEST_SAMPLERATE = 44100
const TEST_BUF_SIZE = 1024 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 type TestAudioStream <: AudioIO.AudioStream
root::AudioIO.AudioMixer root::AudioIO.AudioMixer
info::AudioIO.DeviceInfo info::AudioIO.DeviceInfo
@ -28,69 +51,78 @@ end
#### Test playing back various vector types #### #### Test playing back various vector types ####
# data shared between tests, for convenience facts("Array playback") do
t = linspace(0, 2, 2 * 44100) # data shared between tests, for convenience
phase = 2pi * 100 * t t = linspace(0, 2, 2 * 44100)
phase = 2pi * 100 * t
## Test Float32 arrays, this is currently the native audio playback format ## Test Float32 arrays, this is currently the native audio playback format
info("Testing Playing Float32 arrays...") context("Playing Float32 arrays") do
f32 = convert(Array{Float32}, sin(phase)) f32 = convert(Array{Float32}, sin(phase))
test_stream = TestAudioStream() test_stream = TestAudioStream()
player = play(f32, test_stream) player = play(f32, test_stream)
@test process(test_stream) == f32[1:TEST_BUF_SIZE] @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...") context("Playing Int8(Signed) arrays") do
f64 = convert(Array{Float64}, sin(phase)) i8 = Int8[-127:127]
test_stream = TestAudioStream() test_stream = TestAudioStream()
player = play(f64, test_stream) player = play(i8, test_stream)
@test process(test_stream) == convert(AudioBuf, f64[1:TEST_BUF_SIZE]) @fact process(test_stream)[1:255] =>
mse(convert(AudioBuf, linspace(-1.0, 1.0, 255)))
end
info("Testing Playing Int8(Signed) arrays...") context("Playing Uint8(Unsigned) arrays") do
i8 = Int8[-127:127] # for unsigned 8-bit audio silence is represented as 128, so the symmetric range
test_stream = TestAudioStream() # is 1-255
player = play(i8, test_stream) ui8 = Uint8[1:255]
@test_approx_eq(process(test_stream)[1:255], test_stream = TestAudioStream()
convert(AudioBuf, linspace(-1.0, 1.0, 255))) player = play(ui8, test_stream)
@fact process(test_stream)[1:255] =>
info("Testing Playing Uint8(Unsigned) arrays...") mse(convert(AudioBuf, linspace(-1.0, 1.0, 255)))
# for unsigned 8-bit audio silence is represented as 128, so the symmetric range end
# 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)
end end
af_open(fname) do f facts("AudioNode Stopping") do
@test f.sfinfo.channels == 1 test_stream = TestAudioStream()
@test f.sfinfo.frames == 2 * samplerate node = SinOsc(440)
actual = read(f, 2 * samplerate) play(node, test_stream)
@test_approx_eq(reference, actual) process(test_stream)
stop(node)
@fact process(test_stream) => zeros(AudioIO.AudioSample, TEST_BUF_SIZE)
end end
info("Testing Audio Device Listing...") facts("WAV file write/read") do
# there aren't any devices on the Travis machine so just test that this doesn't crash fname = "test/sinwave.wav"
get_audio_devices()
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

View file

@ -1,4 +1,6 @@
using Base.Test module TestAudioIONodes
using FactCheck
using AudioIO using AudioIO
import AudioIO.AudioSample import AudioIO.AudioSample
import AudioIO.AudioBuf import AudioIO.AudioBuf
@ -7,6 +9,8 @@ import AudioIO.AudioNode
import AudioIO.DeviceInfo import AudioIO.DeviceInfo
import AudioIO.render import AudioIO.render
include("testhelpers.jl")
# A TestNode just renders out 1:buf_size each frame # A TestNode just renders out 1:buf_size each frame
type TestRenderer <: AudioRenderer type TestRenderer <: AudioRenderer
buf::AudioBuf buf::AudioBuf
@ -25,117 +29,145 @@ end
test_info = DeviceInfo(44100, 512) test_info = DeviceInfo(44100, 512)
dev_input = zeros(AudioSample, test_info.buf_size) dev_input = zeros(AudioSample, test_info.buf_size)
# first validate that the TestNode doesn't allocate so it doesn't mess up our facts("Validating TestNode allocation") do
# other tests # first validate that the TestNode doesn't allocate so it doesn't mess up our
test = TestNode(test_info.buf_size) # other tests
# JIT test = TestNode(test_info.buf_size)
render(test, dev_input, test_info) # JIT
@test ((@allocated render(test, dev_input, test_info)) < 20) render(test, dev_input, test_info)
@fact (@allocated render(test, dev_input, test_info)) => lessthan(20)
end
#### AudioMixer Tests #### #### AudioMixer Tests ####
# TODO: there should be a setup/teardown mechanism and some way to isolate # TODO: there should be a setup/teardown mechanism and some way to isolate
# tests # tests
info("Testing AudioMixer...") facts("AudioMixer") do
mix = AudioMixer() context("0 Input Mixer") do
render_output = render(mix, dev_input, test_info) mix = AudioMixer()
@test render_output == AudioSample[] 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) context("1 Input Mixer") do
mix = AudioMixer([testnode]) testnode = TestNode(test_info.buf_size)
render_output = render(mix, dev_input, test_info) mix = AudioMixer([testnode])
@test render_output == AudioSample[1:test_info.buf_size] render_output = render(mix, dev_input, test_info)
@test 100 > (@allocated 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) context("2 Input Mixer") do
test2 = TestNode(test_info.buf_size) test1 = TestNode(test_info.buf_size)
mix = AudioMixer([test1, test2]) test2 = TestNode(test_info.buf_size)
render_output = render(mix, dev_input, test_info) mix = AudioMixer([test1, test2])
# make sure the two inputs are being added together render_output = render(mix, dev_input, test_info)
@test render_output == 2 * AudioSample[1:test_info.buf_size] # 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(mix)
stop(test1) render_output = render(mix, dev_input, test_info)
render_output = render(mix, dev_input, test_info) @fact render_output => AudioSample[]
# make sure the two inputs are being added together end
@test render_output == AudioSample[1:test_info.buf_size] end
stop(mix) MSE_THRESH = 1e-7
render_output = render(mix, dev_input, test_info)
@test render_output == AudioSample[]
# TODO: I think we can do better than this facts("SinOSC") do
const MSE_THRESH = 1e-7 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...") context("Testing SinOsc with signal input") do
freq = 440 t = linspace(0, 1, test_info.sample_rate+1)
# note that this range includes the end, which is why there are sample_rate+1 samples f = 440 .- t .* (440-110)
t = linspace(0, 1, test_info.sample_rate+1) dt = 1 / test_info.sample_rate
test_vect = convert(AudioBuf, sin(2pi * t * freq)) # NOTE - this treats the phase as constant at each sample, which isn't strictly
osc = SinOsc(freq) # true. Unfortunately doing this correctly requires knowing more about the
render_output = render(osc, dev_input, test_info) # modulating signal and doing the real integral
@test mse(render_output, test_vect[1:test_info.buf_size]) < MSE_THRESH phase = cumsum(2pi * dt .* f)
render_output = render(osc, dev_input, test_info) unshift!(phase, 0)
@test mse(render_output, expected = convert(AudioBuf, sin(phase))
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[]
info("Testing SinOsc with signal input") freq = LinRamp(440, 110, 1)
t = linspace(0, 1, test_info.sample_rate+1) osc = SinOsc(freq)
f = 440 .- t .* (440-110) render_output = render(osc, dev_input, test_info)
dt = 1 / test_info.sample_rate @fact mse(render_output, expected[1:test_info.buf_size]) =>
# NOTE - this treats the phase as constant at each sample, which isn't strictly lessthan(MSE_THRESH)
# true. Unfortunately doing this correctly requires knowing more about the render_output = render(osc, dev_input, test_info)
# modulating signal and doing the real integral @fact mse(render_output,
phase = cumsum(2pi * dt .* f) expected[test_info.buf_size+1:2*test_info.buf_size]) =>
unshift!(phase, 0) lessthan(MSE_THRESH)
expected = convert(AudioBuf, sin(phase)) # 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) facts("ArrayPlayer") do
osc = SinOsc(freq) context("playing long sample") do
render_output = render(osc, dev_input, test_info) v = rand(AudioSample, 44100)
@test mse(render_output, expected[1:test_info.buf_size]) < MSE_THRESH player = ArrayPlayer(v)
render_output = render(osc, dev_input, test_info) render_output = render(player, dev_input, test_info)
@test mse(render_output, @fact render_output => v[1:test_info.buf_size]
expected[test_info.buf_size+1:2*test_info.buf_size]) < MSE_THRESH render_output = render(player, dev_input, test_info)
# give a bigger budget here because we're rendering 2 nodes @fact render_output => v[(test_info.buf_size + 1) : (2*test_info.buf_size)]
@test 500 > (@allocated render(osc, dev_input, test_info)) @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...") context("testing end of vector") do
v = rand(AudioSample, 44100) # give a vector just a bit larger than 1 buffer size
player = ArrayPlayer(v) v = rand(AudioSample, test_info.buf_size + 1)
render_output = render(player, dev_input, test_info) player = ArrayPlayer(v)
@test render_output == v[1:test_info.buf_size] render(player, dev_input, test_info)
render_output = render(player, dev_input, test_info) render_output = render(player, dev_input, test_info)
@test render_output == v[(test_info.buf_size + 1) : (2*test_info.buf_size)] @fact render_output => v[test_info.buf_size+1:end]
@test 200 > (@allocated render(player, dev_input, test_info)) end
stop(player) end
render_output = render(player, dev_input, test_info)
@test render_output == AudioSample[]
# give a vector just a bit larger than 1 buffer size facts("Gain") do
v = rand(AudioSample, test_info.buf_size + 1) gained = TestNode(test_info.buf_size) * 0.75
player = ArrayPlayer(v) render_output = render(gained, dev_input, test_info)
render(player, dev_input, test_info) @fact render_output => 0.75 * AudioSample[1:test_info.buf_size]
render_output = render(player, dev_input, test_info) end
@test render_output == v[test_info.buf_size+1: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 end # module TestAudioIONodes
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))

14
test/testhelpers.jl Normal file
View file

@ -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