SimpleTest.h 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. #pragma once
  2. #include <cstdio>
  3. #include <map>
  4. #include <string_view>
  5. namespace SimpleTest {
  6. using TestFunc = void (*)();
  7. using Registry = std::map<std::string_view, TestFunc, std::less<>>;
  8. inline Registry g_registry;
  9. inline Registry& registry()
  10. {
  11. return g_registry;
  12. }
  13. struct failure
  14. {
  15. char const* file;
  16. int line;
  17. char const* expr;
  18. };
  19. struct Registrar
  20. {
  21. template <std::size_t N>
  22. Registrar(char const (&name)[N], TestFunc f)
  23. {
  24. auto [it, inserted] =
  25. registry().emplace(std::string_view{ name, N ? (N - 1) : 0 }, f);
  26. if (!inserted) {
  27. std::printf("[ WARN ] duplicate test name: %.*s\n",
  28. int(it->first.size()), it->first.data());
  29. }
  30. }
  31. };
  32. inline Registry const& all()
  33. {
  34. return registry();
  35. }
  36. inline TestFunc find(std::string_view name)
  37. {
  38. auto it = registry().find(name);
  39. return it == registry().end() ? nullptr : it->second;
  40. }
  41. }
  42. #define SIMPLETEST_CONCAT_(a, b) a##b
  43. #define SIMPLETEST_CONCAT(a, b) SIMPLETEST_CONCAT_(a, b)
  44. #define TEST(name_literal) \
  45. static void SIMPLETEST_CONCAT(_simpletest_fn_, __LINE__)(); \
  46. static ::SimpleTest::Registrar SIMPLETEST_CONCAT(_simpletest_reg_, \
  47. __LINE__)( \
  48. name_literal, &SIMPLETEST_CONCAT(_simpletest_fn_, __LINE__)); \
  49. static void SIMPLETEST_CONCAT(_simpletest_fn_, __LINE__)()
  50. // Minimal assertion
  51. #define REQUIRE(expr) \
  52. do { \
  53. if (!(expr)) \
  54. throw ::SimpleTest::failure{ __FILE__, __LINE__, #expr }; \
  55. } while (0)
  56. int main(int argc, char** argv)
  57. {
  58. using namespace ::SimpleTest;
  59. std::string_view arg1 =
  60. (argc >= 2) ? std::string_view{ argv[1] } : std::string_view{};
  61. if (arg1 == "--list") {
  62. bool first = true;
  63. for (auto const& [name, _] : registry()) {
  64. if (!first)
  65. std::printf(",");
  66. std::printf("%.*s", int(name.size()), name.data());
  67. first = false;
  68. }
  69. std::printf("\n");
  70. return 0;
  71. }
  72. if (arg1 == "--test") {
  73. if (argc < 3) {
  74. std::printf("usage: %s [--list] [--test <name>]\n", argv[0]);
  75. return 2;
  76. }
  77. #ifdef SIMPLETEST_CONFIG
  78. std::printf("SimpleTest built with config: %s\n", SIMPLETEST_CONFIG);
  79. #endif
  80. std::string_view name{ argv[2] };
  81. auto it = registry().find(name);
  82. if (it == registry().end()) {
  83. std::printf("[ NOTFOUND ] %s\n", argv[2]);
  84. return 2;
  85. }
  86. int failed = 0;
  87. std::printf("[ RUN ] %.*s\n", int(it->first.size()),
  88. it->first.data());
  89. try {
  90. it->second();
  91. std::printf("[ OK] %.*s\n", int(it->first.size()),
  92. it->first.data());
  93. } catch (failure const& f) {
  94. std::printf("[ FAILED ] %.*s at %s:%d : %s\n", int(it->first.size()),
  95. it->first.data(), f.file, f.line, f.expr);
  96. failed = 1;
  97. } catch (...) {
  98. std::printf("[ FAILED ] %.*s : unknown exception\n",
  99. int(it->first.size()), it->first.data());
  100. failed = 1;
  101. }
  102. return failed;
  103. }
  104. if (argc > 1) {
  105. std::printf("usage: %s [--list] [--test <name>]\n", argv[0]);
  106. return 2;
  107. }
  108. #ifdef SIMPLETEST_CONFIG
  109. std::printf("SimpleTest built with config: %s\n", SIMPLETEST_CONFIG);
  110. #endif
  111. // Default: run all tests.
  112. int failed = 0;
  113. for (auto const& [name, func] : all()) {
  114. std::printf("[ RUN ] %.*s\n", int(name.size()), name.data());
  115. try {
  116. func();
  117. std::printf("[ OK ] %.*s\n", int(name.size()), name.data());
  118. } catch (failure const& f) {
  119. std::printf("[ FAILED ] %.*s at %s:%d : %s\n", int(name.size()),
  120. name.data(), f.file, f.line, f.expr);
  121. failed = 1;
  122. } catch (...) {
  123. std::printf("[ FAILED ] %.*s : unknown exception\n", int(name.size()),
  124. name.data());
  125. failed = 1;
  126. }
  127. }
  128. return failed;
  129. }