#include #include #include #include namespace drogon { namespace test { std::mutex ThreadSafeStream::mtx_; namespace internal { std::mutex mtxRegister; std::mutex mtxTestStats; bool testHasPrinted = false; std::set registeredTests; std::promise allTestRan; std::atomic numAssertions; std::atomic numCorrectAssertions; size_t numTestCases; std::atomic numFailedTestCases; bool printSuccessfulTests; void registerCase(Case *test) { std::unique_lock l(mtxRegister); registeredTests.insert(test); } void unregisterCase(Case *test) { std::unique_lock l(mtxRegister); registeredTests.erase(test); if (registeredTests.empty()) allTestRan.set_value(); } static std::string leftpad(const std::string &str, size_t len) { if (len <= str.size()) return str; return std::string(len - str.size(), ' ') + str; } std::string prettifyString(const std::string_view sv, size_t maxLength) { if (sv.size() <= maxLength) return "\"" + escapeString(sv) + "\""; const std::string msg = "...\" (truncated)"; return "\"" + escapeString(sv.substr(0, maxLength)) + msg; } } // namespace internal static void printHelp(std::string_view argv0) { print() << "A Drogon Test application:\n\n" << "Usage: " << argv0 << " [options]\n" << "options:\n" << " -r Run a specific test\n" << " -s Print successful tests\n" << " -l List available tests\n" << " -h | --help Print this help message\n"; } void printTestStats() { std::unique_lock lk(internal::mtxTestStats); if (internal::testHasPrinted) return; const size_t successAssertions = internal::numCorrectAssertions; const size_t totalAssertions = internal::numAssertions; const size_t successTests = internal::numTestCases - internal::numFailedTestCases; const size_t totalTests = internal::numTestCases; float ratio; if (totalAssertions != 0) ratio = (float)successTests / totalTests; else ratio = 1; const size_t barSize = 80; auto greenBar = size_t(barSize * ratio); auto redBar = size_t(barSize * (1 - ratio)); if (greenBar + redBar != barSize) { float fraction = (ratio * barSize) - (size_t)(ratio * barSize); if (fraction >= 0.5f) greenBar++; else redBar++; } if (successAssertions != totalAssertions && redBar == 0) { redBar = 1; greenBar--; } print() << "\n\x1B[0;31m" << std::string(redBar, '=') << "\x1B[0;32m" << std::string(greenBar, '=') << "\x1B[0m\n"; if (successAssertions == totalAssertions) { print() << "\x1B[1;32m All tests passed\x1B[0m (" << totalAssertions << " assertions in " << totalTests << " tests cases).\n"; } else { std::string totalAssertsionStr = std::to_string(totalAssertions); std::string successAssertionsStr = std::to_string(successAssertions); std::string failedAssertsionStr = std::to_string(totalAssertions - successAssertions); std::string totalTestsStr = std::to_string(totalTests); std::string successTestsStr = std::to_string(successTests); std::string failedTestsStr = std::to_string(totalTests - successTests); const size_t totalLen = (std::max)(totalAssertsionStr.size(), totalTestsStr.size()); const size_t successLen = (std::max)(successAssertionsStr.size(), successTestsStr.size()); const size_t failedLen = (std::max)(failedAssertsionStr.size(), failedTestsStr.size()); using internal::leftpad; print() << "assertions: " << leftpad(totalAssertsionStr, totalLen) << " | \x1B[0;32m" << leftpad(successAssertionsStr, successLen) << " passed\x1B[0m | \x1B[0;31m" << leftpad(failedAssertsionStr, failedLen) << " failed\x1B[0m\n" << "test cases: " << leftpad(totalTestsStr, totalLen) << " | \x1B[0;32m" << leftpad(successTestsStr, successLen) << " passed\x1B[0m | \x1B[0;31m" << leftpad(failedTestsStr, failedLen) << " failed\x1B[0m\n"; } internal::testHasPrinted = true; } int run(int argc, char **argv) { internal::numCorrectAssertions = 0; internal::numAssertions = 0; internal::numFailedTestCases = 0; internal::numTestCases = 0; internal::printSuccessfulTests = false; std::string targetTest; bool listTests = false; for (int i = 1; i < argc; i++) { const std::string param = argv[i]; if (param == "-r") { if (!targetTest.empty()) { printErr() << "Only one test can be specified to run\n"; exit(1); } else if (i + 1 >= argc) { printErr() << "Missing test name after -r.\n"; exit(1); } targetTest = argv[i + 1]; i++; } else if (param == "-h" || param == "--help") { printHelp(argv[0]); exit(0); } else if (param == "-s") { internal::printSuccessfulTests = true; } else if (param == "-l") { listTests = true; } else { printErr() << "Unknown parameter: " << param << "\n"; printHelp(argv[0]); exit(1); } } auto classNames = DrClassMap::getAllClassName(); if (listTests) { print() << "Available Tests:\n"; for (const auto &name : classNames) { if (name.find(DROGON_TESTCASE_PREIX_STR_) == 0) { auto test = std::unique_ptr(DrClassMap::newObject(name)); auto ptr = dynamic_cast(test.get()); if (ptr == nullptr) continue; print() << " " << ptr->name() << "\n"; } } exit(0); } std::vector> testCases; // NOTE: Registering a dummy case prevents the test-end signal to be // emitted too early as there's always an case that hasn't finish std::shared_ptr dummyCase = std::make_shared("__dummy_dummy_"); for (const auto &name : classNames) { if (name.find(DROGON_TESTCASE_PREIX_STR_) == 0) { auto obj = std::shared_ptr(DrClassMap::newObject(name)); auto test = std::dynamic_pointer_cast(obj); if (test == nullptr) { LOG_WARN << "Class " << name << " seems to be a test case. But type information " "disagrees."; continue; } if (targetTest.empty() || test->name() == targetTest) { internal::numTestCases++; test->doTest_(std::make_shared(test->name())); testCases.emplace_back(std::move(test)); } } } dummyCase = {}; if (targetTest != "" && internal::numTestCases == 0) { printErr() << "Cannot find test named " << targetTest << "\n"; exit(1); } std::unique_lock l(internal::mtxRegister); if (internal::registeredTests.empty() == false) { auto fut = internal::allTestRan.get_future(); l.unlock(); fut.get(); assert(internal::registeredTests.empty()); } testCases.clear(); printTestStats(); return internal::numCorrectAssertions != internal::numAssertions; } ThreadSafeStream print() { return ThreadSafeStream(std::cout); } ThreadSafeStream printErr() { return ThreadSafeStream(std::cerr); } } // namespace test } // namespace drogon