// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors // Distributed under MIT license, or public domain if desired and // recognized in your jurisdiction. // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE #define _CRT_SECURE_NO_WARNINGS 1 // Prevents deprecation warning with MSVC #include "jsontest.h" #include #include #if defined(_MSC_VER) // Used to install a report hook that prevent dialog on assertion and error. #include #endif // if defined(_MSC_VER) #if defined(_WIN32) // Used to prevent dialog on memory fault. // Limits headers included by Windows.h #define WIN32_LEAN_AND_MEAN #define NOSERVICE #define NOMCX #define NOIME #define NOSOUND #define NOCOMM #define NORPC #define NOGDI #define NOUSER #define NODRIVERS #define NOLOGERROR #define NOPROFILER #define NOMEMMGR #define NOLFILEIO #define NOOPENFILE #define NORESOURCE #define NOATOM #define NOLANGUAGE #define NOLSTRING #define NODBCS #define NOKEYBOARDINFO #define NOGDICAPMASKS #define NOCOLOR #define NOGDIOBJ #define NODRAWTEXT #define NOTEXTMETRIC #define NOSCALABLEFONT #define NOBITMAP #define NORASTEROPS #define NOMETAFILE #define NOSYSMETRICS #define NOSYSTEMPARAMSINFO #define NOMSG #define NOWINSTYLES #define NOWINOFFSETS #define NOSHOWWINDOW #define NODEFERWINDOWPOS #define NOVIRTUALKEYCODES #define NOKEYSTATES #define NOWH #define NOMENUS #define NOSCROLL #define NOCLIPBOARD #define NOICONS #define NOMB #define NOSYSCOMMANDS #define NOMDI #define NOCTLMGR #define NOWINMESSAGES #include #endif // if defined(_WIN32) namespace JsonTest { // class TestResult // ////////////////////////////////////////////////////////////////// TestResult::TestResult() { // The root predicate has id 0 rootPredicateNode_.id_ = 0; rootPredicateNode_.next_ = nullptr; predicateStackTail_ = &rootPredicateNode_; } void TestResult::setTestName(const Json::String& name) { name_ = name; } TestResult& TestResult::addFailure(const char* file, unsigned int line, const char* expr) { /// Walks the PredicateContext stack adding them to failures_ if not already /// added. unsigned int nestingLevel = 0; PredicateContext* lastNode = rootPredicateNode_.next_; for (; lastNode != nullptr; lastNode = lastNode->next_) { if (lastNode->id_ > lastUsedPredicateId_) // new PredicateContext { lastUsedPredicateId_ = lastNode->id_; addFailureInfo(lastNode->file_, lastNode->line_, lastNode->expr_, nestingLevel); // Link the PredicateContext to the failure for message target when // popping the PredicateContext. lastNode->failure_ = &(failures_.back()); } ++nestingLevel; } // Adds the failed assertion addFailureInfo(file, line, expr, nestingLevel); messageTarget_ = &(failures_.back()); return *this; } void TestResult::addFailureInfo(const char* file, unsigned int line, const char* expr, unsigned int nestingLevel) { Failure failure; failure.file_ = file; failure.line_ = line; if (expr) { failure.expr_ = expr; } failure.nestingLevel_ = nestingLevel; failures_.push_back(failure); } TestResult& TestResult::popPredicateContext() { PredicateContext* lastNode = &rootPredicateNode_; while (lastNode->next_ != nullptr && lastNode->next_->next_ != nullptr) { lastNode = lastNode->next_; } // Set message target to popped failure PredicateContext* tail = lastNode->next_; if (tail != nullptr && tail->failure_ != nullptr) { messageTarget_ = tail->failure_; } // Remove tail from list predicateStackTail_ = lastNode; lastNode->next_ = nullptr; return *this; } bool TestResult::failed() const { return !failures_.empty(); } void TestResult::printFailure(bool printTestName) const { if (failures_.empty()) { return; } if (printTestName) { printf("* Detail of %s test failure:\n", name_.c_str()); } // Print in reverse to display the callstack in the right order for (const auto& failure : failures_) { Json::String indent(failure.nestingLevel_ * 2, ' '); if (failure.file_) { printf("%s%s(%u): ", indent.c_str(), failure.file_, failure.line_); } if (!failure.expr_.empty()) { printf("%s\n", failure.expr_.c_str()); } else if (failure.file_) { printf("\n"); } if (!failure.message_.empty()) { Json::String reindented = indentText(failure.message_, indent + " "); printf("%s\n", reindented.c_str()); } } } Json::String TestResult::indentText(const Json::String& text, const Json::String& indent) { Json::String reindented; Json::String::size_type lastIndex = 0; while (lastIndex < text.size()) { Json::String::size_type nextIndex = text.find('\n', lastIndex); if (nextIndex == Json::String::npos) { nextIndex = text.size() - 1; } reindented += indent; reindented += text.substr(lastIndex, nextIndex - lastIndex + 1); lastIndex = nextIndex + 1; } return reindented; } TestResult& TestResult::addToLastFailure(const Json::String& message) { if (messageTarget_ != nullptr) { messageTarget_->message_ += message; } return *this; } TestResult& TestResult::operator<<(Json::Int64 value) { return addToLastFailure(Json::valueToString(value)); } TestResult& TestResult::operator<<(Json::UInt64 value) { return addToLastFailure(Json::valueToString(value)); } TestResult& TestResult::operator<<(bool value) { return addToLastFailure(value ? "true" : "false"); } // class TestCase // ////////////////////////////////////////////////////////////////// TestCase::TestCase() = default; TestCase::~TestCase() = default; void TestCase::run(TestResult& result) { result_ = &result; runTestCase(); } // class Runner // ////////////////////////////////////////////////////////////////// Runner::Runner() = default; Runner& Runner::add(TestCaseFactory factory) { tests_.push_back(factory); return *this; } size_t Runner::testCount() const { return tests_.size(); } Json::String Runner::testNameAt(size_t index) const { TestCase* test = tests_[index](); Json::String name = test->testName(); delete test; return name; } void Runner::runTestAt(size_t index, TestResult& result) const { TestCase* test = tests_[index](); result.setTestName(test->testName()); printf("Testing %s: ", test->testName()); fflush(stdout); #if JSON_USE_EXCEPTION try { #endif // if JSON_USE_EXCEPTION test->run(result); #if JSON_USE_EXCEPTION } catch (const std::exception& e) { result.addFailure(__FILE__, __LINE__, "Unexpected exception caught:") << e.what(); } #endif // if JSON_USE_EXCEPTION delete test; const char* status = result.failed() ? "FAILED" : "OK"; printf("%s\n", status); fflush(stdout); } bool Runner::runAllTest(bool printSummary) const { size_t const count = testCount(); std::deque failures; for (size_t index = 0; index < count; ++index) { TestResult result; runTestAt(index, result); if (result.failed()) { failures.push_back(result); } } if (failures.empty()) { if (printSummary) { printf("All %zu tests passed\n", count); } return true; } for (auto& result : failures) { result.printFailure(count > 1); } if (printSummary) { size_t const failedCount = failures.size(); size_t const passedCount = count - failedCount; printf("%zu/%zu tests passed (%zu failure(s))\n", passedCount, count, failedCount); } return false; } bool Runner::testIndex(const Json::String& testName, size_t& indexOut) const { const size_t count = testCount(); for (size_t index = 0; index < count; ++index) { if (testNameAt(index) == testName) { indexOut = index; return true; } } return false; } void Runner::listTests() const { const size_t count = testCount(); for (size_t index = 0; index < count; ++index) { printf("%s\n", testNameAt(index).c_str()); } } int Runner::runCommandLine(int argc, const char* argv[]) const { // typedef std::deque TestNames; Runner subrunner; for (int index = 1; index < argc; ++index) { Json::String opt = argv[index]; if (opt == "--list-tests") { listTests(); return 0; } if (opt == "--test-auto") { preventDialogOnCrash(); } else if (opt == "--test") { ++index; if (index < argc) { size_t testNameIndex; if (testIndex(argv[index], testNameIndex)) { subrunner.add(tests_[testNameIndex]); } else { fprintf(stderr, "Test '%s' does not exist!\n", argv[index]); return 2; } } else { printUsage(argv[0]); return 2; } } else { printUsage(argv[0]); return 2; } } bool succeeded; if (subrunner.testCount() > 0) { succeeded = subrunner.runAllTest(subrunner.testCount() > 1); } else { succeeded = runAllTest(true); } return succeeded ? 0 : 1; } #if defined(_MSC_VER) && defined(_DEBUG) // Hook MSVCRT assertions to prevent dialog from appearing static int msvcrtSilentReportHook(int reportType, char* message, int* /*returnValue*/) { // The default CRT handling of error and assertion is to display // an error dialog to the user. // Instead, when an error or an assertion occurs, we force the // application to terminate using abort() after display // the message on stderr. if (reportType == _CRT_ERROR || reportType == _CRT_ASSERT) { // calling abort() cause the ReportHook to be called // The following is used to detect this case and let's the // error handler fallback on its default behaviour ( // display a warning message) static volatile bool isAborting = false; if (isAborting) { return TRUE; } isAborting = true; fprintf(stderr, "CRT Error/Assert:\n%s\n", message); fflush(stderr); abort(); } // Let's other reportType (_CRT_WARNING) be handled as they would by default return FALSE; } #endif // if defined(_MSC_VER) void Runner::preventDialogOnCrash() { #if defined(_MSC_VER) && defined(_DEBUG) // Install a hook to prevent MSVCRT error and assertion from // popping a dialog // This function a NO-OP in release configuration // (which cause warning since msvcrtSilentReportHook is not referenced) _CrtSetReportHook(&msvcrtSilentReportHook); #endif // if defined(_MSC_VER) // @todo investigate this handler (for buffer overflow) // _set_security_error_handler #if defined(_WIN32) // Prevents the system from popping a dialog for debugging if the // application fails due to invalid memory access. SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); #endif // if defined(_WIN32) } void Runner::printUsage(const char* appName) { printf("Usage: %s [options]\n" "\n" "If --test is not specified, then all the test cases be run.\n" "\n" "Valid options:\n" "--list-tests: print the name of all test cases on the standard\n" " output and exit.\n" "--test TESTNAME: executes the test case with the specified name.\n" " May be repeated.\n" "--test-auto: prevent dialog prompting for debugging on crash.\n", appName); } // Assertion functions // ////////////////////////////////////////////////////////////////// Json::String ToJsonString(const char* toConvert) { return Json::String(toConvert); } Json::String ToJsonString(Json::String in) { return in; } #if JSONCPP_USING_SECURE_MEMORY Json::String ToJsonString(std::string in) { return Json::String(in.data(), in.data() + in.length()); } #endif TestResult& checkStringEqual(TestResult& result, const Json::String& expected, const Json::String& actual, const char* file, unsigned int line, const char* expr) { if (expected != actual) { result.addFailure(file, line, expr); result << "Expected: '" << expected << "'\n"; result << "Actual : '" << actual << "'"; } return result; } } // namespace JsonTest