From 7934a72b243bea52bd4a97b4a83a3d331a8e57dc Mon Sep 17 00:00:00 2001 From: Hannes Matuschek Date: Thu, 24 Jul 2014 00:01:17 +0200 Subject: [PATCH] Added missing examples and unit tests. --- examples/CMakeLists.txt | 14 +++ examples/sdr_rds.cc | 82 +++++++++++++++++ examples/sdr_spec.cc | 64 ++++++++++++++ examples/sdr_wavplay.cc | 41 +++++++++ test/CMakeLists.txt | 7 ++ test/buffertest.cc | 136 ++++++++++++++++++++++++++++ test/buffertest.hh | 22 +++++ test/coretest.cc | 37 ++++++++ test/coretest.hh | 18 ++++ test/coreutilstest.cc | 90 +++++++++++++++++++ test/coreutilstest.hh | 19 ++++ test/cputime.cc | 46 ++++++++++ test/cputime.hh | 31 +++++++ test/main.cc | 21 +++++ test/unittest.cc | 192 ++++++++++++++++++++++++++++++++++++++++ test/unittest.hh | 159 +++++++++++++++++++++++++++++++++ 16 files changed, 979 insertions(+) create mode 100644 examples/CMakeLists.txt create mode 100644 examples/sdr_rds.cc create mode 100644 examples/sdr_spec.cc create mode 100644 examples/sdr_wavplay.cc create mode 100644 test/CMakeLists.txt create mode 100644 test/buffertest.cc create mode 100644 test/buffertest.hh create mode 100644 test/coretest.cc create mode 100644 test/coretest.hh create mode 100644 test/coreutilstest.cc create mode 100644 test/coreutilstest.hh create mode 100644 test/cputime.cc create mode 100644 test/cputime.hh create mode 100644 test/main.cc create mode 100644 test/unittest.cc create mode 100644 test/unittest.hh diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..2421087 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,14 @@ +IF(SDR_WITH_QT5 AND SDR_WITH_FFTW AND SDR_WITH_PORTAUDIO) + add_executable(sdr_spec sdr_spec.cc) + target_link_libraries(sdr_spec ${LIBS} ${QT_LIBRARIES} libsdr libsdr-gui ) +ENDIF(SDR_WITH_QT5 AND SDR_WITH_FFTW AND SDR_WITH_PORTAUDIO) + +IF(SDR_WITH_PORTAUDIO) + add_executable(sdr_wavplay sdr_wavplay.cc) + target_link_libraries(sdr_wavplay ${LIBS} libsdr) +ENDIF(SDR_WITH_PORTAUDIO) + +IF(SDR_WITH_QT5 AND SDR_WITH_FFTW AND SDR_WITH_PORTAUDIO) + add_executable(sdr_rds sdr_rds.cc) + target_link_libraries(sdr_rds ${LIBS} ${QT_LIBRARIES} libsdr libsdr-gui) +ENDIF(SDR_WITH_QT5 AND SDR_WITH_FFTW AND SDR_WITH_PORTAUDIO) diff --git a/examples/sdr_rds.cc b/examples/sdr_rds.cc new file mode 100644 index 0000000..87590cf --- /dev/null +++ b/examples/sdr_rds.cc @@ -0,0 +1,82 @@ +#include "sdr.hh" +#include "rtlsource.hh" +#include "baseband.hh" +#include "autocast.hh" +#include "gui/gui.hh" +#include "logger.hh" +#include + +#include +#include +#include + +using namespace sdr; + +static void __sigint_handler(int signo) { + // On SIGINT -> stop queue properly + Queue::get().stop(); +} + + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + std::cerr << "USAGE: sdr_rds FREQUENCY" << std::endl; + return -1; + } + double freq = atof(argv[1]); + + PortAudio::init(); + Queue &queue = Queue::get(); + + // Register handler: + signal(SIGINT, __sigint_handler); + + QApplication app(argc, argv); + + QMainWindow *win = new QMainWindow(); + gui::Spectrum *spec = new gui::Spectrum(2, 1024, 5); + gui::SpectrumView *spec_view = new gui::SpectrumView(spec); + spec_view->setMindB(-200); + win->setCentralWidget(spec_view); + win->setMinimumSize(640, 240); + + win->show(); + + sdr::Logger::get().addHandler(new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); + + // Assemble processing chain + //RTLSource src(freq, 1e6); + WavSource src(argv[1]); + AutoCast< std::complex > cast; + IQBaseBand baseband(0, 200e3, 16, 5); + FMDemod demod; + BaseBand mono(0, 15e3, 16, 6); + BaseBand pilot(19e3, 5e2, 16, 84); + BaseBand rds(57e3, 3e3, 16, 84); + PortSink sink; + + src.connect(&cast, true); + cast.connect(&baseband, true); + //src.connect(&baseband, true); + baseband.connect(&demod, true); + demod.connect(&mono); + demod.connect(&pilot); + demod.connect(&rds); + + mono.connect(&sink); + mono.connect(spec); + + //queue.addStart(&src, &RTLSource::start); + //queue.addStop(&src, &RTLSource::stop); + queue.addIdle(&src, &WavSource::next); + + queue.start(); + app.exec(); + queue.stop(); + queue.wait(); + + PortAudio::terminate(); + + return 0; +} diff --git a/examples/sdr_spec.cc b/examples/sdr_spec.cc new file mode 100644 index 0000000..0830c98 --- /dev/null +++ b/examples/sdr_spec.cc @@ -0,0 +1,64 @@ +#include "sdr.hh" +#include "rtlsource.hh" +#include "baseband.hh" +#include "utils.hh" +#include "gui/gui.hh" +#include +#include "portaudio.hh" + +#include +#include +#include + +using namespace sdr; + +static void __sigint_handler(int signo) { + // On SIGINT -> stop queue properly + Queue::get().stop(); +} + + +int main(int argc, char *argv[]) +{ + Queue &queue = Queue::get(); + + // Register handler: + signal(SIGINT, __sigint_handler); + + PortAudio::init(); + + QApplication app(argc, argv); + + QMainWindow *win = new QMainWindow(); + gui::Spectrum *spec = new gui::Spectrum(2, 1024, 5); + gui::WaterFallView *spec_view = new gui::WaterFallView(spec); + win->setCentralWidget(spec_view); + win->setMinimumSize(640, 240); + + win->show(); + + // Assemble processing chain + PortSource< int16_t > src(44100.0, 2048); + AGC agc; + //IQBaseBand baseband(0, 500e3, 8, 5); + //AMDemod demod; + PortSink sink; + + src.connect(&agc, true); + agc.connect(&sink, true); + //baseband.connect(&demod); + //demod.connect(&sink, true); + src.connect(spec); + + queue.addIdle(&src, &PortSource< int16_t >::next); + + queue.start(); + app.exec(); + + queue.stop(); + queue.wait(); + + PortAudio::terminate(); + + return 0; +} diff --git a/examples/sdr_wavplay.cc b/examples/sdr_wavplay.cc new file mode 100644 index 0000000..e18510f --- /dev/null +++ b/examples/sdr_wavplay.cc @@ -0,0 +1,41 @@ +#include "sdr.hh" +#include + +using namespace sdr; + + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + std::cerr << "USAGE: sdr_wavplay FILENAME" << std::endl; + return -1; + } + + Queue &queue = Queue::get(); + + PortAudio::init(); + + WavSource src(argv[1]); + if (! src.isOpen() ) { + std::cerr << "Can not open file " << argv[1] << std::endl; + return -1; + } + queue.addIdle(&src, &WavSource::next); + + RealPart to_real; + PortSink sink; + + if (src.isReal()) { + src.connect(&sink, true); + } else { + src.connect(&to_real, true); + to_real.connect(&sink, true); + } + + // run... + queue.start(); + queue.wait(); + + PortAudio::terminate(); + return 0; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..0e16800 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,7 @@ +set(test_SOURCES main.cc + cputime.cc unittest.cc buffertest.cc coreutilstest.cc coretest.cc) +set(test_HEADERS + cputime.hh unittest.hh buffertest.hh coreutilstest.hh coretest.hh) + +add_executable(sdr_test ${test_SOURCES}) +target_link_libraries(sdr_test ${LIBS} libsdr) diff --git a/test/buffertest.cc b/test/buffertest.cc new file mode 100644 index 0000000..bf8a9ca --- /dev/null +++ b/test/buffertest.cc @@ -0,0 +1,136 @@ +#include "buffertest.hh" +#include +using namespace sdr; +using namespace UnitTest; + +BufferTest::~BufferTest() { } + + +void +BufferTest::testRefcount() { + Buffer a(3); + + // Test Direct reference counting + UT_ASSERT_EQUAL(a.refCount(), 1); + UT_ASSERT(a.isUnused()); + + { + Buffer b(a); + UT_ASSERT_EQUAL(a.refCount(), 1); + UT_ASSERT_EQUAL(b.refCount(), 1); + UT_ASSERT(a.isUnused()); + UT_ASSERT(b.isUnused()); + } + + { + Buffer b(a); + b.ref(); + UT_ASSERT_EQUAL(a.refCount(), 2); + UT_ASSERT_EQUAL(b.refCount(), 2); + UT_ASSERT(!a.isUnused()); + UT_ASSERT(!b.isUnused()); + b.unref(); + } + + UT_ASSERT_EQUAL(a.refCount(), 1); + UT_ASSERT(a.isUnused()); + + // Test indirect reference counting + std::list buffers; + buffers.push_back(a); + + UT_ASSERT_EQUAL(a.refCount(), 1); + UT_ASSERT(a.isUnused()); + + // check direct referenceing + UT_ASSERT_EQUAL(buffers.back().refCount(), 1); + UT_ASSERT(buffers.back().isUnused()); + + buffers.pop_back(); + UT_ASSERT_EQUAL(a.refCount(), 1); + UT_ASSERT(a.isUnused()); +} + + +void +BufferTest::testReinterprete() { + // Check handle interleaved numbers as real & imag part + Buffer real_buffer(4); + + real_buffer[0] = 1; + real_buffer[1] = 2; + real_buffer[2] = 3; + real_buffer[3] = 4; + + // Cast to complex char + Buffer< std::complex > cmplx_buffer(real_buffer); + // Check size + UT_ASSERT_EQUAL(real_buffer.size()/2, cmplx_buffer.size()); + // Check content + UT_ASSERT_EQUAL(cmplx_buffer[0], std::complex(1,2)); + UT_ASSERT_EQUAL(cmplx_buffer[1], std::complex(3,4)); +} + + +void +BufferTest::testRawRingBuffer() { + RawBuffer a(3), b(3); + RawRingBuffer ring(3); + + memcpy(a.data(), "abc", 3); + + // Check if ring is empty + UT_ASSERT_EQUAL(ring.bytesLen(), size_t(0)); + UT_ASSERT_EQUAL(ring.bytesFree(), size_t(3)); + + // Put a byte + UT_ASSERT(ring.put(RawBuffer(a, 0, 1))); + UT_ASSERT_EQUAL(ring.bytesLen(), size_t(1)); + UT_ASSERT_EQUAL(ring.bytesFree(), size_t(2)); + + // Put two more bytes + UT_ASSERT(ring.put(RawBuffer(a, 1, 2))); + UT_ASSERT_EQUAL(ring.bytesLen(), size_t(3)); + UT_ASSERT_EQUAL(ring.bytesFree(), size_t(0)); + + // Now, the ring is full, any further put should fail + UT_ASSERT(!ring.put(a)); + + // Take a byte from ring + UT_ASSERT(ring.take(b, 1)); + UT_ASSERT_EQUAL(ring.bytesLen(), size_t(2)); + UT_ASSERT_EQUAL(ring.bytesFree(), size_t(1)); + UT_ASSERT_EQUAL(*(b.data()), 'a'); + + // Take another byte + UT_ASSERT(ring.take(b, 1)); + UT_ASSERT_EQUAL(ring.bytesLen(), size_t(1)); + UT_ASSERT_EQUAL(ring.bytesFree(), size_t(2)); + UT_ASSERT_EQUAL(*(b.data()), 'b'); + + // Put two more back + UT_ASSERT(ring.put(RawBuffer(a, 0, 2))); + UT_ASSERT_EQUAL(ring.bytesLen(), size_t(3)); + UT_ASSERT_EQUAL(ring.bytesFree(), size_t(0)); + + // Take all + UT_ASSERT(ring.take(b, 3)); + UT_ASSERT_EQUAL(ring.bytesLen(), size_t(0)); + UT_ASSERT_EQUAL(ring.bytesFree(), size_t(3)); + UT_ASSERT(0 == memcmp(b.data(), "cab", 3)); + +} + + +TestSuite * +BufferTest::suite() { + TestSuite *suite = new TestSuite("Buffer Tests"); + + suite->addTest(new TestCaller( + "reference counter", &BufferTest::testRefcount)); + suite->addTest(new TestCaller( + "re-interprete case", &BufferTest::testReinterprete)); + suite->addTest(new TestCaller( + "raw ring buffer", &BufferTest::testRawRingBuffer)); + return suite; +} diff --git a/test/buffertest.hh b/test/buffertest.hh new file mode 100644 index 0000000..2301162 --- /dev/null +++ b/test/buffertest.hh @@ -0,0 +1,22 @@ +#ifndef __SDR_TEST_BUFFERTEST_HH__ +#define __SDR_TEST_BUFFERTEST_HH__ + +#include "buffer.hh" +#include "unittest.hh" + + +class BufferTest : public UnitTest::TestCase +{ +public: + virtual ~BufferTest(); + + void testRefcount(); + void testReinterprete(); + void testRawRingBuffer(); + + +public: + static UnitTest::TestSuite *suite(); +}; + +#endif // BUFFERTEST_HH diff --git a/test/coretest.cc b/test/coretest.cc new file mode 100644 index 0000000..20bad83 --- /dev/null +++ b/test/coretest.cc @@ -0,0 +1,37 @@ +#include "coretest.hh" +#include "sdr.hh" + +using namespace sdr; + + +CoreTest::~CoreTest() { /* pass... */ } + + +void +CoreTest::testShiftOperators() { + // Test if shift can be used as multiplication or division by a power of two + // (even on negative integers) + int a=128, b=-128; + // On positive integers (should work always) + UT_ASSERT_EQUAL(a>>1, 64); + UT_ASSERT_EQUAL(a<<1, 256); + UT_ASSERT_EQUAL(a>>0, 128); + UT_ASSERT_EQUAL(a<<0, 128); + + UT_ASSERT_EQUAL(b>>1, -64); + UT_ASSERT_EQUAL(b<<1, -256); + UT_ASSERT_EQUAL(b>>0, -128); + UT_ASSERT_EQUAL(b<<0, -128); +} + + + +UnitTest::TestSuite * +CoreTest::suite() { + UnitTest::TestSuite *suite = new UnitTest::TestSuite("Core operations"); + + suite->addTest(new UnitTest::TestCaller( + "shift operators", &CoreTest::testShiftOperators)); + + return suite; +} diff --git a/test/coretest.hh b/test/coretest.hh new file mode 100644 index 0000000..126cb1d --- /dev/null +++ b/test/coretest.hh @@ -0,0 +1,18 @@ +#ifndef __SDT_TEST_CORETEST_HH__ +#define __SDT_TEST_CORETEST_HH__ + +#include "unittest.hh" + +class CoreTest : public UnitTest::TestCase +{ +public: + virtual ~CoreTest(); + + void testShiftOperators(); + + +public: + static UnitTest::TestSuite *suite(); +}; + +#endif // __SDT_TEST_CORETEST_HH__ diff --git a/test/coreutilstest.cc b/test/coreutilstest.cc new file mode 100644 index 0000000..e8a228c --- /dev/null +++ b/test/coreutilstest.cc @@ -0,0 +1,90 @@ +#include "coreutilstest.hh" +#include "config.hh" +#include "utils.hh" +#include "combine.hh" + +using namespace sdr; +using namespace UnitTest; + +CoreUtilsTest::~CoreUtilsTest() { } + +void +CoreUtilsTest::testUChar2Char() { + Buffer uchar_buffer(3); + uchar_buffer[0] = 0u; + uchar_buffer[1] = 128u; + uchar_buffer[2] = 255u; + + // Assemble cast instance and configure it + UnsignedToSigned cast; + cast.config(Config(Config::Type_u8, 1, 3, 1)); + // Perform in-place operation + cast.handleBuffer(uchar_buffer, true); + + // Reinterprete uchar buffer as char buffer + Buffer char_buffer(uchar_buffer); + // Check values + UT_ASSERT_EQUAL(char_buffer[0], (signed char)-128); + UT_ASSERT_EQUAL(char_buffer[1], (signed char)0); + UT_ASSERT_EQUAL(char_buffer[2], (signed char)127); +} + +void +CoreUtilsTest::testUShort2Short() { + Buffer uchar_buffer(3); + uchar_buffer[0] = 0u; + uchar_buffer[1] = 128u; + uchar_buffer[2] = 255u; + + // Assemble cast instance and configure it + UnsignedToSigned cast; + cast.config(Config(Config::Type_u16, 1, 3, 1)); + // Perform in-place operation + cast.handleBuffer(uchar_buffer, true); + + // Reinterprete uchar buffer as char buffer + Buffer char_buffer(uchar_buffer); + // Check values + UT_ASSERT_EQUAL(char_buffer[0], (int16_t)-128); + UT_ASSERT_EQUAL(char_buffer[1], (int16_t)0); + UT_ASSERT_EQUAL(char_buffer[2], (int16_t)127); +} + + +void +CoreUtilsTest::testInterleave() { + Interleave interl(2); + DebugStore sink; + Buffer a(3); + + interl.sink(0)->config(Config(Config::Type_s16, 1, a.size(), 1)); + interl.sink(1)->config(Config(Config::Type_s16, 1, a.size(), 1)); + interl.connect(&sink, true); + + // Send some data + a[0] = 1; a[1] = 2; a[2] = 3; interl.sink(0)->process(a, false); + a[0] = 4; a[1] = 5; a[2] = 6; interl.sink(1)->process(a, false); + + // Check content of sink + UT_ASSERT_EQUAL(sink.buffer()[0], (int16_t)1); + UT_ASSERT_EQUAL(sink.buffer()[1], (int16_t)4); + UT_ASSERT_EQUAL(sink.buffer()[2], (int16_t)2); + UT_ASSERT_EQUAL(sink.buffer()[3], (int16_t)5); + UT_ASSERT_EQUAL(sink.buffer()[4], (int16_t)3); + UT_ASSERT_EQUAL(sink.buffer()[5], (int16_t)6); +} + + +TestSuite * +CoreUtilsTest::suite() { + TestSuite *suite = new TestSuite("Core Utils"); + + suite->addTest(new TestCaller( + "cast uint8_t -> int8_t", &CoreUtilsTest::testUChar2Char)); + suite->addTest(new TestCaller( + "cast uint16_t -> int16_t", &CoreUtilsTest::testUChar2Char)); + suite->addTest(new TestCaller( + "Interleave", &CoreUtilsTest::testInterleave)); + + return suite; +} diff --git a/test/coreutilstest.hh b/test/coreutilstest.hh new file mode 100644 index 0000000..c88f137 --- /dev/null +++ b/test/coreutilstest.hh @@ -0,0 +1,19 @@ +#ifndef __SDR_TEST_COREUTILSTEST_HH__ +#define __SDR_TEST_COREUTILSTEST_HH__ + +#include "unittest.hh" + +class CoreUtilsTest : public UnitTest::TestCase +{ +public: + virtual ~CoreUtilsTest(); + + void testUChar2Char(); + void testUShort2Short(); + void testInterleave(); + +public: + static UnitTest::TestSuite *suite(); +}; + +#endif diff --git a/test/cputime.cc b/test/cputime.cc new file mode 100644 index 0000000..6272394 --- /dev/null +++ b/test/cputime.cc @@ -0,0 +1,46 @@ +#include "cputime.hh" + +using namespace UnitTest; + + +CpuTime::CpuTime() +{ +} + + +void +CpuTime::start() +{ + this->_clocks.push_back(clock()); +} + + +double +CpuTime::stop() +{ + // measure time. + clock_t end = clock(); + + // Get time-diff since start: + double dt = end-this->_clocks.back(); + dt /= CLOCKS_PER_SEC; + + // Remove start time from stack: + this->_clocks.pop_back(); + + // Return delta t: + return dt; +} + + +double +CpuTime::getTime() +{ + clock_t end = clock(); + + // get diff: + double dt = end - this->_clocks.back(); + dt /= CLOCKS_PER_SEC; + + return dt; +} diff --git a/test/cputime.hh b/test/cputime.hh new file mode 100644 index 0000000..77c6597 --- /dev/null +++ b/test/cputime.hh @@ -0,0 +1,31 @@ +#ifndef __SDR_CPUTIME_HH__ +#define __SDR_CPUTIME_HH__ + +#include +#include + + +namespace UnitTest { + +/** A utility class to measure the CPU time used by some algorithms. */ +class CpuTime +{ +public: + /** Constructs a new CPU time clock. */ + CpuTime(); + + /** Start the clock. */ + void start(); + /** Stops the clock and returns the time in seconds. */ + double stop(); + /** Retruns the current time of the current clock. */ + double getTime(); + +protected: + /** The stack of start times. */ + std::list< clock_t > _clocks; +}; + +} + +#endif // CPUTIME_HH diff --git a/test/main.cc b/test/main.cc new file mode 100644 index 0000000..7e8db1e --- /dev/null +++ b/test/main.cc @@ -0,0 +1,21 @@ +#include "coretest.hh" +#include "coreutilstest.hh" +#include "unittest.hh" +#include "buffertest.hh" +#include + +using namespace sdr; + + +int main(int argc, char *argv[]) { + + UnitTest::TestRunner runner(std::cout); + + runner.addSuite(CoreTest::suite()); + runner.addSuite(BufferTest::suite()); + runner.addSuite(CoreUtilsTest::suite()); + + runner(); + + return 0; +} diff --git a/test/unittest.cc b/test/unittest.cc new file mode 100644 index 0000000..2665cdc --- /dev/null +++ b/test/unittest.cc @@ -0,0 +1,192 @@ +#include "unittest.hh" +#include +#include +#include +#include +#include "cputime.hh" + +using namespace UnitTest; + + + +/* ********************************************************************************************* * + * Implementation of TestFailure + * ********************************************************************************************* */ +TestFailure::TestFailure(const std::string &message) throw() + : message(message) +{ + // pass... +} + +TestFailure::~TestFailure() throw() +{ + // Pass... +} + +const char * +TestFailure::what() const throw () +{ + return this->message.c_str(); +} + + + + +/* ********************************************************************************************* * + * Implementation of TestCase + * ********************************************************************************************* */ +void +TestCase::setUp() +{ + // Pass... +} + + +void +TestCase::tearDown() +{ + // Pass... +} + + +void +TestCase::assertTrue(bool test, const std::string &file, size_t line) +{ + if (! test) + { + std::stringstream str; + str << "Assert failed in " << file << " in line " << line; + throw TestFailure(str.str()); + } +} + + + +/* ********************************************************************************************* * + * Implementation of TestSuite + * ********************************************************************************************* */ +TestSuite::TestSuite(const std::string &desc) + : description(desc) +{ + // pass... +} + + +TestSuite::~TestSuite() +{ + // Free callers: + for (std::list::iterator caller=this->tests.begin(); + caller != this->tests.end(); caller++) { + delete *caller; + } +} + + +void +TestSuite::addTest(TestCallerInterface *test) +{ + this->tests.push_back(test); +} + + +const std::string & +TestSuite::getDescription() +{ + return this->description; +} + + +TestSuite::iterator +TestSuite::begin() +{ + return this->tests.begin(); +} + + +TestSuite::iterator +TestSuite::end() +{ + return this->tests.end(); +} + + + + +/* ********************************************************************************************* * + * Implementation of TestSuite + * ********************************************************************************************* */ +TestRunner::TestRunner(std::ostream &stream) + : stream(stream) +{ + // Pass... +} + + +TestRunner::~TestRunner() +{ + // Free suites: + for (std::list::iterator suite = this->suites.begin(); + suite != this->suites.end(); suite++) + { + delete *suite; + } +} + + +void +TestRunner::addSuite(TestSuite *suite) +{ + this->suites.push_back(suite); +} + + +void +TestRunner::operator ()() +{ + size_t tests_run = 0; + size_t tests_failed = 0; + size_t tests_error = 0; + + for (std::list::iterator suite = this->suites.begin(); + suite != this->suites.end(); suite++) + { + // Dump Suite description + this->stream << "Suite: " << (*suite)->getDescription() << std::endl; + + // For each test in suite: + for (TestSuite::iterator test = (*suite)->begin(); test != (*suite)->end(); test++) + { + this->stream << " test: " << (*test)->getDescription() << ": "; + + try + { + tests_run++; + CpuTime clock; clock.start(); + // Run test + (**test)(); + this->stream << " ok (" << clock.stop() << "s)" << std::endl; + } + catch (TestFailure &fail) + { + this->stream << " fail" << std::endl; + this->stream << " reason: " << fail.what() << std::endl; + tests_failed++; + } + catch (std::exception &err) + { + this->stream << " exception" << std::endl; + this->stream << " what(): " << err.what() << std::endl; + tests_error++; + } + } + + this->stream << std::endl; + } + + this->stream << "Summary: " << tests_failed << " tests failed out of " + << tests_run - tests_error + << " (" << 100. * float((tests_run-tests_failed-tests_error))/(tests_run-tests_error) + << "% passed)." << std::endl << " Where " + << tests_error << " tests produced errors." << std::endl; +} + diff --git a/test/unittest.hh b/test/unittest.hh new file mode 100644 index 0000000..5958cd7 --- /dev/null +++ b/test/unittest.hh @@ -0,0 +1,159 @@ +#ifndef __SDR_UNITTEST_HH__ +#define __SDR_UNITTEST_HH__ + +#include +#include +#include +#include + + +namespace UnitTest { + +class TestFailure : public std::exception +{ +protected: + std::string message; + +public: + TestFailure(const std::string &message) throw(); + virtual ~TestFailure() throw(); + + const char *what() const throw(); +}; + + + +class TestCase +{ +public: + virtual void setUp(); + virtual void tearDown(); + + void assertTrue(bool test, const std::string &file, size_t line); + + template + void assertEqual(Scalar t, Scalar e, const std::string &file, size_t line) { + if (e != t) { + std::stringstream str; + str << "Expected: " << +e << " but got: " << +t + << " in file "<< file << " in line " << line; + throw TestFailure(str.str()); + } + } + + template + void assertNear(Scalar t, Scalar e, const std::string &file, size_t line, + Scalar err_abs=Scalar(1e-8), Scalar err_rel=Scalar(1e-6)) + { + if (std::abs(e-t) > (err_abs + err_rel*std::abs(e))) { + std::stringstream str; + str << "Expected: " << +e << " but got: " << +t + << " in file "<< file << " in line " << line; + throw TestFailure(str.str()); + } + } +}; + + + +class TestCallerInterface +{ +protected: + std::string description; + +public: + TestCallerInterface(const std::string &desc) + : description(desc) + { + // Pass... + } + + virtual ~TestCallerInterface() { /* pass... */ } + + virtual const std::string &getDescription() + { + return this->description; + } + + virtual void operator() () = 0; +}; + + +template +class TestCaller : public TestCallerInterface +{ +protected: + void (T::*function)(void); + +public: + TestCaller(const std::string &desc, void (T::*func)(void)) + : TestCallerInterface(desc), function(func) + { + // Pass... + } + + virtual ~TestCaller() { /* pass... */ } + + virtual void operator() () + { + // Create new test: + T *instance = new T(); + + // Call test + instance->setUp(); + (instance->*function)(); + instance->tearDown(); + + // free instance: + delete instance; + } +}; + + +class TestSuite +{ +public: + typedef std::list::iterator iterator; + +protected: + std::string description; + std::list tests; + +public: + TestSuite(const std::string &desc); + virtual ~TestSuite(); + + void addTest(TestCallerInterface *test); + + const std::string &getDescription(); + + iterator begin(); + iterator end(); +}; + + +class TestRunner +{ +protected: + std::ostream &stream; + std::list suites; + +public: + TestRunner(std::ostream &stream); + virtual ~TestRunner(); + + void addSuite(TestSuite *suite); + + void operator() (); +}; + + +#define UT_ASSERT(t) this->assertTrue(t, __FILE__, __LINE__) +#define UT_ASSERT_EQUAL(t, e) this->assertEqual(t, e, __FILE__, __LINE__) +#define UT_ASSERT_NEAR(t, e) this->assertNear(t, e, __FILE__, __LINE__) +#define UT_ASSERT_THROW(t, e) \ + try { t; throw UnitTest::TestFailure("No exception thrown!"); } catch (e &err) {} +} + + +#endif // UNITTEST_HH