#pragma once #include #include #include namespace SimpleTest { using TestFunc = void (*)(); using Registry = std::map>; inline Registry g_registry; inline Registry& registry() { return g_registry; } struct failure { char const* file; int line; char const* expr; }; struct Registrar { template Registrar(char const (&name)[N], TestFunc f) { auto [it, inserted] = registry().emplace(std::string_view{ name, N ? (N - 1) : 0 }, f); if (!inserted) { std::printf("[ WARN ] duplicate test name: %.*s\n", int(it->first.size()), it->first.data()); } } }; inline Registry const& all() { return registry(); } inline TestFunc find(std::string_view name) { auto it = registry().find(name); return it == registry().end() ? nullptr : it->second; } } #define SIMPLETEST_STRINGIFY(a) #a #define SIMPLETEST_XSTRINGIFY(a) SIMPLETEST_STRINGIFY(a) #define SIMPLETEST_CONCAT_(a, b) a##b #define SIMPLETEST_CONCAT(a, b) SIMPLETEST_CONCAT_(a, b) #define TEST(name_literal) \ static void SIMPLETEST_CONCAT(_simpletest_fn_, __LINE__)(); \ static ::SimpleTest::Registrar SIMPLETEST_CONCAT(_simpletest_reg_, \ __LINE__)( \ name_literal, &SIMPLETEST_CONCAT(_simpletest_fn_, __LINE__)); \ static void SIMPLETEST_CONCAT(_simpletest_fn_, __LINE__)() // Minimal assertion #define REQUIRE(expr) \ do { \ if (!(expr)) \ throw ::SimpleTest::failure{ __FILE__, __LINE__, #expr }; \ } while (0) int main(int argc, char** argv) { using namespace ::SimpleTest; std::string_view arg1 = (argc >= 2) ? std::string_view{ argv[1] } : std::string_view{}; if (arg1 == "--list") { bool first = true; for (auto const& [name, _] : registry()) { if (!first) std::printf(","); std::printf("%.*s", int(name.size()), name.data()); first = false; } std::printf("\n"); return 0; } if (arg1 == "--test") { if (argc < 3) { std::printf("usage: %s [--list] [--test ]\n", argv[0]); return 2; } #ifdef SIMPLETEST_CONFIG std::printf("SimpleTest built with config: " SIMPLETEST_XSTRINGIFY( SIMPLETEST_CONFIG) "\n"); #endif std::string_view name{ argv[2] }; auto it = registry().find(name); if (it == registry().end()) { std::printf("[ NOTFOUND ] %s\n", argv[2]); return 2; } int failed = 0; std::printf("[ RUN ] %.*s\n", int(it->first.size()), it->first.data()); try { it->second(); std::printf("[ OK] %.*s\n", int(it->first.size()), it->first.data()); } catch (failure const& f) { std::printf("[ FAILED ] %.*s at %s:%d : %s\n", int(it->first.size()), it->first.data(), f.file, f.line, f.expr); failed = 1; } catch (...) { std::printf("[ FAILED ] %.*s : unknown exception\n", int(it->first.size()), it->first.data()); failed = 1; } return failed; } if (argc > 1) { std::printf("usage: %s [--list] [--test ]\n", argv[0]); return 2; } #ifdef SIMPLETEST_CONFIG std::printf("SimpleTest built with config: " SIMPLETEST_XSTRINGIFY( SIMPLETEST_CONFIG) "\n"); #endif // Default: run all tests. int failed = 0; for (auto const& [name, func] : all()) { std::printf("[ RUN ] %.*s\n", int(name.size()), name.data()); try { func(); std::printf("[ OK ] %.*s\n", int(name.size()), name.data()); } catch (failure const& f) { std::printf("[ FAILED ] %.*s at %s:%d : %s\n", int(name.size()), name.data(), f.file, f.line, f.expr); failed = 1; } catch (...) { std::printf("[ FAILED ] %.*s : unknown exception\n", int(name.size()), name.data()); failed = 1; } } return failed; }