mirror of
https://github.com/open-source-parsers/jsoncpp.git
synced 2025-04-05 18:41:10 +02:00
444 lines
12 KiB
C++
444 lines
12 KiB
C++
// 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
|