diff options
author | Bent Bisballe Nyeng <deva@aasimon.org> | 2020-09-19 14:21:22 +0200 |
---|---|---|
committer | Bent Bisballe Nyeng <deva@aasimon.org> | 2020-09-19 14:55:51 +0200 |
commit | e46cf7b5489213ac0f8941c870121b04cf6091ac (patch) | |
tree | 4395e82f619e3a02b94fd9b7fc4e325754d9045b | |
parent | 70976334a2edd8bee091a0fd9b07e5714b518974 (diff) |
Import uunit from drumgizmo project - renamed and relicensed.
-rw-r--r-- | uunit.cc | 16 | ||||
-rw-r--r-- | uunit.h | 227 |
2 files changed, 243 insertions, 0 deletions
diff --git a/uunit.cc b/uunit.cc new file mode 100644 index 0000000..609310a --- /dev/null +++ b/uunit.cc @@ -0,0 +1,16 @@ +// -*- c++ -*- +// This is the uUnit main file. +// Copyright 2020 Bent Bisballe Nyeng (deva@aasimon.org) +// This file released under the CC0-1.0 license. See CC0-1.0 file for details. + +#define uUNIT_MAIN +#include "uunit.h" + +#include <fstream> + +int main(int argc, char* argv[]) +{ + std::ofstream xmlfile; + xmlfile.open("result_" OUTPUT ".xml"); + return uUnit::runTests(xmlfile); +} @@ -0,0 +1,227 @@ +// -*- c++ -*- +// This is the header-only implementation of the uUnit unit-test framework. +// Copyright 2020 Bent Bisballe Nyeng (deva@aasimon.org) +// This file released under the CC0-1.0 license. See CC0-1.0 file for details. +#pragma once + +#include <cstddef> +#include <iostream> +#include <list> +#include <vector> +#include <functional> +#include <string> +#include <sstream> +#include <fstream> +#include <cmath> + +class uUnit +{ +public: + uUnit() + { + if(uUnit::suite_list == nullptr) + { + uUnit::suite_list = this; + return; + } + + auto unit = uUnit::suite_list; + while(unit->next_unit) + { + unit = unit->next_unit; + } + unit->next_unit = this; + } + + virtual ~uUnit() = default; + + //! 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 + { + std::string func; + std::string 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) + { + std::size_t test_num{0}; + std::size_t failed{0}; + + std::list<test_result> failed_tests; + std::list<test_result> successful_tests; + + for(auto suite = uUnit::suite_list; suite; suite = suite->next_unit) + { + 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); + ++failed; + 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.push_back(result); + } + } + + out << "<?xml version=\"1.0\" encoding='ISO-8859-1' standalone='yes' ?>" << + std::endl; + out << "<TestRun>" << std::endl; + out << " <FailedTests>" << std::endl; + for(auto test : failed_tests) + { + out << " <FailedTest id=\"" << test.id << "\">" << std::endl; + out << " <Name>" << sanitize(test.func) << "</Name>" << std::endl; + out << " <FailureType>Assertion</FailureType>" << std::endl; + out << " <Location>" << std::endl; + out << " <File>" << sanitize(test.file) << "</File>" << std::endl; + out << " <Line>" << test.line << "</Line>" << std::endl; + out << " </Location>" << std::endl; + out << " <Message>" << sanitize(test.msg) << "</Message>" << + std::endl; + out << " </FailedTest>" << std::endl; + } + out << " </FailedTests>" << std::endl; + out << " <SuccessfulTests>" << std::endl; + for(auto test : successful_tests) + { + out << " <Test id=\"" << test.id << "\">" << std::endl; + out << " <Name>" << sanitize(test.func) << "</Name>" << std::endl; + out << " </Test>" << std::endl; + + } + out << " </SuccessfulTests>" << std::endl; + out << " <Statistics>" << std::endl; + out << " <Tests>" << (successful_tests.size() + failed_tests.size()) << + "</Tests>" << std::endl; + out << " <FailuresTotal>" << failed_tests.size() << "</FailuresTotal>" << + std::endl; + out << " <Errors>0</Errors>" << std::endl; + out << " <Failures>" << failed_tests.size() << "</Failures>" << + std::endl; + out << " </Statistics>" << std::endl; + out << "</TestRun>" << std::endl; + + 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 uUNIT_TEST(func) \ + registerTest(this, &func, #func) + + void u_assert(bool value, const char* expr, + const char* file, std::size_t line) + { + if(!value) + { + std::stringstream ss; + ss << "assertion failed" << std::endl << + "- Expression: " << expr << "" << std::endl; + throw test_result{"", file, line, ss.str()}; + } + } + //! Convenience macro to pass along filename and linenumber + #define uUNIT_ASSERT(value) \ + u_assert(value, #value, __FILE__, __LINE__) + + void assert_equal(double expected, double value, + const char* file, std::size_t line) + { + if(std::fabs(expected - value) > 0.0000001) + { + std::stringstream ss; + ss << "equality assertion failed" << std::endl << + "- Expected: " << expected << "" << std::endl << + "- Actual : " << value << "" << std::endl; + throw test_result{"", file, line, ss.str()}; + } + } + 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" << std::endl << + "- Expected: " << expected << "" << std::endl << + "- Actual : " << value << "" << std::endl; + throw test_result{"", file, line, ss.str()}; + } + } + //! Convenience macro to pass along filename and linenumber + #define uUNIT_ASSERT_EQUAL(expected, value) \ + assert_equal(expected, value, __FILE__, __LINE__) + +private: + static std::string sanitize(const std::string& input) + { + std::string output; + for(auto c : input) + { + switch(c) + { + case '"': + output += """; + break; + case '&': + output += "&"; + break; + case '<': + output += "<"; + break; + case '>': + output += ">"; + break; + default: + output += c; + break; + } + } + return output; + } + + static uUnit* suite_list; + uUnit* next_unit{nullptr}; + std::vector<std::pair<std::function<void()>, const char*>> tests; +}; + +#ifdef uUNIT_MAIN +uUnit* uUnit::suite_list{nullptr}; +#endif |