diff options
Diffstat (limited to 'src/lib/netlist/plib/ptests.h')
-rw-r--r-- | src/lib/netlist/plib/ptests.h | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/src/lib/netlist/plib/ptests.h b/src/lib/netlist/plib/ptests.h new file mode 100644 index 00000000000..4e65de7f978 --- /dev/null +++ b/src/lib/netlist/plib/ptests.h @@ -0,0 +1,313 @@ +// license:BSD-3-Clause +// copyright-holders:Couriersud + +#ifndef PTESTS_H_ +#define PTESTS_H_ + +/// +/// \file ptests.h +/// +/// google tests compatible (hopefully) test macros. This is work in progress! +/// + +#include <algorithm> +#include <exception> +#include <functional> +#include <iostream> +#include <sstream> +#include <string> +#include <vector> + +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wglobal-constructors" +#endif + +#define PEXPECT_EQ(exp1, exp2) PINT_EXPECT(eq, exp1, exp2) +#define PEXPECT_NE(exp1, exp2) PINT_EXPECT(ne, exp1, exp2) +#define PEXPECT_GT(exp1, exp2) PINT_EXPECT(gt, exp1, exp2) +#define PEXPECT_LT(exp1, exp2) PINT_EXPECT(lt, exp1, exp2) +#define PEXPECT_GE(exp1, exp2) PINT_EXPECT(ge, exp1, exp2) +#define PEXPECT_LE(exp1, exp2) PINT_EXPECT(le, exp1, exp2) + +#define PEXPECT_TRUE(exp1) PINT_EXPECT(eq, exp1, true) +#define PEXPECT_FALSE(exp1) PINT_EXPECT(eq, exp1, false) + +#define PEXPECT_THROW(exp, excep) PINT_EXPECT_THROW(exp, excep) +#define PEXPECT_NO_THROW(exp) PINT_EXPECT_NO_THROW(exp) + +#define PTEST(name, desc) PINT_TEST(name, desc) +#define PTEST_F(name, desc) PINT_TEST_F(name, desc, name) +#define PRUN_ALL_TESTS(loglevel) plib::testing::run_all_tests(loglevel) + +#define PINT_TEST(name, desc) PINT_TEST_F(name, desc, plib::testing::Test) + +#define PINT_TESTNAME(name, desc) name ## _ ## desc +#define PINT_LOCATION() ::plib::testing::test_location(__FILE__, __LINE__) +#define PINT_SET_LAST(loc) this->m_parameters->m_last_source = loc + +#define PINT_REGISTER(name, desc) \ + extern const plib::testing::reg_entry<PINT_TESTNAME(name, desc)> PINT_TESTNAME(name, desc ## _reg); \ + const plib::testing::reg_entry<PINT_TESTNAME(name, desc)> PINT_TESTNAME(name, desc ## _reg)(#name, #desc, PINT_LOCATION()); + +#define PINT_TEST_F(name, desc, base) \ + class PINT_TESTNAME(name, desc) : public base \ + { public:\ + void desc (); \ + void run() override { desc (); } \ + }; \ + PINT_REGISTER(name, desc) \ + void PINT_TESTNAME(name, desc) :: desc () + +#define PINT_EXPECT(comp, exp1, exp2) \ + if (true) \ + { \ + ::plib::testing::test_location source = PINT_LOCATION(); PINT_SET_LAST(source); \ + m_parameters->m_num_tests++; \ + if (!this->internal_assert(plib::testing::comp_ ## comp (), # exp1, # exp2, exp1, exp2)) \ + this->test_error(source) << "test failed" << std::endl; \ + } else do {} while (0) + +#define PINT_EXPECT_THROW(exp, excep) \ + if (true) \ + { \ + ::plib::testing::test_location source = PINT_LOCATION(); PINT_SET_LAST(source); \ + m_parameters->m_num_tests++; \ + try { exp; this->test_error(source) << "no " #excep " exception thrown" << std::endl;} \ + catch (excep &) { this->test_ok() << "got " #excep " for " # exp "" << std::endl;} \ + catch (std::exception &ptest_e) { this->test_error(source) << "unexpected exception thrown: " << ptest_e.what() << std::endl; } \ + catch (...) { this->test_error(source) << "unexpected exception thrown" << std::endl; } \ + } else do {} while (0) + +#define PINT_EXPECT_NO_THROW(exp) \ + if (true) \ + { \ + ::plib::testing::test_location source = PINT_LOCATION(); PINT_SET_LAST(source); \ + m_parameters->m_num_tests++; \ + try { exp; this->test_ok() << "got no exception for " # exp << std::endl;} \ + catch (std::exception &test_exception) { this->test_error(source) << "unexpected exception thrown: " << test_exception.what() << std::endl; } \ + catch (...) { this->test_error(source) << "unexpected exception thrown" << std::endl; } \ + } else do {} while (0) + +namespace plib::testing +{ + enum class loglevel + { + INFO, + WARNING, + ERROR + }; + + using test_location = std::pair<const char *, std::size_t>; + + struct test_parameters + { + loglevel m_loglevel = loglevel::INFO; + std::size_t m_num_errors = 0; + std::size_t m_num_tests = 0; + test_location m_last_source = {"", 0 }; + }; + + static std::ostream &stream_error(std::ostream &os, test_location loc) + { + return os << loc.first << ":" << loc.second << ":1: error: "; + } + + class Test + { + public: + Test() = default; + virtual ~Test() = default; + + Test(const Test &) = delete; + Test &operator=(const Test &) = delete; + Test(Test &&) = delete; + Test &operator=(Test &&) = delete; + + virtual void run() {} + virtual void SetUp() {} + virtual void TearDown() {} + + void set_parameters(test_parameters *params) + { + m_parameters = params; + } + protected: + std::ostream & test_ok() { return output(loglevel::INFO) << "\tOK: "; } + std::ostream & test_fail() { return output(loglevel::WARNING) << "\tFAIL: "; } + std::ostream & test_error(const test_location & loc) + { + return stream_error(output(loglevel::ERROR), loc); + } + + template <typename C, typename T1, typename T2> + bool internal_assert(C comp, + const char* exp1, const char* exp2, + const T1& val1, const T2& val2); + test_parameters *m_parameters = nullptr; + private: + std::ostream &output(loglevel ll) + { + if (ll == loglevel::ERROR) + m_parameters->m_num_errors++; + return (ll >= m_parameters->m_loglevel) ? std::cout : m_nulstream; + } + std::ostringstream m_nulstream; + }; + + template <typename C, typename T1, typename T2> + bool Test::internal_assert(C comp, + const char* exp1, const char* exp2, + const T1& val1, const T2& val2) + { + if (comp(val1, val2)) + { + test_ok() << exp1 << " " << C::opstr() << " " << exp2 << std::endl; + return true; + } + test_fail() << exp1 << " " << C::opstr() << " " << exp2 + << " <" << val1 << ">,<" << val2 << ">" << std::endl; + return false; + } + + struct reg_entry_base + { + using list_t = std::vector<reg_entry_base *>; + reg_entry_base(const char *n, const char *d, test_location l) + : name(n), desc(d), location(l) + { + try + { + registry().push_back(this); + } + catch (...) + { + std::terminate(); + } + } + + reg_entry_base(const reg_entry_base &) = delete; + reg_entry_base &operator=(const reg_entry_base &) = delete; + reg_entry_base(reg_entry_base &&) = delete; + reg_entry_base &operator=(reg_entry_base &&) = delete; + + virtual ~reg_entry_base() = default; + + virtual Test *create() const { return nullptr; } + + const char *name; + const char *desc; + test_location location; + public: + static list_t & registry() + { + static list_t prlist; + return prlist; + } + }; + + template <typename T> + struct reg_entry : public reg_entry_base + { + using reg_entry_base::reg_entry_base; + + Test *create() const override { return new T(); } // NOLINT + }; + + template <typename L> + std::pair<bool, std::string> catch_exception(L lambda) + { + try { + lambda(); + } + catch (std::exception &ptest_e) + { + return { true, ptest_e.what() }; + } + catch (...) + { + return { true, "" }; + } + return { false, "" }; + } + + static inline int run_all_tests(loglevel ll) + { + std::cout << "================================================" << std::endl; + std::cout << "Running " << reg_entry_base::registry().size() << " test groups" << std::endl; + std::cout << "================================================" << std::endl; + + auto &list = reg_entry_base::registry(); + + std::sort(list.begin(), list.end(), [](reg_entry_base *a, reg_entry_base *b) { + return (a->name < b->name); + }); + + std::size_t total_errors(0); + std::size_t total_tests(0); + + for (auto &e : list) + { + test_parameters params; + params.m_loglevel = ll; + params.m_last_source = e->location; + + std::cout << e->name << "::" << e->desc << ":" << std::endl; + Test *t = nullptr; + + std::pair<bool, std::string> r; + if ((r = catch_exception([&]{ + t = e->create(); + t->set_parameters(¶ms); + })).first) + { + stream_error(std::cout, e->location) << "unexpected exception thrown during instantiation" << (!r.second.empty() ? ": " + r.second : "") << std::endl; + total_errors++; + } + else if ((r = catch_exception([&]{ t->SetUp(); })).first) + { + stream_error(std::cout, e->location) << "unexpected exception thrown during Setup" << (!r.second.empty() ? ": " + r.second : "") << std::endl; + total_errors++; + } + else if ((r = catch_exception([&]{ t->run(); })).first) + { + stream_error(std::cout, params.m_last_source) << "unexpected exception thrown during run after this line" << (!r.second.empty() ? ": " + r.second : "") << std::endl; + total_errors++; + } + else if ((r = catch_exception([&]{ t->TearDown(); })).first) + { + stream_error(std::cout, e->location) << "unexpected exception thrown during Teardown" << (!r.second.empty() ? ": " + r.second : "") << std::endl; + total_errors++; + } + + total_errors += params.m_num_errors; + total_tests += params.m_num_tests; + if (t != nullptr) + delete t; + } + std::cout << "================================================" << std::endl; + std::cout << "Found " << total_errors << " errors in " << total_tests << " tests from " << reg_entry_base::registry().size() << " test groups" << std::endl; + std::cout << "================================================" << std::endl; + return (total_errors ? 1 : 0); + } + +#define DEF_COMP(name, op) \ + struct comp_ ## name \ + { \ + static const char * opstr() { return #op ; } \ + template <typename T1, typename T2> \ + bool operator()(const T1 &v1, const T2 &v2) { return v1 op v2; } \ + }; + + DEF_COMP(eq, ==) + DEF_COMP(ne, !=) + DEF_COMP(gt, >) + DEF_COMP(lt, <) + DEF_COMP(ge, >=) + DEF_COMP(le, <=) + +#undef DEF_COMP + +} // namespace plib::testing + + +#endif // PTESTS_H_ |