ports tests over to RunTest.jl
This commit is contained in:
parent
201d2852c6
commit
389636fda2
6 changed files with 253 additions and 184 deletions
1
test/REQUIRE
Normal file
1
test/REQUIRE
Normal file
|
@ -0,0 +1 @@
|
|||
FactCheck
|
18
test/runtests.jl
Executable file
18
test/runtests.jl
Executable 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()
|
28
test/test.jl
28
test/test.jl
|
@ -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
|
|
@ -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)))
|
||||
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
|
||||
|
||||
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)))
|
||||
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 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)
|
||||
facts("WAV file write/read") do
|
||||
fname = "test/sinwave.wav"
|
||||
|
||||
info("Testing wav file write/read")
|
||||
samplerate = 44100
|
||||
freq = 440
|
||||
t = [0 : 2 * samplerate - 1] / samplerate
|
||||
phase = 2 * pi * freq * t
|
||||
reference = int16((2 ^ 15 - 1) * sin(phase))
|
||||
|
||||
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
|
||||
af_open(fname, "w") do f
|
||||
write(f, reference)
|
||||
end
|
||||
end
|
||||
|
||||
af_open(fname) do f
|
||||
@test f.sfinfo.channels == 1
|
||||
@test f.sfinfo.frames == 2 * samplerate
|
||||
af_open(fname) do f
|
||||
@fact f.sfinfo.channels => 1
|
||||
@fact f.sfinfo.frames => 2 * samplerate
|
||||
actual = read(f, 2 * samplerate)
|
||||
@test_approx_eq(reference, actual)
|
||||
@fact reference => mse(actual)
|
||||
end
|
||||
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("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
|
||||
|
|
|
@ -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
14
test/testhelpers.jl
Normal 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
|
Loading…
Reference in a new issue