#ifndef BSTEST_H #define BSTEST_H #include #include #include #include #include #include "BSProtocol.h" #if defined(ARDUINO) #include "BSArduino.h" #else #include "BSStdio.h" #endif #ifndef BS_LINE_BUF_SIZE #define BS_LINE_BUF_SIZE 80 #endif namespace bs { typedef void(*test_case_func_t)(); class TestCase { public: TestCase(TestCase* prev, test_case_func_t func, const char* file, size_t line, const char* name, const char* desc) : m_func(func), m_file(file), m_line(line), m_name(name), m_desc(desc) { if (prev) { prev->m_next = this; } } void run() const { (*m_func)(); } TestCase* next() const { return m_next; } const char* file() const { return m_file; } size_t line() const { return m_line; } const char* name() const { return m_name; } const char* desc() const { return (m_desc)?m_desc:""; } protected: TestCase* m_next = nullptr; test_case_func_t m_func; const char* m_file; size_t m_line; const char* m_name; const char* m_desc; }; struct Registry { void add(test_case_func_t func, const char* file, size_t line, const char* name, const char* desc) { TestCase* tc = new TestCase(m_last, func, file, line, name, desc); if (!m_first) { m_first = tc; } m_last = tc; } TestCase* m_first = nullptr; TestCase* m_last = nullptr; }; struct Env { std::function m_check_pass; std::function m_check_fail; std::function m_fail; Registry m_registry; }; extern Env g_env; template class Runner { typedef Runner Tself; public: Runner(IO& io) : m_io(io) { g_env.m_check_pass = std::bind(&Tself::check_pass, this); g_env.m_check_fail = std::bind(&Tself::check_fail, this, std::placeholders::_1); g_env.m_fail = std::bind(&Tself::fail, this, std::placeholders::_1); } ~Runner() { g_env.m_check_pass = 0; g_env.m_check_fail = 0; g_env.m_fail = 0; } void run() { do { } while(do_menu()); } void check_pass() { ++m_check_pass_count; } void check_fail(size_t line) { ++m_check_fail_count; protocol::output_check_failure(m_io, line); } void fail(size_t line) { protocol::output_test_end(m_io, false, m_check_pass_count + m_check_fail_count, m_check_fail_count, line); bs::fatal(); } protected: bool do_menu() { protocol::output_menu_begin(m_io); int id = 1; for (TestCase* tc = g_env.m_registry.m_first; tc; tc = tc->next(), ++id) { protocol::output_menu_item(m_io, id, tc->name(), tc->desc()); } protocol::output_menu_end(m_io); while(true) { int id; char line_buf[BS_LINE_BUF_SIZE]; if (!protocol::input_handle(m_io, line_buf, sizeof(line_buf), id)) { continue; } if (id < 0) { return true; } TestCase* tc = g_env.m_registry.m_first; for (int i = 0; i != id - 1 && tc; ++i, tc = tc->next()); if (!tc) { bs::fatal(); } m_check_pass_count = 0; m_check_fail_count = 0; protocol::output_test_start(m_io, tc->file(), tc->line(), tc->name(), tc->desc()); tc->run(); bool success = m_check_fail_count == 0; protocol::output_test_end(m_io, success, m_check_pass_count + m_check_fail_count, m_check_fail_count); } } protected: IO& m_io; size_t m_check_pass_count; size_t m_check_fail_count; }; class AutoReg { public: AutoReg(test_case_func_t func, const char* file, size_t line, const char* name, const char* desc = nullptr) { g_env.m_registry.add(func, file, line, name, desc); } }; inline void check(bool condition, size_t line) { if (!condition) { g_env.m_check_fail(line); } else { g_env.m_check_pass(); } } inline void require(bool condition, size_t line) { if (!condition) { g_env.m_check_fail(line); g_env.m_fail(line); } else { g_env.m_check_pass(); } } } // ::bs #define BS_NAME_LINE2( name, line ) name##line #define BS_NAME_LINE( name, line ) BS_NAME_LINE2( name, line ) #define BS_UNIQUE_NAME( name ) BS_NAME_LINE( name, __LINE__ ) #define TEST_CASE( ... ) \ static void BS_UNIQUE_NAME( TEST_FUNC__ )(); \ namespace{ bs::AutoReg BS_UNIQUE_NAME( test_autoreg__ )( &BS_UNIQUE_NAME( TEST_FUNC__ ), __FILE__, __LINE__, __VA_ARGS__ ); }\ static void BS_UNIQUE_NAME( TEST_FUNC__ )() #define CHECK(condition) bs::check((condition), __LINE__) #define REQUIRE(condition) bs::require((condition), __LINE__) #define FAIL() bs::g_env.m_fail(__LINE__) #define BS_ENV_DECLARE() namespace bs { Env g_env; } #define BS_RUN(...) do { bs::IOHelper helper = bs::IOHelper(__VA_ARGS__); bs::Runner runner(helper); runner.run(); } while(0); #endif //BSTEST_H