Leon Scroggins III | f59fb0e | 2014-05-28 15:19:42 -0400 | [diff] [blame^] | 1 | // Copyright 2007-2010 Baptiste Lepilleur |
| 2 | // Distributed under MIT license, or public domain if desired and |
| 3 | // recognized in your jurisdiction. |
| 4 | // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE |
| 5 | |
| 6 | #define _CRT_SECURE_NO_WARNINGS 1 // Prevents deprecation warning with MSVC |
| 7 | #include "jsontest.h" |
| 8 | #include <stdio.h> |
| 9 | #include <string> |
| 10 | |
| 11 | #if defined(_MSC_VER) |
| 12 | // Used to install a report hook that prevent dialog on assertion and error. |
| 13 | # include <crtdbg.h> |
| 14 | #endif // if defined(_MSC_VER) |
| 15 | |
| 16 | #if defined(_WIN32) |
| 17 | // Used to prevent dialog on memory fault. |
| 18 | // Limits headers included by Windows.h |
| 19 | # define WIN32_LEAN_AND_MEAN |
| 20 | # define NOSERVICE |
| 21 | # define NOMCX |
| 22 | # define NOIME |
| 23 | # define NOSOUND |
| 24 | # define NOCOMM |
| 25 | # define NORPC |
| 26 | # define NOGDI |
| 27 | # define NOUSER |
| 28 | # define NODRIVERS |
| 29 | # define NOLOGERROR |
| 30 | # define NOPROFILER |
| 31 | # define NOMEMMGR |
| 32 | # define NOLFILEIO |
| 33 | # define NOOPENFILE |
| 34 | # define NORESOURCE |
| 35 | # define NOATOM |
| 36 | # define NOLANGUAGE |
| 37 | # define NOLSTRING |
| 38 | # define NODBCS |
| 39 | # define NOKEYBOARDINFO |
| 40 | # define NOGDICAPMASKS |
| 41 | # define NOCOLOR |
| 42 | # define NOGDIOBJ |
| 43 | # define NODRAWTEXT |
| 44 | # define NOTEXTMETRIC |
| 45 | # define NOSCALABLEFONT |
| 46 | # define NOBITMAP |
| 47 | # define NORASTEROPS |
| 48 | # define NOMETAFILE |
| 49 | # define NOSYSMETRICS |
| 50 | # define NOSYSTEMPARAMSINFO |
| 51 | # define NOMSG |
| 52 | # define NOWINSTYLES |
| 53 | # define NOWINOFFSETS |
| 54 | # define NOSHOWWINDOW |
| 55 | # define NODEFERWINDOWPOS |
| 56 | # define NOVIRTUALKEYCODES |
| 57 | # define NOKEYSTATES |
| 58 | # define NOWH |
| 59 | # define NOMENUS |
| 60 | # define NOSCROLL |
| 61 | # define NOCLIPBOARD |
| 62 | # define NOICONS |
| 63 | # define NOMB |
| 64 | # define NOSYSCOMMANDS |
| 65 | # define NOMDI |
| 66 | # define NOCTLMGR |
| 67 | # define NOWINMESSAGES |
| 68 | # include <windows.h> |
| 69 | #endif // if defined(_WIN32) |
| 70 | |
| 71 | namespace JsonTest { |
| 72 | |
| 73 | |
| 74 | // class TestResult |
| 75 | // ////////////////////////////////////////////////////////////////// |
| 76 | |
| 77 | TestResult::TestResult() |
| 78 | : predicateId_( 1 ) |
| 79 | , lastUsedPredicateId_( 0 ) |
| 80 | , messageTarget_( 0 ) |
| 81 | { |
| 82 | // The root predicate has id 0 |
| 83 | rootPredicateNode_.id_ = 0; |
| 84 | rootPredicateNode_.next_ = 0; |
| 85 | predicateStackTail_ = &rootPredicateNode_; |
| 86 | } |
| 87 | |
| 88 | |
| 89 | void |
| 90 | TestResult::setTestName( const std::string &name ) |
| 91 | { |
| 92 | name_ = name; |
| 93 | } |
| 94 | |
| 95 | TestResult & |
| 96 | TestResult::addFailure( const char *file, unsigned int line, |
| 97 | const char *expr ) |
| 98 | { |
| 99 | /// Walks the PredicateContext stack adding them to failures_ if not already added. |
| 100 | unsigned int nestingLevel = 0; |
| 101 | PredicateContext *lastNode = rootPredicateNode_.next_; |
| 102 | for ( ; lastNode != 0; lastNode = lastNode->next_ ) |
| 103 | { |
| 104 | if ( lastNode->id_ > lastUsedPredicateId_ ) // new PredicateContext |
| 105 | { |
| 106 | lastUsedPredicateId_ = lastNode->id_; |
| 107 | addFailureInfo( lastNode->file_, lastNode->line_, lastNode->expr_, |
| 108 | nestingLevel ); |
| 109 | // Link the PredicateContext to the failure for message target when |
| 110 | // popping the PredicateContext. |
| 111 | lastNode->failure_ = &( failures_.back() ); |
| 112 | } |
| 113 | ++nestingLevel; |
| 114 | } |
| 115 | |
| 116 | // Adds the failed assertion |
| 117 | addFailureInfo( file, line, expr, nestingLevel ); |
| 118 | messageTarget_ = &( failures_.back() ); |
| 119 | return *this; |
| 120 | } |
| 121 | |
| 122 | |
| 123 | void |
| 124 | TestResult::addFailureInfo( const char *file, unsigned int line, |
| 125 | const char *expr, unsigned int nestingLevel ) |
| 126 | { |
| 127 | Failure failure; |
| 128 | failure.file_ = file; |
| 129 | failure.line_ = line; |
| 130 | if ( expr ) |
| 131 | { |
| 132 | failure.expr_ = expr; |
| 133 | } |
| 134 | failure.nestingLevel_ = nestingLevel; |
| 135 | failures_.push_back( failure ); |
| 136 | } |
| 137 | |
| 138 | |
| 139 | TestResult & |
| 140 | TestResult::popPredicateContext() |
| 141 | { |
| 142 | PredicateContext *lastNode = &rootPredicateNode_; |
| 143 | while ( lastNode->next_ != 0 && lastNode->next_->next_ != 0 ) |
| 144 | { |
| 145 | lastNode = lastNode->next_; |
| 146 | } |
| 147 | // Set message target to popped failure |
| 148 | PredicateContext *tail = lastNode->next_; |
| 149 | if ( tail != 0 && tail->failure_ != 0 ) |
| 150 | { |
| 151 | messageTarget_ = tail->failure_; |
| 152 | } |
| 153 | // Remove tail from list |
| 154 | predicateStackTail_ = lastNode; |
| 155 | lastNode->next_ = 0; |
| 156 | return *this; |
| 157 | } |
| 158 | |
| 159 | |
| 160 | bool |
| 161 | TestResult::failed() const |
| 162 | { |
| 163 | return !failures_.empty(); |
| 164 | } |
| 165 | |
| 166 | |
| 167 | unsigned int |
| 168 | TestResult::getAssertionNestingLevel() const |
| 169 | { |
| 170 | unsigned int level = 0; |
| 171 | const PredicateContext *lastNode = &rootPredicateNode_; |
| 172 | while ( lastNode->next_ != 0 ) |
| 173 | { |
| 174 | lastNode = lastNode->next_; |
| 175 | ++level; |
| 176 | } |
| 177 | return level; |
| 178 | } |
| 179 | |
| 180 | |
| 181 | void |
| 182 | TestResult::printFailure( bool printTestName ) const |
| 183 | { |
| 184 | if ( failures_.empty() ) |
| 185 | { |
| 186 | return; |
| 187 | } |
| 188 | |
| 189 | if ( printTestName ) |
| 190 | { |
| 191 | printf( "* Detail of %s test failure:\n", name_.c_str() ); |
| 192 | } |
| 193 | |
| 194 | // Print in reverse to display the callstack in the right order |
| 195 | Failures::const_iterator itEnd = failures_.end(); |
| 196 | for ( Failures::const_iterator it = failures_.begin(); it != itEnd; ++it ) |
| 197 | { |
| 198 | const Failure &failure = *it; |
| 199 | std::string indent( failure.nestingLevel_ * 2, ' ' ); |
| 200 | if ( failure.file_ ) |
| 201 | { |
| 202 | printf( "%s%s(%d): ", indent.c_str(), failure.file_, failure.line_ ); |
| 203 | } |
| 204 | if ( !failure.expr_.empty() ) |
| 205 | { |
| 206 | printf( "%s\n", failure.expr_.c_str() ); |
| 207 | } |
| 208 | else if ( failure.file_ ) |
| 209 | { |
| 210 | printf( "\n" ); |
| 211 | } |
| 212 | if ( !failure.message_.empty() ) |
| 213 | { |
| 214 | std::string reindented = indentText( failure.message_, indent + " " ); |
| 215 | printf( "%s\n", reindented.c_str() ); |
| 216 | } |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | |
| 221 | std::string |
| 222 | TestResult::indentText( const std::string &text, |
| 223 | const std::string &indent ) |
| 224 | { |
| 225 | std::string reindented; |
| 226 | std::string::size_type lastIndex = 0; |
| 227 | while ( lastIndex < text.size() ) |
| 228 | { |
| 229 | std::string::size_type nextIndex = text.find( '\n', lastIndex ); |
| 230 | if ( nextIndex == std::string::npos ) |
| 231 | { |
| 232 | nextIndex = text.size() - 1; |
| 233 | } |
| 234 | reindented += indent; |
| 235 | reindented += text.substr( lastIndex, nextIndex - lastIndex + 1 ); |
| 236 | lastIndex = nextIndex + 1; |
| 237 | } |
| 238 | return reindented; |
| 239 | } |
| 240 | |
| 241 | |
| 242 | TestResult & |
| 243 | TestResult::addToLastFailure( const std::string &message ) |
| 244 | { |
| 245 | if ( messageTarget_ != 0 ) |
| 246 | { |
| 247 | messageTarget_->message_ += message; |
| 248 | } |
| 249 | return *this; |
| 250 | } |
| 251 | |
| 252 | TestResult & |
| 253 | TestResult::operator << ( Json::Int64 value ) { |
| 254 | return addToLastFailure( Json::valueToString(value) ); |
| 255 | } |
| 256 | |
| 257 | |
| 258 | TestResult & |
| 259 | TestResult::operator << ( Json::UInt64 value ) { |
| 260 | return addToLastFailure( Json::valueToString(value) ); |
| 261 | } |
| 262 | |
| 263 | |
| 264 | TestResult & |
| 265 | TestResult::operator << ( bool value ) { |
| 266 | return addToLastFailure(value ? "true" : "false"); |
| 267 | } |
| 268 | |
| 269 | |
| 270 | // class TestCase |
| 271 | // ////////////////////////////////////////////////////////////////// |
| 272 | |
| 273 | TestCase::TestCase() |
| 274 | : result_( 0 ) |
| 275 | { |
| 276 | } |
| 277 | |
| 278 | |
| 279 | TestCase::~TestCase() |
| 280 | { |
| 281 | } |
| 282 | |
| 283 | |
| 284 | void |
| 285 | TestCase::run( TestResult &result ) |
| 286 | { |
| 287 | result_ = &result; |
| 288 | runTestCase(); |
| 289 | } |
| 290 | |
| 291 | |
| 292 | |
| 293 | // class Runner |
| 294 | // ////////////////////////////////////////////////////////////////// |
| 295 | |
| 296 | Runner::Runner() |
| 297 | { |
| 298 | } |
| 299 | |
| 300 | |
| 301 | Runner & |
| 302 | Runner::add( TestCaseFactory factory ) |
| 303 | { |
| 304 | tests_.push_back( factory ); |
| 305 | return *this; |
| 306 | } |
| 307 | |
| 308 | |
| 309 | unsigned int |
| 310 | Runner::testCount() const |
| 311 | { |
| 312 | return static_cast<unsigned int>( tests_.size() ); |
| 313 | } |
| 314 | |
| 315 | |
| 316 | std::string |
| 317 | Runner::testNameAt( unsigned int index ) const |
| 318 | { |
| 319 | TestCase *test = tests_[index](); |
| 320 | std::string name = test->testName(); |
| 321 | delete test; |
| 322 | return name; |
| 323 | } |
| 324 | |
| 325 | |
| 326 | void |
| 327 | Runner::runTestAt( unsigned int index, TestResult &result ) const |
| 328 | { |
| 329 | TestCase *test = tests_[index](); |
| 330 | result.setTestName( test->testName() ); |
| 331 | printf( "Testing %s: ", test->testName() ); |
| 332 | fflush( stdout ); |
| 333 | #if JSON_USE_EXCEPTION |
| 334 | try |
| 335 | { |
| 336 | #endif // if JSON_USE_EXCEPTION |
| 337 | test->run( result ); |
| 338 | #if JSON_USE_EXCEPTION |
| 339 | } |
| 340 | catch ( const std::exception &e ) |
| 341 | { |
| 342 | result.addFailure( __FILE__, __LINE__, |
| 343 | "Unexpected exception caught:" ) << e.what(); |
| 344 | } |
| 345 | #endif // if JSON_USE_EXCEPTION |
| 346 | delete test; |
| 347 | const char *status = result.failed() ? "FAILED" |
| 348 | : "OK"; |
| 349 | printf( "%s\n", status ); |
| 350 | fflush( stdout ); |
| 351 | } |
| 352 | |
| 353 | |
| 354 | bool |
| 355 | Runner::runAllTest( bool printSummary ) const |
| 356 | { |
| 357 | unsigned int count = testCount(); |
| 358 | std::deque<TestResult> failures; |
| 359 | for ( unsigned int index = 0; index < count; ++index ) |
| 360 | { |
| 361 | TestResult result; |
| 362 | runTestAt( index, result ); |
| 363 | if ( result.failed() ) |
| 364 | { |
| 365 | failures.push_back( result ); |
| 366 | } |
| 367 | } |
| 368 | |
| 369 | if ( failures.empty() ) |
| 370 | { |
| 371 | if ( printSummary ) |
| 372 | { |
| 373 | printf( "All %d tests passed\n", count ); |
| 374 | } |
| 375 | return true; |
| 376 | } |
| 377 | else |
| 378 | { |
| 379 | for ( unsigned int index = 0; index < failures.size(); ++index ) |
| 380 | { |
| 381 | TestResult &result = failures[index]; |
| 382 | result.printFailure( count > 1 ); |
| 383 | } |
| 384 | |
| 385 | if ( printSummary ) |
| 386 | { |
| 387 | unsigned int failedCount = static_cast<unsigned int>( failures.size() ); |
| 388 | unsigned int passedCount = count - failedCount; |
| 389 | printf( "%d/%d tests passed (%d failure(s))\n", passedCount, count, failedCount ); |
| 390 | } |
| 391 | return false; |
| 392 | } |
| 393 | } |
| 394 | |
| 395 | |
| 396 | bool |
| 397 | Runner::testIndex( const std::string &testName, |
| 398 | unsigned int &indexOut ) const |
| 399 | { |
| 400 | unsigned int count = testCount(); |
| 401 | for ( unsigned int index = 0; index < count; ++index ) |
| 402 | { |
| 403 | if ( testNameAt(index) == testName ) |
| 404 | { |
| 405 | indexOut = index; |
| 406 | return true; |
| 407 | } |
| 408 | } |
| 409 | return false; |
| 410 | } |
| 411 | |
| 412 | |
| 413 | void |
| 414 | Runner::listTests() const |
| 415 | { |
| 416 | unsigned int count = testCount(); |
| 417 | for ( unsigned int index = 0; index < count; ++index ) |
| 418 | { |
| 419 | printf( "%s\n", testNameAt( index ).c_str() ); |
| 420 | } |
| 421 | } |
| 422 | |
| 423 | |
| 424 | int |
| 425 | Runner::runCommandLine( int argc, const char *argv[] ) const |
| 426 | { |
| 427 | typedef std::deque<std::string> TestNames; |
| 428 | Runner subrunner; |
| 429 | for ( int index = 1; index < argc; ++index ) |
| 430 | { |
| 431 | std::string opt = argv[index]; |
| 432 | if ( opt == "--list-tests" ) |
| 433 | { |
| 434 | listTests(); |
| 435 | return 0; |
| 436 | } |
| 437 | else if ( opt == "--test-auto" ) |
| 438 | { |
| 439 | preventDialogOnCrash(); |
| 440 | } |
| 441 | else if ( opt == "--test" ) |
| 442 | { |
| 443 | ++index; |
| 444 | if ( index < argc ) |
| 445 | { |
| 446 | unsigned int testNameIndex; |
| 447 | if ( testIndex( argv[index], testNameIndex ) ) |
| 448 | { |
| 449 | subrunner.add( tests_[testNameIndex] ); |
| 450 | } |
| 451 | else |
| 452 | { |
| 453 | fprintf( stderr, "Test '%s' does not exist!\n", argv[index] ); |
| 454 | return 2; |
| 455 | } |
| 456 | } |
| 457 | else |
| 458 | { |
| 459 | printUsage( argv[0] ); |
| 460 | return 2; |
| 461 | } |
| 462 | } |
| 463 | else |
| 464 | { |
| 465 | printUsage( argv[0] ); |
| 466 | return 2; |
| 467 | } |
| 468 | } |
| 469 | bool succeeded; |
| 470 | if ( subrunner.testCount() > 0 ) |
| 471 | { |
| 472 | succeeded = subrunner.runAllTest( subrunner.testCount() > 1 ); |
| 473 | } |
| 474 | else |
| 475 | { |
| 476 | succeeded = runAllTest( true ); |
| 477 | } |
| 478 | return succeeded ? 0 |
| 479 | : 1; |
| 480 | } |
| 481 | |
| 482 | |
| 483 | #if defined(_MSC_VER) |
| 484 | // Hook MSVCRT assertions to prevent dialog from appearing |
| 485 | static int |
| 486 | msvcrtSilentReportHook( int reportType, char *message, int *returnValue ) |
| 487 | { |
| 488 | // The default CRT handling of error and assertion is to display |
| 489 | // an error dialog to the user. |
| 490 | // Instead, when an error or an assertion occurs, we force the |
| 491 | // application to terminate using abort() after display |
| 492 | // the message on stderr. |
| 493 | if ( reportType == _CRT_ERROR || |
| 494 | reportType == _CRT_ASSERT ) |
| 495 | { |
| 496 | // calling abort() cause the ReportHook to be called |
| 497 | // The following is used to detect this case and let's the |
| 498 | // error handler fallback on its default behaviour ( |
| 499 | // display a warning message) |
| 500 | static volatile bool isAborting = false; |
| 501 | if ( isAborting ) |
| 502 | { |
| 503 | return TRUE; |
| 504 | } |
| 505 | isAborting = true; |
| 506 | |
| 507 | fprintf( stderr, "CRT Error/Assert:\n%s\n", message ); |
| 508 | fflush( stderr ); |
| 509 | abort(); |
| 510 | } |
| 511 | // Let's other reportType (_CRT_WARNING) be handled as they would by default |
| 512 | return FALSE; |
| 513 | } |
| 514 | #endif // if defined(_MSC_VER) |
| 515 | |
| 516 | |
| 517 | void |
| 518 | Runner::preventDialogOnCrash() |
| 519 | { |
| 520 | #if defined(_MSC_VER) |
| 521 | // Install a hook to prevent MSVCRT error and assertion from |
| 522 | // popping a dialog. |
| 523 | _CrtSetReportHook( &msvcrtSilentReportHook ); |
| 524 | #endif // if defined(_MSC_VER) |
| 525 | |
| 526 | // @todo investiguate this handler (for buffer overflow) |
| 527 | // _set_security_error_handler |
| 528 | |
| 529 | #if defined(_WIN32) |
| 530 | // Prevents the system from popping a dialog for debugging if the |
| 531 | // application fails due to invalid memory access. |
| 532 | SetErrorMode( SEM_FAILCRITICALERRORS |
| 533 | | SEM_NOGPFAULTERRORBOX |
| 534 | | SEM_NOOPENFILEERRORBOX ); |
| 535 | #endif // if defined(_WIN32) |
| 536 | } |
| 537 | |
| 538 | void |
| 539 | Runner::printUsage( const char *appName ) |
| 540 | { |
| 541 | printf( |
| 542 | "Usage: %s [options]\n" |
| 543 | "\n" |
| 544 | "If --test is not specified, then all the test cases be run.\n" |
| 545 | "\n" |
| 546 | "Valid options:\n" |
| 547 | "--list-tests: print the name of all test cases on the standard\n" |
| 548 | " output and exit.\n" |
| 549 | "--test TESTNAME: executes the test case with the specified name.\n" |
| 550 | " May be repeated.\n" |
| 551 | "--test-auto: prevent dialog prompting for debugging on crash.\n" |
| 552 | , appName ); |
| 553 | } |
| 554 | |
| 555 | |
| 556 | |
| 557 | // Assertion functions |
| 558 | // ////////////////////////////////////////////////////////////////// |
| 559 | |
| 560 | TestResult & |
| 561 | checkStringEqual( TestResult &result, |
| 562 | const std::string &expected, const std::string &actual, |
| 563 | const char *file, unsigned int line, const char *expr ) |
| 564 | { |
| 565 | if ( expected != actual ) |
| 566 | { |
| 567 | result.addFailure( file, line, expr ); |
| 568 | result << "Expected: '" << expected << "'\n"; |
| 569 | result << "Actual : '" << actual << "'"; |
| 570 | } |
| 571 | return result; |
| 572 | } |
| 573 | |
| 574 | |
| 575 | } // namespace JsonTest |