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