Mercurial > hg > vamp-plugin-tester
changeset 3:0f65bb22172b
* Add basic tests for output numbers, data existence, timestamps &c
author | cannam |
---|---|
date | Tue, 17 Mar 2009 16:38:47 +0000 |
parents | c9a4bd247497 |
children | d8724c5a6d83 |
files | Makefile Test.cpp Test.h TestMultipleRuns.cpp TestOutputs.cpp TestOutputs.h Tester.cpp vamp-plugin-tester.cpp |
diffstat | 8 files changed, 379 insertions(+), 72 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile Mon Mar 16 17:43:31 2009 +0000 +++ b/Makefile Tue Mar 17 16:38:47 2009 +0000 @@ -2,7 +2,7 @@ LDFLAGS += -lvamp-hostsdk -ldl CXXFLAGS += -Wall -Wextra -OBJECTS := vamp-plugin-tester.o Tester.o Test.o TestStaticData.o TestInputExtremes.o TestMultipleRuns.o +OBJECTS := vamp-plugin-tester.o Tester.o Test.o TestStaticData.o TestInputExtremes.o TestMultipleRuns.o TestOutputs.o vamp-plugin-tester: $(OBJECTS) @@ -18,9 +18,14 @@ # DO NOT DELETE Test.o: Test.h -TestInputExtremes.o: TestInputExtremes.h +TestInputExtremes.o: TestInputExtremes.h Test.h Tester.h +TestMultipleRuns.o: TestMultipleRuns.h Test.h Tester.h +TestOutputs.o: TestOutputs.h Test.h Tester.h TestStaticData.o: TestStaticData.h Test.h Tester.h Tester.o: Tester.h Test.h vamp-plugin-tester.o: Tester.h Test.h +TestInputExtremes.o: Test.h Tester.h +TestMultipleRuns.o: Test.h Tester.h +TestOutputs.o: Test.h Tester.h TestStaticData.o: Test.h Tester.h Tester.o: Test.h
--- a/Test.cpp Mon Mar 16 17:43:31 2009 +0000 +++ b/Test.cpp Tue Mar 17 16:38:47 2009 +0000 @@ -75,6 +75,33 @@ delete[] blocks; } +float ** +Test::createTestAudio(size_t channels, size_t blocksize, size_t blocks) +{ + float **b = new float *[channels]; + for (size_t c = 0; c < channels; ++c) { + b[c] = new float[blocksize * blocks]; + for (int i = 0; i < int(blocksize * blocks); ++i) { + b[c][i] = sinf(float(i) / 10.f); + if (i == 5005 || i == 20002) { + b[c][i-2] = 0; + b[c][i-1] = -1; + b[c][i] = 1; + } + } + } + return b; +} + +void +Test::destroyTestAudio(float **b, size_t channels) +{ + for (size_t c = 0; c < channels; ++c) { + delete[] b[c]; + } + delete[] b; +} + bool Test::initDefaults(Plugin *p, size_t &channels, size_t &step, size_t &block, Results &r) @@ -94,6 +121,18 @@ return true; } +bool +Test::initAdapted(Plugin *p, size_t &channels, size_t step, size_t block, + Results &r) +{ + channels = p->getMinChannelCount(); + if (!p->initialise(channels, step, block)) { + r.push_back(error("initialisation failed")); + return false; + } + return true; +} + void Test::appendFeatures(Plugin::FeatureSet &a, const Plugin::FeatureSet &b) { @@ -124,6 +163,42 @@ return true; } +void +Test::dump(const Plugin::FeatureSet &fs) +{ + for (Plugin::FeatureSet::const_iterator fsi = fs.begin(); + fsi != fs.end(); ++fsi) { + int output = fsi->first; + std::cerr << "Output " << output << ":" << std::endl; + const Plugin::FeatureList &fl = fsi->second; + for (int i = 0; i < (int)fl.size(); ++i) { + std::cerr << " Feature " << i << ":" << std::endl; + const Plugin::Feature &f = fl[i]; + std::cerr << " Timestamp: " << (f.hasTimestamp ? "(none)" : f.timestamp.toText()) << std::endl; + std::cerr << " Duration: " << (f.hasDuration ? "(none)" : f.duration.toText()) << std::endl; + std::cerr << " Label: " << (f.label == "" ? "(none)" : f.label) << std::endl; + std::cerr << " Value: " << (f.values.empty() ? "(none)" : ""); + for (int j = 0; j < (int)f.values.size(); ++j) { + std::cerr << f.values[j] << " "; + } + std::cerr << std::endl; + } + } +} + +void +Test::dump(const Result &r, + const Plugin::FeatureSet &a, + const Plugin::FeatureSet &b) +{ + std::cerr << r.message() << std::endl; + std::cerr << "\nFirst result set:" << std::endl; + dump(a); + std::cerr << "\nSecond result set:" << std::endl; + dump(b); + std::cerr << std::endl; +} + bool operator==(const Plugin::FeatureSet &a, const Plugin::FeatureSet &b) {
--- a/Test.h Mon Mar 16 17:43:31 2009 +0000 +++ b/Test.h Tue Mar 17 16:38:47 2009 +0000 @@ -52,12 +52,12 @@ class Result { public: - enum Code { Success, Warning, Error }; + enum Code { Success, Note, Warning, Error }; Result(Code c, std::string m) : m_code(c), m_message(m) { } - Code code() { return m_code; } - std::string message() { return m_message; } + Code code() const { return m_code; } + std::string message() const { return m_message; } protected: Code m_code; @@ -65,6 +65,7 @@ }; static Result success() { return Result(Result::Success, ""); } + static Result note(std::string m) { return Result(Result::Note, m); } static Result warning(std::string m) { return Result(Result::Warning, m); } static Result error(std::string m) { return Result(Result::Error, m); } @@ -84,13 +85,24 @@ float **createBlock(size_t channels, size_t blocksize); void destroyBlock(float **blocks, size_t channels); + float **createTestAudio(size_t channels, size_t blocksize, size_t blocks); + void destroyTestAudio(float **audio, size_t channels); + bool initDefaults(Vamp::Plugin *, size_t &channels, size_t &step, size_t &block, Results &r); + bool initAdapted(Vamp::Plugin *, size_t &channels, + size_t step, size_t block, Results &r); + void appendFeatures(Vamp::Plugin::FeatureSet &a, const Vamp::Plugin::FeatureSet &b); bool allFeaturesValid(const Vamp::Plugin::FeatureSet &); // i.e. no NaN/inf + + void dump(const Vamp::Plugin::FeatureSet &); + void dump(const Result &r, + const Vamp::Plugin::FeatureSet &, + const Vamp::Plugin::FeatureSet &); }; extern bool operator==(const Vamp::Plugin::FeatureSet &a,
--- a/TestMultipleRuns.cpp Mon Mar 16 17:43:31 2009 +0000 +++ b/TestMultipleRuns.cpp Tue Mar 17 16:38:47 2009 +0000 @@ -56,38 +56,34 @@ Tester::TestRegistrar<TestInterleavedRuns> TestInterleavedRuns::m_registrar("Simultaneous interleaved runs in a single thread"); +static const size_t _step = 1000; + Test::Results TestDistinctRuns::test(string key) { Plugin::FeatureSet f[2]; int rate = 44100; Results r; - float **block = 0; - size_t channels = 0, step = 0, blocksize = 0; + float **data = 0; + size_t channels = 0; + size_t count = 100; for (int run = 0; run < 2; ++run) { auto_ptr<Plugin> p(load(key, rate)); - if (!initDefaults(p.get(), channels, step, blocksize, r)) return r; - if (!block) block = createBlock(channels, blocksize); - int idx = 0; - for (int i = 0; i < 100; ++i) { - for (size_t j = 0; j < blocksize; ++j) { - for (size_t c = 0; c < channels; ++c) { - block[c][j] = sinf(float(idx) / 10.f); - if ((i == 20 || i == 80) && (j < 2)) { - block[c][j] = float(j) - 1.f; - } - } - ++idx; - } + if (!initAdapted(p.get(), channels, _step, _step, r)) return r; + if (!data) data = createTestAudio(channels, _step, count); + for (size_t i = 0; i < count; ++i) { + float *ptr[channels]; + size_t idx = i * _step; + for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; RealTime timestamp = RealTime::frame2RealTime(idx, rate); - Plugin::FeatureSet fs = p->process(block, timestamp); + Plugin::FeatureSet fs = p->process(ptr, timestamp); appendFeatures(f[run], fs); } Plugin::FeatureSet fs = p->getRemainingFeatures(); appendFeatures(f[run], fs); } - if (block) destroyBlock(block, channels); + if (data) destroyTestAudio(data, channels); if (!(f[0] == f[1])) { r.push_back(warning("Consecutive runs with separate instances produce different results")); @@ -104,36 +100,33 @@ Plugin::FeatureSet f[2]; int rate = 44100; Results r; - float **block = 0; - size_t channels = 0, step = 0, blocksize = 0; + float **data = 0; + size_t channels = 0; + size_t count = 100; auto_ptr<Plugin> p(load(key, rate)); + for (int run = 0; run < 2; ++run) { if (run == 1) p->reset(); - if (!initDefaults(p.get(), channels, step, blocksize, r)) return r; - if (!block) block = createBlock(channels, blocksize); - int idx = 0; - for (int i = 0; i < 100; ++i) { - for (size_t j = 0; j < blocksize; ++j) { - for (size_t c = 0; c < channels; ++c) { - block[c][j] = sinf(float(idx) / 10.f); - if ((i == 20 || i == 80) && (j < 2)) { - block[c][j] = float(j) - 1.f; - } - } - ++idx; - } + if (!initAdapted(p.get(), channels, _step, _step, r)) return r; + if (!data) data = createTestAudio(channels, _step, count); + for (size_t i = 0; i < count; ++i) { + float *ptr[channels]; + size_t idx = i * _step; + for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; RealTime timestamp = RealTime::frame2RealTime(idx, rate); - Plugin::FeatureSet fs = p->process(block, timestamp); + Plugin::FeatureSet fs = p->process(ptr, timestamp); appendFeatures(f[run], fs); } Plugin::FeatureSet fs = p->getRemainingFeatures(); appendFeatures(f[run], fs); } - if (block) destroyBlock(block, channels); + if (data) destroyTestAudio(data, channels); if (!(f[0] == f[1])) { - r.push_back(warning("Consecutive runs with the same instance (using reset) produce different results")); + Result res = warning("Consecutive runs with the same instance (using reset) produce different results"); + dump(res, f[0], f[1]); + r.push_back(res); } else { r.push_back(success()); } @@ -147,32 +140,27 @@ Plugin::FeatureSet f[2]; int rate = 44100; Results r; - float **block = 0; - size_t channels = 0, step = 0, blocksize = 0; + float **data = 0; + size_t channels = 0; + size_t count = 100; + Plugin *p[2]; for (int run = 0; run < 2; ++run) { p[run] = load(key, rate); - if (!initDefaults(p[run], channels, step, blocksize, r)) { + if (!initAdapted(p[run], channels, _step, _step, r)) { delete p[run]; if (run > 0) delete p[0]; return r; } - if (!block) block = createBlock(channels, blocksize); + if (!data) data = createTestAudio(channels, _step, count); } - int idx = 0; - for (int i = 0; i < 100; ++i) { - for (size_t j = 0; j < blocksize; ++j) { - for (size_t c = 0; c < channels; ++c) { - block[c][j] = sinf(float(idx) / 10.f); - if ((i == 20 || i == 80) && (j < 2)) { - block[c][j] = float(j) - 1.f; - } - } - ++idx; - } + for (size_t i = 0; i < count; ++i) { + float *ptr[channels]; + size_t idx = i * _step; + for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; RealTime timestamp = RealTime::frame2RealTime(idx, rate); for (int run = 0; run < 2; ++run) { - Plugin::FeatureSet fs = p[run]->process(block, timestamp); + Plugin::FeatureSet fs = p[run]->process(ptr, timestamp); appendFeatures(f[run], fs); } } @@ -182,10 +170,10 @@ delete p[run]; } - if (block) destroyBlock(block, channels); + if (data) destroyTestAudio(data, channels); if (!(f[0] == f[1])) { - r.push_back(warning("Consecutive runs with the same instance (using reset) produce different results")); + r.push_back(warning("Simultaneous runs with separate instances produce different results")); } else { r.push_back(success()); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TestOutputs.cpp Tue Mar 17 16:38:47 2009 +0000 @@ -0,0 +1,157 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp Plugin Tester + Chris Cannam, cannam@all-day-breakfast.com + Centre for Digital Music, Queen Mary, University of London. + Copyright 2009 QMUL. + + This program loads a Vamp plugin and tests its susceptibility to a + number of common pitfalls, including handling of extremes of input + data. If you can think of any additional useful tests that are + easily added, please send them to me. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#include "TestOutputs.h" + +#include <vamp-hostsdk/Plugin.h> +using namespace Vamp; + +#include <set> +#include <memory> +using namespace std; + +#include <cmath> + +Tester::TestRegistrar<TestOutputNumbers> +TestOutputNumbers::m_registrar("Output number mismatching"); + +Tester::TestRegistrar<TestTimestamps> +TestTimestamps::m_registrar("Invalid or dubious timestamp usage"); + +static const size_t _step = 1000; + +Test::Results +TestOutputNumbers::test(string key) +{ + int rate = 44100; + auto_ptr<Plugin> p(load(key, rate)); + Plugin::FeatureSet f; + Results r; + float **data = 0; + size_t channels = 0; + size_t count = 100; + + if (!initAdapted(p.get(), channels, _step, _step, r)) return r; + if (!data) data = createTestAudio(channels, _step, count); + for (size_t i = 0; i < count; ++i) { + float *ptr[channels]; + size_t idx = i * _step; + for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; + RealTime timestamp = RealTime::frame2RealTime(idx, rate); + Plugin::FeatureSet fs = p->process(ptr, timestamp); + appendFeatures(f, fs); + } + Plugin::FeatureSet fs = p->getRemainingFeatures(); + appendFeatures(f, fs); + if (data) destroyTestAudio(data, channels); + + std::set<int> used; + Plugin::OutputList outputs = p->getOutputDescriptors(); + for (Plugin::FeatureSet::const_iterator i = fs.begin(); + i != fs.end(); ++i) { + int o = i->first; + used.insert(o); + if (o < 0 || o >= outputs.size()) { + r.push_back(error("Data returned on nonexistent output")); + } + } + for (int o = 0; o < outputs.size(); ++o) { + if (used.find(o) == used.end()) { + r.push_back(note("No results returned for one or more outputs")); } + } + + return r; +} + +Test::Results +TestTimestamps::test(string key) +{ + int rate = 44100; + auto_ptr<Plugin> p(load(key, rate)); + Plugin::FeatureSet f; + Results r; + float **data = 0; + size_t channels = 0; + size_t step = 0, block = 0; + size_t count = 100; + + //!!! want to ensure buffer size adapter is not used: + if (!initDefaults(p.get(), channels, step, block, r)) return r; + if (!data) data = createTestAudio(channels, block, count); + for (size_t i = 0; i < count; ++i) { + float *ptr[channels]; + size_t idx = i * step; + for (size_t c = 0; c < channels; ++c) ptr[c] = data[c] + idx; + RealTime timestamp = RealTime::frame2RealTime(idx, rate); + Plugin::FeatureSet fs = p->process(ptr, timestamp); + appendFeatures(f, fs); + } + Plugin::FeatureSet fs = p->getRemainingFeatures(); + appendFeatures(f, fs); + if (data) destroyTestAudio(data, channels); + + Plugin::OutputList outputs = p->getOutputDescriptors(); + for (Plugin::FeatureSet::const_iterator i = fs.begin(); + i != fs.end(); ++i) { + const Plugin::OutputDescriptor &o = outputs[i->first]; + const Plugin::FeatureList &fl = i->second; + for (int j = 0; j < (int)fl.size(); ++j) { + const Plugin::Feature &f = fl[j]; + switch (o.sampleType) { + case Plugin::OutputDescriptor::OneSamplePerStep: + if (f.hasTimestamp) { + r.push_back(note("Plugin returns features with timestamps on OneSamplePerStep output")); + } + if (f.hasDuration) { + r.push_back(note("Plugin returns features with durations on OneSamplePerStep output")); + } + break; + case Plugin::OutputDescriptor::FixedSampleRate: + break; + case Plugin::OutputDescriptor::VariableSampleRate: + if (!f.hasTimestamp) { + r.push_back(error("Plugin returns features with no timestamps on VariableSampleRate output")); + } + break; + } + } + } + + return r; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TestOutputs.h Tue Mar 17 16:38:47 2009 +0000 @@ -0,0 +1,67 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp Plugin Tester + Chris Cannam, cannam@all-day-breakfast.com + Centre for Digital Music, Queen Mary, University of London. + Copyright 2009 QMUL. + + This program loads a Vamp plugin and tests its susceptibility to a + number of common pitfalls, including handling of extremes of input + data. If you can think of any additional useful tests that are + easily added, please send them to me. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef _TEST_OUTPUTS_H_ +#define _TEST_OUTPUTS_H_ + +#include "Test.h" +#include "Tester.h" + +class TestOutputNumbers : public Test +{ +public: + TestOutputNumbers() : Test() { } + Results test(std::string key); + +protected: + static Tester::TestRegistrar<TestOutputNumbers> m_registrar; +}; + +class TestTimestamps : public Test +{ +public: + TestTimestamps() : Test() { } + Results test(std::string key); + +protected: + static Tester::TestRegistrar<TestTimestamps> m_registrar; +}; + + +#endif
--- a/Tester.cpp Mon Mar 16 17:43:31 2009 +0000 +++ b/Tester.cpp Tue Mar 17 16:38:47 2009 +0000 @@ -100,14 +100,14 @@ * Plugin fails when given "normal" random input (just in case!) - DONE * Plugin returns different results if another instance is - constructed and run "interleaved" with it (from same thread) + constructed and run "interleaved" with it (from same thread) - DONE * Plugin's returned timestamps do not change as expected when run with a different base timestamp for input (though there could be legitimate reasons for this) * Plugin produces different results on second run, after reset - called + called - DONE * Initial value of a parameter on plugin construction differs from its default value (i.e. plugin produces different @@ -147,7 +147,7 @@ for (Registry::const_iterator i = registry().begin(); i != registry().end(); ++i) { - std::cerr << " -- Performing test: " << i->first << std::endl; + std::cout << " -- Performing test: " << i->first << std::endl; Test *test = i->second->makeTest(); Test::Results results = test->test(m_key); @@ -157,18 +157,21 @@ switch (results[j].code()) { case Test::Result::Success: break; + case Test::Result::Note: + std::cout << " ** NOTE: " << results[j].message() << std::endl; + break; case Test::Result::Warning: - std::cerr << " ** WARNING: " << results[j].message() << std::endl; + std::cout << " ** WARNING: " << results[j].message() << std::endl; break; case Test::Result::Error: - std::cerr << " ** ERROR: " << results[j].message() << std::endl; + std::cout << " ** ERROR: " << results[j].message() << std::endl; good = false; break; } } } } catch (Test::FailedToLoadPlugin) { - std::cerr << "ERROR: Failed to load plugin (key = \"" << m_key + std::cout << "ERROR: Failed to load plugin (key = \"" << m_key << "\")" << std::endl; }
--- a/vamp-plugin-tester.cpp Mon Mar 16 17:43:31 2009 +0000 +++ b/vamp-plugin-tester.cpp Tue Mar 17 16:38:47 2009 +0000 @@ -87,31 +87,31 @@ Vamp::HostExt::PluginLoader::PluginKeyList keys = Vamp::HostExt::PluginLoader::getInstance()->listPlugins(); for (int i = 0; i < (int)keys.size(); ++i) { - cerr << "Testing plugin: " << keys[i] << endl; + cout << "Testing plugin: " << keys[i] << endl; Tester tester(keys[i]); if (tester.test()) { - cerr << name << ": All tests succeeded for this plugin" << endl; + cout << name << ": All tests succeeded for this plugin" << endl; } else { - cerr << name << ": Some tests failed for this plugin" << endl; + cout << name << ": Some tests failed for this plugin" << endl; good = false; } - cerr << endl; + cout << endl; } if (good) { - cerr << name << ": All tests succeeded" << endl; + cout << name << ": All tests succeeded" << endl; return 0; } else { - cerr << name << ": Some tests failed" << endl; + cout << name << ": Some tests failed" << endl; return 1; } } else { string key = argv[1]; Tester tester(key); if (tester.test()) { - cerr << name << ": All tests succeeded" << endl; + cout << name << ": All tests succeeded" << endl; return 0; } else { - cerr << name << ": Some tests failed" << endl; + cout << name << ": Some tests failed" << endl; return 1; } }