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

View file

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

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