]> git.tdb.fi Git - libs/test.git/commitdiff
Initial commit
authorMikko Rasa <tdb@tdb.fi>
Sat, 23 Jul 2011 09:58:45 +0000 (12:58 +0300)
committerMikko Rasa <tdb@tdb.fi>
Sat, 23 Jul 2011 09:58:45 +0000 (12:58 +0300)
.gitignore [new file with mode: 0644]
Build [new file with mode: 0644]
source/exceptioncheck.h [new file with mode: 0644]
source/function.cpp [new file with mode: 0644]
source/function.h [new file with mode: 0644]
source/runner.cpp [new file with mode: 0644]
source/runner.h [new file with mode: 0644]
source/test.cpp [new file with mode: 0644]
source/test.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..df32a97
--- /dev/null
@@ -0,0 +1,7 @@
+/.deps
+/.options.*
+/.profile
+/libmsptest.a
+/libmsptest.so
+/msptest.pc
+/temp
diff --git a/Build b/Build
new file mode 100644 (file)
index 0000000..07242bc
--- /dev/null
+++ b/Build
@@ -0,0 +1,19 @@
+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;
+       };
+};
diff --git a/source/exceptioncheck.h b/source/exceptioncheck.h
new file mode 100644 (file)
index 0000000..8211502
--- /dev/null
@@ -0,0 +1,27 @@
+#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
diff --git a/source/function.cpp b/source/function.cpp
new file mode 100644 (file)
index 0000000..0053d39
--- /dev/null
@@ -0,0 +1,19 @@
+#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
diff --git a/source/function.h b/source/function.h
new file mode 100644 (file)
index 0000000..b76426e
--- /dev/null
@@ -0,0 +1,56 @@
+#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
diff --git a/source/runner.cpp b/source/runner.cpp
new file mode 100644 (file)
index 0000000..0508312
--- /dev/null
@@ -0,0 +1,42 @@
+#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
diff --git a/source/runner.h b/source/runner.h
new file mode 100644 (file)
index 0000000..0f6193c
--- /dev/null
@@ -0,0 +1,27 @@
+#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
diff --git a/source/test.cpp b/source/test.cpp
new file mode 100644 (file)
index 0000000..ab6e052
--- /dev/null
@@ -0,0 +1,162 @@
+#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
diff --git a/source/test.h b/source/test.h
new file mode 100644 (file)
index 0000000..ae459f5
--- /dev/null
@@ -0,0 +1,111 @@
+#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