From 1d09b14f91000e9e62bd2a0e7f0be53f2077e59c Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Sat, 23 Jul 2011 12:58:45 +0300 Subject: [PATCH] Initial commit --- .gitignore | 7 ++ Build | 19 +++++ source/exceptioncheck.h | 27 +++++++ source/function.cpp | 19 +++++ source/function.h | 56 ++++++++++++++ source/runner.cpp | 42 +++++++++++ source/runner.h | 27 +++++++ source/test.cpp | 162 ++++++++++++++++++++++++++++++++++++++++ source/test.h | 111 +++++++++++++++++++++++++++ 9 files changed, 470 insertions(+) create mode 100644 .gitignore create mode 100644 Build create mode 100644 source/exceptioncheck.h create mode 100644 source/function.cpp create mode 100644 source/function.h create mode 100644 source/runner.cpp create mode 100644 source/runner.h create mode 100644 source/test.cpp create mode 100644 source/test.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df32a97 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/.deps +/.options.* +/.profile +/libmsptest.a +/libmsptest.so +/msptest.pc +/temp diff --git a/Build b/Build new file mode 100644 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 index 0000000..8211502 --- /dev/null +++ b/source/exceptioncheck.h @@ -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 +class ExceptionTypeCheck: public ExceptionCheck +{ +public: + virtual bool check(const std::exception &e) const + { return dynamic_cast(&e); } +}; + +} // namespace Test +} // namespace Msp + +#endif diff --git a/source/function.cpp b/source/function.cpp new file mode 100644 index 0000000..0053d39 --- /dev/null +++ b/source/function.cpp @@ -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 index 0000000..b76426e --- /dev/null +++ b/source/function.h @@ -0,0 +1,56 @@ +#ifndef MSP_TEST_TESTFUNCTION_H_ +#define MSP_TEST_TESTFUNCTION_H_ + +#include +#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 + Function &expect_throw() { exc_check = new ExceptionTypeCheck; return *this; } + + const ExceptionCheck *get_exception_check() const { return exc_check; } + + virtual void run(Test &) const = 0; +}; + +template +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).*func)(); + } +}; + +} // namespace Test +} // namespace Msp + +#endif diff --git a/source/runner.cpp b/source/runner.cpp new file mode 100644 index 0000000..0508312 --- /dev/null +++ b/source/runner.cpp @@ -0,0 +1,42 @@ +#include +#include +#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 &args = getopt.get_args(); + tests.assign(args.begin(), args.end()); +} + +int Runner::main() +{ + if(show_list) + { + const list &factories = Test::get_factories(); + for(list::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::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 index 0000000..0f6193c --- /dev/null +++ b/source/runner.h @@ -0,0 +1,27 @@ +#ifndef MSP_TEST_RUNNER_H_ +#define MSP_TEST_RUNNER_H_ + +#include +#include +#include + +namespace Msp { +namespace Test { + +class Runner: public RegisteredApplication +{ +private: + bool verbose; + bool show_list; + std::list 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 index 0000000..ab6e052 --- /dev/null +++ b/source/test.cpp @@ -0,0 +1,162 @@ +#include +#include +#include +#include +#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::iterator i=functions.begin(); i!=functions.end(); ++i) + delete *i; +} + +list &Test::get_factories() +{ + static list factories; + return factories; +} + +void Test::run_single(const string &name, bool verbose) +{ + const list &factories = get_factories(); + for(list::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 &factories = get_factories(); + IO::print("Running %d test suites\n", factories.size()); + for(list::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::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 lines = split(detail_info, '\n'); + for(vector::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 index 0000000..ae459f5 --- /dev/null +++ b/source/test.h @@ -0,0 +1,111 @@ +#ifndef MSP_TEST_TEST_H_ +#define MSP_TEST_TEST_H_ + +#include +#include +#include +#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 functions; + std::string detail_info; + std::string detail_debug; + bool passed; + +protected: + Test(const std::string &); +public: + virtual ~Test(); + + static std::list &get_factories(); + +protected: + template + Function &add(void (T::*f)(), const std::string &d) + { + functions.push_back(new TypedFunction(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 + 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 +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 RegisteredTest::Factory RegisteredTest::factory; + +} // namespace Test +} // namespace Msp + +#endif -- 2.45.2