diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/dgunit.h | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/test/dgunit.h b/test/dgunit.h new file mode 100644 index 0000000..4439134 --- /dev/null +++ b/test/dgunit.h @@ -0,0 +1,208 @@ +/* -*- Mode: c++ -*- */ +/*************************************************************************** + * dgunit.h + * + * Sat Jun 16 15:44:06 CEST 2018 + * Copyright 2018 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#pragma once + +#include <cstddef> +#include <iostream> +#include <list> +#include <functional> +#include <string> +#include <sstream> +#include <fstream> + +class DGUnit +{ +public: + DGUnit(const std::string& test_name, const std::string& test_suite = "all") + : test_name(test_name) + , test_suite(test_suite) + { + suites.emplace_back(this); + } + + //! Overload to prepare stuff for each of the tests. + virtual void setup() {} + + //! Overload to tear down stuff for each of the tests. + virtual void teardown() {} + + struct test_result + { + const char* func; + const char* file; + std::size_t line; + std::string msg; + int id; + }; + + //! Run test + //! \param test_suite the name of a test suite or null for all. + //! \param test_name the name of a test name inside a test suite. Only valid + //! if test_suite is non-null. nullptr for all tests. + static int runTests(std::ofstream& out, + const std::string& test_suite = "all", + const std::string& test_name = "all") + { + std::size_t test_num{0}; + std::size_t failed{0}; + + std::list<test_result> failed_tests; + std::list<test_result> successful_tests; + + std::cout << "Run tests\n"; + for(auto suite : suites) + { + if(test_suite != "all" && suite->test_suite != test_suite) + { + continue; + } + + if(test_name != "all" && suite->test_name != test_name) + { + continue; + } + + std::cout << "Test: " << suite->test_suite << "::" + << suite->test_name << std::endl; + + for(auto test : suite->tests) + { + ++test_num; + try + { + suite->setup(); + test.first(); + suite->teardown(); + } + catch(test_result& result) + { + std::cout << "F"; + fflush(stdout); + result.id = test_num; + result.func = test.second; + failed_tests.push_back(result); + continue; + } + catch(...) + { + break; // Uncaught exception. Do not proceed with this test. + } + std::cout << "."; + fflush(stdout); + test_result result{test.second}; + result.id = test_num; + successful_tests.emplace_back(result); + } + } + + out << "<?xml version=\"1.0\" encoding='ISO-8859-1' standalone='yes' ?>\n"; + out << "<TestRun>\n"; + out << " <FailedTests>\n"; + for(auto test : failed_tests) + { + out << " <FailedTest id=\"" << test.id << "\">\n"; + out << " <Name>" << test.func << "</Name>\n"; + out << " <FailureType>Assertion</FailureType>\n"; + out << " <Location>\n"; + out << " <File>" << test.file << "</File>\n"; + out << " <Line>" << test.line << "</Line>\n"; + out << " </Location>\n"; + out << " <Message>" << test.msg << "</Message>\n"; + out << " </FailedTest>\n"; + } + out << " <FailedTests>\n"; + out << " <SuccessfulTests>\n"; + for(auto test : successful_tests) + { + out << " <Test id=\"" << test.id << "\">\n"; + out << " <Name>" << test.func << "</Name>\n"; + out << " </Test>\n"; + + } + out << " </SuccessfulTests>\n"; + out << " <Statistics>\n"; + out << " <Tests>" << (successful_tests.size() + failed_tests.size()) << "</Tests>\n"; + out << " <FailuresTotal>" << failed_tests.size() << "</FailuresTotal>\n"; + out << " <Errors>0</Errors>\n"; + out << " <Failures>" << failed_tests.size() << "</Failures>\n"; + out << " </Statistics>\n"; + out << "</TestRun>\n"; + + return failed == 0 ? 0 : 1; + } + +protected: + template<typename O, typename F> + void registerTest(O* obj, const F& fn, const char* name) + { + tests.emplace_back(std::make_pair(std::bind(fn, obj), name)); + } + #define DGUNIT_TEST(func) \ + registerTest(this, &func, #func) + + void assert(bool value, const char* expr, + const char* file, std::size_t line) + { + if(!value) + { + std::stringstream ss; + ss << "assertion failed\n" + "- Expression: " << expr << "\n"; + throw test_result{"", file, line, ss.str()}; + } + } + //! Convenience macro to pass along filename and linenumber + #define DGUNIT_ASSERT(value) \ + assert(value, #value, __FILE__, __LINE__) + + template<typename T> + void assert_equal(T expected, T value, + const char* file, std::size_t line) + { + if(expected != value) + { + std::stringstream ss; + ss << "equality assertion failed\n" + "- Expected: " << expected << "\n" + "- Actual : " << value << "\n"; + throw test_result{"", file, line, ss.str()}; + } + } + //! Convenience macro to pass along filename and linenumber + #define DGUNIT_ASSERT_EQUAL(expected, value) \ + assert_equal(expected, value, __FILE__, __LINE__) + +private: + static std::list<class DGUnit*> suites; + std::list<std::pair<std::function<void()>, const char*>> tests; + const std::string test_name; + const std::string test_suite; +}; + +#ifdef DGUNIT_MAIN +std::list<class DGUnit*> DGUnit::suites; +#endif |