| // Copyright 2007-2010 Baptiste Lepilleur | |
| // 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 <stdio.h> | |
| #include <string> | |
| #if defined(_MSC_VER) | |
| // Used to install a report hook that prevent dialog on assertion and error. | |
| #include <crtdbg.h> | |
| #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 <windows.h> | |
| #endif // if defined(_WIN32) | |
| namespace JsonTest { | |
| // class TestResult | |
| // ////////////////////////////////////////////////////////////////// | |
| TestResult::TestResult() | |
| : predicateId_(1), lastUsedPredicateId_(0), messageTarget_(0) { | |
| // The root predicate has id 0 | |
| rootPredicateNode_.id_ = 0; | |
| rootPredicateNode_.next_ = 0; | |
| predicateStackTail_ = &rootPredicateNode_; | |
| } | |
| void TestResult::setTestName(const std::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 != 0; 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_ != 0 && lastNode->next_->next_ != 0) { | |
| lastNode = lastNode->next_; | |
| } | |
| // Set message target to popped failure | |
| PredicateContext* tail = lastNode->next_; | |
| if (tail != 0 && tail->failure_ != 0) { | |
| messageTarget_ = tail->failure_; | |
| } | |
| // Remove tail from list | |
| predicateStackTail_ = lastNode; | |
| lastNode->next_ = 0; | |
| return *this; | |
| } | |
| bool TestResult::failed() const { return !failures_.empty(); } | |
| unsigned int TestResult::getAssertionNestingLevel() const { | |
| unsigned int level = 0; | |
| const PredicateContext* lastNode = &rootPredicateNode_; | |
| while (lastNode->next_ != 0) { | |
| lastNode = lastNode->next_; | |
| ++level; | |
| } | |
| return level; | |
| } | |
| 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 | |
| Failures::const_iterator itEnd = failures_.end(); | |
| for (Failures::const_iterator it = failures_.begin(); it != itEnd; ++it) { | |
| const Failure& failure = *it; | |
| std::string indent(failure.nestingLevel_ * 2, ' '); | |
| if (failure.file_) { | |
| printf("%s%s(%d): ", 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()) { | |
| std::string reindented = indentText(failure.message_, indent + " "); | |
| printf("%s\n", reindented.c_str()); | |
| } | |
| } | |
| } | |
| std::string TestResult::indentText(const std::string& text, | |
| const std::string& indent) { | |
| std::string reindented; | |
| std::string::size_type lastIndex = 0; | |
| while (lastIndex < text.size()) { | |
| std::string::size_type nextIndex = text.find('\n', lastIndex); | |
| if (nextIndex == std::string::npos) { | |
| nextIndex = text.size() - 1; | |
| } | |
| reindented += indent; | |
| reindented += text.substr(lastIndex, nextIndex - lastIndex + 1); | |
| lastIndex = nextIndex + 1; | |
| } | |
| return reindented; | |
| } | |
| TestResult& TestResult::addToLastFailure(const std::string& message) { | |
| if (messageTarget_ != 0) { | |
| 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() : result_(0) {} | |
| TestCase::~TestCase() {} | |
| void TestCase::run(TestResult& result) { | |
| result_ = &result; | |
| runTestCase(); | |
| } | |
| // class Runner | |
| // ////////////////////////////////////////////////////////////////// | |
| Runner::Runner() {} | |
| Runner& Runner::add(TestCaseFactory factory) { | |
| tests_.push_back(factory); | |
| return *this; | |
| } | |
| unsigned int Runner::testCount() const { | |
| return static_cast<unsigned int>(tests_.size()); | |
| } | |
| std::string Runner::testNameAt(unsigned int index) const { | |
| TestCase* test = tests_[index](); | |
| std::string name = test->testName(); | |
| delete test; | |
| return name; | |
| } | |
| void Runner::runTestAt(unsigned int 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 { | |
| unsigned int count = testCount(); | |
| std::deque<TestResult> failures; | |
| for (unsigned int index = 0; index < count; ++index) { | |
| TestResult result; | |
| runTestAt(index, result); | |
| if (result.failed()) { | |
| failures.push_back(result); | |
| } | |
| } | |
| if (failures.empty()) { | |
| if (printSummary) { | |
| printf("All %d tests passed\n", count); | |
| } | |
| return true; | |
| } else { | |
| for (unsigned int index = 0; index < failures.size(); ++index) { | |
| TestResult& result = failures[index]; | |
| result.printFailure(count > 1); | |
| } | |
| if (printSummary) { | |
| unsigned int failedCount = static_cast<unsigned int>(failures.size()); | |
| unsigned int passedCount = count - failedCount; | |
| printf("%d/%d tests passed (%d failure(s))\n", | |
| passedCount, | |
| count, | |
| failedCount); | |
| } | |
| return false; | |
| } | |
| } | |
| bool Runner::testIndex(const std::string& testName, | |
| unsigned int& indexOut) const { | |
| unsigned int count = testCount(); | |
| for (unsigned int index = 0; index < count; ++index) { | |
| if (testNameAt(index) == testName) { | |
| indexOut = index; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| void Runner::listTests() const { | |
| unsigned int count = testCount(); | |
| for (unsigned int index = 0; index < count; ++index) { | |
| printf("%s\n", testNameAt(index).c_str()); | |
| } | |
| } | |
| int Runner::runCommandLine(int argc, const char* argv[]) const { | |
| typedef std::deque<std::string> TestNames; | |
| Runner subrunner; | |
| for (int index = 1; index < argc; ++index) { | |
| std::string opt = argv[index]; | |
| if (opt == "--list-tests") { | |
| listTests(); | |
| return 0; | |
| } else if (opt == "--test-auto") { | |
| preventDialogOnCrash(); | |
| } else if (opt == "--test") { | |
| ++index; | |
| if (index < argc) { | |
| unsigned int 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 investiguate 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 | |
| // ////////////////////////////////////////////////////////////////// | |
| TestResult& checkStringEqual(TestResult& result, | |
| const std::string& expected, | |
| const std::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 |