#include #include #include #include #include #include #include #include #include #include namespace psemek::test { static std::map tests; static std::string normalize(std::string name) { std::replace(name.begin(), name.end(), '_', '/'); for (std::size_t i = 0;;) { i = name.find("//", i); if (i == std::string::npos) break; name.replace(i, 2, "_"); ++i; } return name; } void add_test_case(char const * name, void(*f)(context &)) { std::string pname = normalize(name); if (tests.count(pname)) { std::cerr << "Test " << pname << " already registered" << std::endl; std::exit(EXIT_FAILURE); } tests[pname] = f; } } int main(int argc, char ** argv) { std::set tests; if (argc == 1) { for (auto const & p : psemek::test::tests) tests.insert(p.first); } else { for (int a = 1; a < argc; ++a) { std::string pattern = std::string(argv[a]); if (pattern.empty()) { std::cerr << "Empty test case pattern in argument list" << std::endl; std::exit(EXIT_FAILURE); } if (pattern.back() != '/') pattern += "/"; for (auto const & p : psemek::test::tests) { if (p.first == argv[a] || p.first.starts_with(pattern)) tests.insert(p.first); } } } std::size_t test_count = tests.size(); std::size_t const test_index_len = std::ceil(std::log10(test_count + 1)); std::string const indent = std::string(2 * test_index_len + 6, ' '); std::size_t max_name_length = 0; for (auto const & name : tests) max_name_length = std::max(max_name_length, name.size()); std::size_t success = 0; using clock = std::chrono::high_resolution_clock; auto all_start = clock::now(); std::size_t i = 0; for (auto const & name : tests) { ++i; std::cout << '[' << std::setfill(' ') << std::setw(test_index_len) << std::right << i << '/' << test_count << "] " << std::left << std::setfill('.') << std::setw(max_name_length + 5) << name; psemek::test::context ctx; try { auto start = clock::now(); psemek::test::tests[name](ctx); auto end = clock::now(); std::cout << "ok (" << psemek::util::pretty(end - start, std::chrono::milliseconds{1}) << ")" << std::endl; ++success; } catch (psemek::test::failure const & e) { std::cout << "failure" << std::endl; std::cout << indent << "Reason: " << e.message() << std::endl; std::cout << indent << "Location: " << e.location() << std::endl; } catch (std::exception const & e) { std::cout << "failure: " << e.what() << std::endl; } catch (...) { std::cout << "failure: (unknown exception)" << std::endl; } std::size_t max_profile_name_len = 0; for (auto const & p : ctx.profile) max_profile_name_len = std::max(max_profile_name_len, p.name.size()); for (auto const & p : ctx.profile) { std::cout << indent << std::setfill(' ') << std::setw(max_profile_name_len + 5) << p.name << psemek::util::pretty(p.duration, std::chrono::milliseconds{1}) << (p.ended_with_exception ? " (exception)" : "") << std::endl; } } auto all_end = clock::now(); std::cout << std::endl; std::cout << success << '/' << test_count << " passed (" << psemek::util::pretty(all_end - all_start, std::chrono::milliseconds{1}) << ")" << std::endl; if (success <= test_count) return EXIT_FAILURE; }