// 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 #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() : 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( 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 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( 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 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