#ifndef UNIT_TEST_FRAMEWORK_H #define UNIT_TEST_FRAMEWORK_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using Test_func_t = void (*)(); #define TEST(name) \ static void name(); \ static TestRegisterer register_##name((#name), name); \ static void name() #define TEST_MAIN() \ int main(int argc, char** argv) { \ return TestSuite::get().run_tests(argc, argv); \ } \ TEST_SUITE_INSTANCE(); struct TestCase { TestCase(const std::string& name_, Test_func_t test_func_) : name(name_), test_func(test_func_) {} void run(bool quiet_mode); void print(bool quiet_mode); std::string name; Test_func_t test_func; std::string failure_msg{}; std::string exception_msg{}; }; class TestSuite { public: static TestSuite& get() { if (not instance) { instance = new TestSuite; } return *instance; } void add_test(const std::string& test_name, Test_func_t test) { tests_.insert({test_name, TestCase{test_name, test}}); } int run_tests(int argc, char** argv); void print_results(); void enable_quiet_mode() { quiet_mode = true; } std::ostream& print_test_names(std::ostream& os) { for (const auto& test_pair : tests_) { os << test_pair.first << '\n'; } return os; } friend class TestSuiteDestroyer; private: TestSuite() { auto func = []() { if (TestSuite::incomplete) { std::cout << "ERROR: premature call to exit()" << std::endl; std::abort(); } }; std::atexit(func); #ifdef _GLIBCXX_HAVE_AT_QUICK_EXIT std::at_quick_exit(func); #endif } TestSuite(const TestSuite&) = delete; bool operator=(const TestSuite&) = delete; ~TestSuite() {} std::vector get_test_names_to_run(int argc, char** argv); static TestSuite* instance; std::map tests_; bool quiet_mode = false; static bool incomplete; }; class TestSuiteDestroyer { public: ~TestSuiteDestroyer() { delete TestSuite::instance; } }; class TestRegisterer { public: TestRegisterer(const std::string& test_name, Test_func_t test) { TestSuite::get().add_test(test_name, test); } }; class TestFailure { public: TestFailure(std::string reason, int line_number, const char* assertion_text) : reason_m(std::move(reason)), line_number_m(line_number), assertion_text_m(assertion_text) {} std::ostream& print(std::ostream& os) const { os << "In " << assertion_text_m << ", line " << line_number_m << ":\n" << reason_m << '\n'; return os; } std::string to_string() const { std::ostringstream oss; print(oss); return oss.str(); } private: std::string reason_m; int line_number_m; const char* assertion_text_m; }; std::ostream& operator<<(std::ostream& os, const TestFailure& test_failure); #define TEST_SUITE_INSTANCE() \ static TestSuiteDestroyer destroyer; \ bool TestSuite::incomplete = false; \ TestSuite* TestSuite::instance = &TestSuite::get() void assert_true(bool value, int line_number, const char* assertion_text) { if (value) { return; } std::ostringstream reason; reason << "Expected true, but was false"; throw TestFailure(reason.str(), line_number, assertion_text); } void assert_false(bool value, int line_number, const char* assertion_text) { if (not value) { return; } std::ostringstream reason; reason << "Expected false, but was true"; throw TestFailure(reason.str(), line_number, assertion_text); } #endif // UNIT_TEST_FRAMEWORK_H