--- /dev/null
+/.deps
+/.options.*
+/.profile
+/libmsptest.a
+/libmsptest.so
+/msptest.pc
+/temp
--- /dev/null
+package "msptest"
+{
+ description "C++ unit test framework";
+ version "0.1";
+
+ require "mspcore";
+
+ headers "msp/test"
+ {
+ source "source";
+ install true;
+ };
+
+ library "msptest"
+ {
+ source "source";
+ install true;
+ };
+};
--- /dev/null
+#ifndef MSP_TEST_EXCEPTIONCHECK_H_
+#define MSP_TEST_EXCEPTIONCHECK_H_
+
+namespace Msp {
+namespace Test {
+
+class ExceptionCheck
+{
+protected:
+ ExceptionCheck() { }
+
+public:
+ virtual bool check(const std::exception &) const = 0;
+};
+
+template<typename T>
+class ExceptionTypeCheck: public ExceptionCheck
+{
+public:
+ virtual bool check(const std::exception &e) const
+ { return dynamic_cast<const T *>(&e); }
+};
+
+} // namespace Test
+} // namespace Msp
+
+#endif
--- /dev/null
+#include "function.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Test {
+
+Function::Function(const string &d):
+ description(d),
+ exc_check(0)
+{ }
+
+Function::~Function()
+{
+ delete exc_check;
+}
+
+} // namespace Test
+} // namespace Msp
--- /dev/null
+#ifndef MSP_TEST_TESTFUNCTION_H_
+#define MSP_TEST_TESTFUNCTION_H_
+
+#include <string>
+#include "exceptioncheck.h"
+
+namespace Msp {
+namespace Test {
+
+class Test;
+
+class Function
+{
+private:
+ std::string description;
+ ExceptionCheck *exc_check;
+
+protected:
+ Function(const std::string &);
+public:
+ virtual ~Function();
+
+ const std::string &get_description() const { return description; }
+
+ template<typename T>
+ Function &expect_throw() { exc_check = new ExceptionTypeCheck<T>; return *this; }
+
+ const ExceptionCheck *get_exception_check() const { return exc_check; }
+
+ virtual void run(Test &) const = 0;
+};
+
+template<typename T>
+class TypedFunction: public Function
+{
+private:
+ typedef void (T::*FuncPtr)();
+
+ FuncPtr func;
+
+public:
+ TypedFunction(FuncPtr f, const std::string & d):
+ Function(d),
+ func(f)
+ { }
+
+ virtual void run(Test &t) const
+ {
+ (dynamic_cast<T &>(t).*func)();
+ }
+};
+
+} // namespace Test
+} // namespace Msp
+
+#endif
--- /dev/null
+#include <msp/core/getopt.h>
+#include <msp/io/print.h>
+#include "runner.h"
+#include "test.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Test {
+
+Runner::Runner(int argc, char **argv)
+{
+ GetOpt getopt;
+ getopt.add_option('v', "verbose", verbose, GetOpt::NO_ARG).set_help("Show what is being done");
+ getopt.add_option('l', "list", show_list, GetOpt::NO_ARG).set_help("Print a list of available test cases");
+ getopt(argc, argv);
+
+ const vector<string> &args = getopt.get_args();
+ tests.assign(args.begin(), args.end());
+}
+
+int Runner::main()
+{
+ if(show_list)
+ {
+ const list<Test::Factory *> &factories = Test::get_factories();
+ for(list<Test::Factory *>::const_iterator i=factories.begin(); i!=factories.end(); ++i)
+ IO::print("%s\n", (*i)->get_name());
+ }
+ else if(tests.empty())
+ Test::run_all(verbose);
+ else
+ {
+ for(list<string>::const_iterator i=tests.begin(); i!=tests.end(); ++i)
+ Test::run_single(*i, verbose);
+ }
+
+ return 0;
+}
+
+} // namespace Test
+} // namespace Msp
--- /dev/null
+#ifndef MSP_TEST_RUNNER_H_
+#define MSP_TEST_RUNNER_H_
+
+#include <list>
+#include <string>
+#include <msp/core/application.h>
+
+namespace Msp {
+namespace Test {
+
+class Runner: public RegisteredApplication<Runner>
+{
+private:
+ bool verbose;
+ bool show_list;
+ std::list<std::string> tests;
+
+public:
+ Runner(int, char **);
+
+ virtual int main();
+};
+
+} // namespace Test
+} // namespace Msp
+
+#endif
--- /dev/null
+#include <typeinfo>
+#include <msp/debug/demangle.h>
+#include <msp/io/print.h>
+#include <msp/strings/utils.h>
+#include "runner.h"
+#include "test.h"
+
+using namespace std;
+
+namespace {
+
+/* This makes the linker pull in the Runner class when linking msptest as a
+static library */
+void *dummy()
+{ return new Msp::Test::Runner(0, 0); }
+
+}
+
+
+namespace Msp {
+namespace Test {
+
+Test::Test(const string &n):
+ name(n)
+{ }
+
+Test::~Test()
+{
+ for(list<Function *>::iterator i=functions.begin(); i!=functions.end(); ++i)
+ delete *i;
+}
+
+list<Test::Factory *> &Test::get_factories()
+{
+ static list<Factory *> factories;
+ return factories;
+}
+
+void Test::run_single(const string &name, bool verbose)
+{
+ const list<Factory *> &factories = get_factories();
+ for(list<Factory *>::const_iterator i=factories.begin(); i!=factories.end(); ++i)
+ if((*i)->get_name()==name)
+ {
+ Test *test = (*i)->create();
+ test->run(verbose);
+ delete test;
+ }
+}
+
+void Test::run_all(bool verbose)
+{
+ const list<Factory *> &factories = get_factories();
+ IO::print("Running %d test suites\n", factories.size());
+ for(list<Factory *>::const_iterator i=factories.begin(); i!=factories.end(); ++i)
+ {
+ Test *test = (*i)->create();
+ test->run(verbose);
+ delete test;
+ }
+}
+
+void Test::run(bool verbose)
+{
+ IO::print("Running tests for %s\n", name);
+
+ unsigned n_passed = 0;
+ unsigned total = 0;
+ for(list<Function *>::const_iterator i=functions.begin(); i!=functions.end(); ++i)
+ {
+ detail_info = string();
+ detail_debug = string();
+ passed = false;
+ start_test((*i)->get_description());
+ const ExceptionCheck *exc_check = (*i)->get_exception_check();
+ try
+ {
+ (*i)->run(*this);
+ if(exc_check)
+ fail_test("Exception expected but none thrown");
+ else
+ pass_test();
+ }
+ catch(const test_failed &e)
+ {
+ fail_test(e.what());
+ }
+ catch(const exception &e)
+ {
+ if(exc_check && exc_check->check(e))
+ {
+ pass_test();
+ debug(Debug::demangle(typeid(e).name()));
+ debug(e.what());
+ }
+ else
+ fail_test(e.what());
+ }
+ catch(...)
+ {
+ fail_test("Unknown object thrown");
+ }
+
+ if(verbose)
+ detail_info += detail_debug;
+ if(!detail_info.empty())
+ {
+ vector<string> lines = split(detail_info, '\n');
+ for(vector<string>::const_iterator j=lines.begin(); j!=lines.end(); ++j)
+ IO::print(" %s\n", *j);
+ }
+
+ ++total;
+ if(passed)
+ ++n_passed;
+ }
+
+ IO::print(" %d/%d passed\n", n_passed, total);
+}
+
+void Test::start_test(const string &descr)
+{
+ IO::print(" %s: ", descr);
+}
+
+void Test::pass_test()
+{
+ IO::print("\033[32mok\033[0m\n");
+ ++passed;
+}
+
+void Test::fail_test(const string &why)
+{
+ IO::print("\033[31mfailed\033[0m\n");
+ IO::print(" %s\n", why);
+}
+
+void Test::expect(bool cond, const string &expr)
+{
+ if(!cond)
+ throw test_failed(format("!(%s)", expr));
+ debug(expr);
+}
+
+void Test::info(const string &str)
+{
+ detail_info += format("%s\n", str);
+}
+
+void Test::debug(const string &str)
+{
+ detail_debug += format("%s\n", str);
+}
+
+
+Test::Factory::Factory()
+{
+ get_factories().push_back(this);
+}
+
+} // namespace Test
+} // namespace Msp
--- /dev/null
+#ifndef MSP_TEST_TEST_H_
+#define MSP_TEST_TEST_H_
+
+#include <list>
+#include <string>
+#include <msp/strings/format.h>
+#include "function.h"
+
+namespace Msp {
+namespace Test {
+
+#define EXPECT(c) expect(c, #c)
+#define EXPECT_EQUAL(a, b) expect_equal(a, a==b, #a " == " #b)
+
+class test_failed: public std::exception
+{
+private:
+ std::string what_;
+
+public:
+ test_failed(const std::string &w): what_(w) { }
+ virtual ~test_failed() throw() { }
+
+ const char *what() const throw() { return what_.c_str(); }
+};
+
+
+class Test
+{
+public:
+ class Factory
+ {
+ protected:
+ Factory();
+
+ public:
+ virtual const char *get_name() const = 0;
+ virtual Test *create() const = 0;
+ };
+
+private:
+ std::string name;
+ std::list<Function *> functions;
+ std::string detail_info;
+ std::string detail_debug;
+ bool passed;
+
+protected:
+ Test(const std::string &);
+public:
+ virtual ~Test();
+
+ static std::list<Factory *> &get_factories();
+
+protected:
+ template<typename T>
+ Function &add(void (T::*f)(), const std::string &d)
+ {
+ functions.push_back(new TypedFunction<T>(f, d));
+ return *functions.back();
+ }
+
+public:
+ static void run_single(const std::string &, bool);
+ static void run_all(bool);
+
+ void run(bool);
+private:
+ void start_test(const std::string &);
+ void pass_test();
+ void fail_test(const std::string &);
+
+protected:
+ void expect(bool, const std::string &);
+
+ template<typename T>
+ void expect_equal(const T &var, bool cond, const std::string &expr)
+ {
+ if(!cond)
+ throw test_failed(format("!(%s)\nActual: %s", expr, var));
+ debug(expr);
+ }
+
+ void info(const std::string &);
+ void debug(const std::string &);
+};
+
+template<typename T>
+class RegisteredTest: public Test
+{
+private:
+ class Factory: public Test::Factory
+ {
+ public:
+ virtual const char *get_name() const { return T::get_name(); }
+ virtual Test *create() const { return new T; }
+ };
+
+ static Factory factory;
+
+protected:
+ RegisteredTest(): Test(T::get_name()) { (void)factory; }
+};
+
+template<typename T>
+typename RegisteredTest<T>::Factory RegisteredTest<T>::factory;
+
+} // namespace Test
+} // namespace Msp
+
+#endif