| // 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) |
| // 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) |
| // Install a hook to prevent MSVCRT error and assertion from |
| // popping a dialog. |
| _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 |