1 #include "catch_run_context.h" 2 #include "catch_compiler_capabilities.h" 3 #include "catch_context.h" 4 #include "catch_enforce.h" 5 #include "catch_random_number_generator.h" 6 #include "catch_stream.h" 7 #include "catch_output_redirect.h" 8 9 #include <cassert> 10 #include <algorithm> 11 #include <sstream> 12 13 namespace Catch { 14 15 namespace Generators { 16 struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker { 17 GeneratorBasePtr m_generator; 18 GeneratorTrackerCatch::Generators::GeneratorTracker19 GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) 20 : TrackerBase( nameAndLocation, ctx, parent ) 21 {} 22 ~GeneratorTracker(); 23 acquireCatch::Generators::GeneratorTracker24 static GeneratorTracker& acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocation const& nameAndLocation ) { 25 std::shared_ptr<GeneratorTracker> tracker; 26 27 ITracker& currentTracker = ctx.currentTracker(); 28 // Under specific circumstances, the generator we want 29 // to acquire is also the current tracker. If this is 30 // the case, we have to avoid looking through current 31 // tracker's children, and instead return the current 32 // tracker. 33 // A case where this check is important is e.g. 34 // for (int i = 0; i < 5; ++i) { 35 // int n = GENERATE(1, 2); 36 // } 37 // 38 // without it, the code above creates 5 nested generators. 39 if (currentTracker.nameAndLocation() == nameAndLocation) { 40 auto thisTracker = currentTracker.parent().findChild(nameAndLocation); 41 assert(thisTracker); 42 assert(thisTracker->isGeneratorTracker()); 43 tracker = std::static_pointer_cast<GeneratorTracker>(thisTracker); 44 } else if ( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { 45 assert( childTracker ); 46 assert( childTracker->isGeneratorTracker() ); 47 tracker = std::static_pointer_cast<GeneratorTracker>( childTracker ); 48 } else { 49 tracker = std::make_shared<GeneratorTracker>( nameAndLocation, ctx, ¤tTracker ); 50 currentTracker.addChild( tracker ); 51 } 52 53 if( !tracker->isComplete() ) { 54 tracker->open(); 55 } 56 57 return *tracker; 58 } 59 60 // TrackerBase interface isGeneratorTrackerCatch::Generators::GeneratorTracker61 bool isGeneratorTracker() const override { return true; } hasGeneratorCatch::Generators::GeneratorTracker62 auto hasGenerator() const -> bool override { 63 return !!m_generator; 64 } closeCatch::Generators::GeneratorTracker65 void close() override { 66 TrackerBase::close(); 67 // If a generator has a child (it is followed by a section) 68 // and none of its children have started, then we must wait 69 // until later to start consuming its values. 70 // This catches cases where `GENERATE` is placed between two 71 // `SECTION`s. 72 // **The check for m_children.empty cannot be removed**. 73 // doing so would break `GENERATE` _not_ followed by `SECTION`s. 74 const bool should_wait_for_child = [&]() { 75 // No children -> nobody to wait for 76 if ( m_children.empty() ) { 77 return false; 78 } 79 // If at least one child started executing, don't wait 80 if ( std::find_if( 81 m_children.begin(), 82 m_children.end(), 83 []( TestCaseTracking::ITrackerPtr tracker ) { 84 return tracker->hasStarted(); 85 } ) != m_children.end() ) { 86 return false; 87 } 88 89 // No children have started. We need to check if they _can_ 90 // start, and thus we should wait for them, or they cannot 91 // start (due to filters), and we shouldn't wait for them 92 auto* parent = m_parent; 93 // This is safe: there is always at least one section 94 // tracker in a test case tracking tree 95 while ( !parent->isSectionTracker() ) { 96 parent = &( parent->parent() ); 97 } 98 assert( parent && 99 "Missing root (test case) level section" ); 100 101 auto const& parentSection = 102 static_cast<SectionTracker&>( *parent ); 103 auto const& filters = parentSection.getFilters(); 104 // No filters -> no restrictions on running sections 105 if ( filters.empty() ) { 106 return true; 107 } 108 109 for ( auto const& child : m_children ) { 110 if ( child->isSectionTracker() && 111 std::find( filters.begin(), 112 filters.end(), 113 static_cast<SectionTracker&>( *child ) 114 .trimmedName() ) != 115 filters.end() ) { 116 return true; 117 } 118 } 119 return false; 120 }(); 121 122 // This check is a bit tricky, because m_generator->next() 123 // has a side-effect, where it consumes generator's current 124 // value, but we do not want to invoke the side-effect if 125 // this generator is still waiting for any child to start. 126 if ( should_wait_for_child || 127 ( m_runState == CompletedSuccessfully && 128 m_generator->next() ) ) { 129 m_children.clear(); 130 m_runState = Executing; 131 } 132 } 133 134 // IGeneratorTracker interface getGeneratorCatch::Generators::GeneratorTracker135 auto getGenerator() const -> GeneratorBasePtr const& override { 136 return m_generator; 137 } setGeneratorCatch::Generators::GeneratorTracker138 void setGenerator( GeneratorBasePtr&& generator ) override { 139 m_generator = std::move( generator ); 140 } 141 }; ~GeneratorTracker()142 GeneratorTracker::~GeneratorTracker() {} 143 } 144 RunContext(IConfigPtr const & _config,IStreamingReporterPtr && reporter)145 RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) 146 : m_runInfo(_config->name()), 147 m_context(getCurrentMutableContext()), 148 m_config(_config), 149 m_reporter(std::move(reporter)), 150 m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, 151 m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) 152 { 153 m_context.setRunner(this); 154 m_context.setConfig(m_config); 155 m_context.setResultCapture(this); 156 m_reporter->testRunStarting(m_runInfo); 157 } 158 ~RunContext()159 RunContext::~RunContext() { 160 m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); 161 } 162 testGroupStarting(std::string const & testSpec,std::size_t groupIndex,std::size_t groupsCount)163 void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) { 164 m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount)); 165 } 166 testGroupEnded(std::string const & testSpec,Totals const & totals,std::size_t groupIndex,std::size_t groupsCount)167 void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) { 168 m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting())); 169 } 170 runTest(TestCase const & testCase)171 Totals RunContext::runTest(TestCase const& testCase) { 172 Totals prevTotals = m_totals; 173 174 std::string redirectedCout; 175 std::string redirectedCerr; 176 177 auto const& testInfo = testCase.getTestCaseInfo(); 178 179 m_reporter->testCaseStarting(testInfo); 180 181 m_activeTestCase = &testCase; 182 183 184 ITracker& rootTracker = m_trackerContext.startRun(); 185 assert(rootTracker.isSectionTracker()); 186 static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun()); 187 do { 188 m_trackerContext.startCycle(); 189 m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); 190 runCurrentTest(redirectedCout, redirectedCerr); 191 } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); 192 193 Totals deltaTotals = m_totals.delta(prevTotals); 194 if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { 195 deltaTotals.assertions.failed++; 196 deltaTotals.testCases.passed--; 197 deltaTotals.testCases.failed++; 198 } 199 m_totals.testCases += deltaTotals.testCases; 200 m_reporter->testCaseEnded(TestCaseStats(testInfo, 201 deltaTotals, 202 redirectedCout, 203 redirectedCerr, 204 aborting())); 205 206 m_activeTestCase = nullptr; 207 m_testCaseTracker = nullptr; 208 209 return deltaTotals; 210 } 211 config() const212 IConfigPtr RunContext::config() const { 213 return m_config; 214 } 215 reporter() const216 IStreamingReporter& RunContext::reporter() const { 217 return *m_reporter; 218 } 219 assertionEnded(AssertionResult const & result)220 void RunContext::assertionEnded(AssertionResult const & result) { 221 if (result.getResultType() == ResultWas::Ok) { 222 m_totals.assertions.passed++; 223 m_lastAssertionPassed = true; 224 } else if (!result.isOk()) { 225 m_lastAssertionPassed = false; 226 if( m_activeTestCase->getTestCaseInfo().okToFail() ) 227 m_totals.assertions.failedButOk++; 228 else 229 m_totals.assertions.failed++; 230 } 231 else { 232 m_lastAssertionPassed = true; 233 } 234 235 // We have no use for the return value (whether messages should be cleared), because messages were made scoped 236 // and should be let to clear themselves out. 237 static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); 238 239 if (result.getResultType() != ResultWas::Warning) 240 m_messageScopes.clear(); 241 242 // Reset working state 243 resetAssertionInfo(); 244 m_lastResult = result; 245 } resetAssertionInfo()246 void RunContext::resetAssertionInfo() { 247 m_lastAssertionInfo.macroName = StringRef(); 248 m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; 249 } 250 sectionStarted(SectionInfo const & sectionInfo,Counts & assertions)251 bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) { 252 ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); 253 if (!sectionTracker.isOpen()) 254 return false; 255 m_activeSections.push_back(§ionTracker); 256 257 m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; 258 259 m_reporter->sectionStarting(sectionInfo); 260 261 assertions = m_totals.assertions; 262 263 return true; 264 } acquireGeneratorTracker(StringRef generatorName,SourceLineInfo const & lineInfo)265 auto RunContext::acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { 266 using namespace Generators; 267 GeneratorTracker& tracker = GeneratorTracker::acquire(m_trackerContext, 268 TestCaseTracking::NameAndLocation( static_cast<std::string>(generatorName), lineInfo ) ); 269 m_lastAssertionInfo.lineInfo = lineInfo; 270 return tracker; 271 } 272 testForMissingAssertions(Counts & assertions)273 bool RunContext::testForMissingAssertions(Counts& assertions) { 274 if (assertions.total() != 0) 275 return false; 276 if (!m_config->warnAboutMissingAssertions()) 277 return false; 278 if (m_trackerContext.currentTracker().hasChildren()) 279 return false; 280 m_totals.assertions.failed++; 281 assertions.failed++; 282 return true; 283 } 284 sectionEnded(SectionEndInfo const & endInfo)285 void RunContext::sectionEnded(SectionEndInfo const & endInfo) { 286 Counts assertions = m_totals.assertions - endInfo.prevAssertions; 287 bool missingAssertions = testForMissingAssertions(assertions); 288 289 if (!m_activeSections.empty()) { 290 m_activeSections.back()->close(); 291 m_activeSections.pop_back(); 292 } 293 294 m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); 295 m_messages.clear(); 296 m_messageScopes.clear(); 297 } 298 sectionEndedEarly(SectionEndInfo const & endInfo)299 void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) { 300 if (m_unfinishedSections.empty()) 301 m_activeSections.back()->fail(); 302 else 303 m_activeSections.back()->close(); 304 m_activeSections.pop_back(); 305 306 m_unfinishedSections.push_back(endInfo); 307 } 308 309 #if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) benchmarkPreparing(std::string const & name)310 void RunContext::benchmarkPreparing(std::string const& name) { 311 m_reporter->benchmarkPreparing(name); 312 } benchmarkStarting(BenchmarkInfo const & info)313 void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { 314 m_reporter->benchmarkStarting( info ); 315 } benchmarkEnded(BenchmarkStats<> const & stats)316 void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) { 317 m_reporter->benchmarkEnded( stats ); 318 } benchmarkFailed(std::string const & error)319 void RunContext::benchmarkFailed(std::string const & error) { 320 m_reporter->benchmarkFailed(error); 321 } 322 #endif // CATCH_CONFIG_ENABLE_BENCHMARKING 323 pushScopedMessage(MessageInfo const & message)324 void RunContext::pushScopedMessage(MessageInfo const & message) { 325 m_messages.push_back(message); 326 } 327 popScopedMessage(MessageInfo const & message)328 void RunContext::popScopedMessage(MessageInfo const & message) { 329 m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); 330 } 331 emplaceUnscopedMessage(MessageBuilder const & builder)332 void RunContext::emplaceUnscopedMessage( MessageBuilder const& builder ) { 333 m_messageScopes.emplace_back( builder ); 334 } 335 getCurrentTestName() const336 std::string RunContext::getCurrentTestName() const { 337 return m_activeTestCase 338 ? m_activeTestCase->getTestCaseInfo().name 339 : std::string(); 340 } 341 getLastResult() const342 const AssertionResult * RunContext::getLastResult() const { 343 return &(*m_lastResult); 344 } 345 exceptionEarlyReported()346 void RunContext::exceptionEarlyReported() { 347 m_shouldReportUnexpected = false; 348 } 349 handleFatalErrorCondition(StringRef message)350 void RunContext::handleFatalErrorCondition( StringRef message ) { 351 // First notify reporter that bad things happened 352 m_reporter->fatalErrorEncountered(message); 353 354 // Don't rebuild the result -- the stringification itself can cause more fatal errors 355 // Instead, fake a result data. 356 AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); 357 tempResult.message = static_cast<std::string>(message); 358 AssertionResult result(m_lastAssertionInfo, tempResult); 359 360 assertionEnded(result); 361 362 handleUnfinishedSections(); 363 364 // Recreate section for test case (as we will lose the one that was in scope) 365 auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); 366 SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); 367 368 Counts assertions; 369 assertions.failed = 1; 370 SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false); 371 m_reporter->sectionEnded(testCaseSectionStats); 372 373 auto const& testInfo = m_activeTestCase->getTestCaseInfo(); 374 375 Totals deltaTotals; 376 deltaTotals.testCases.failed = 1; 377 deltaTotals.assertions.failed = 1; 378 m_reporter->testCaseEnded(TestCaseStats(testInfo, 379 deltaTotals, 380 std::string(), 381 std::string(), 382 false)); 383 m_totals.testCases.failed++; 384 testGroupEnded(std::string(), m_totals, 1, 1); 385 m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); 386 } 387 lastAssertionPassed()388 bool RunContext::lastAssertionPassed() { 389 return m_lastAssertionPassed; 390 } 391 assertionPassed()392 void RunContext::assertionPassed() { 393 m_lastAssertionPassed = true; 394 ++m_totals.assertions.passed; 395 resetAssertionInfo(); 396 m_messageScopes.clear(); 397 } 398 aborting() const399 bool RunContext::aborting() const { 400 return m_totals.assertions.failed >= static_cast<std::size_t>(m_config->abortAfter()); 401 } 402 runCurrentTest(std::string & redirectedCout,std::string & redirectedCerr)403 void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { 404 auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); 405 SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); 406 m_reporter->sectionStarting(testCaseSection); 407 Counts prevAssertions = m_totals.assertions; 408 double duration = 0; 409 m_shouldReportUnexpected = true; 410 m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; 411 412 seedRng(*m_config); 413 414 Timer timer; 415 CATCH_TRY { 416 if (m_reporter->getPreferences().shouldRedirectStdOut) { 417 #if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) 418 RedirectedStreams redirectedStreams(redirectedCout, redirectedCerr); 419 420 timer.start(); 421 invokeActiveTestCase(); 422 #else 423 OutputRedirect r(redirectedCout, redirectedCerr); 424 timer.start(); 425 invokeActiveTestCase(); 426 #endif 427 } else { 428 timer.start(); 429 invokeActiveTestCase(); 430 } 431 duration = timer.getElapsedSeconds(); 432 } CATCH_CATCH_ANON (TestFailureException&) { 433 // This just means the test was aborted due to failure 434 } CATCH_CATCH_ALL { 435 // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions 436 // are reported without translation at the point of origin. 437 if( m_shouldReportUnexpected ) { 438 AssertionReaction dummyReaction; 439 handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); 440 } 441 } 442 Counts assertions = m_totals.assertions - prevAssertions; 443 bool missingAssertions = testForMissingAssertions(assertions); 444 445 m_testCaseTracker->close(); 446 handleUnfinishedSections(); 447 m_messages.clear(); 448 m_messageScopes.clear(); 449 450 SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); 451 m_reporter->sectionEnded(testCaseSectionStats); 452 } 453 invokeActiveTestCase()454 void RunContext::invokeActiveTestCase() { 455 FatalConditionHandler fatalConditionHandler; // Handle signals 456 m_activeTestCase->invoke(); 457 fatalConditionHandler.reset(); 458 } 459 handleUnfinishedSections()460 void RunContext::handleUnfinishedSections() { 461 // If sections ended prematurely due to an exception we stored their 462 // infos here so we can tear them down outside the unwind process. 463 for (auto it = m_unfinishedSections.rbegin(), 464 itEnd = m_unfinishedSections.rend(); 465 it != itEnd; 466 ++it) 467 sectionEnded(*it); 468 m_unfinishedSections.clear(); 469 } 470 handleExpr(AssertionInfo const & info,ITransientExpression const & expr,AssertionReaction & reaction)471 void RunContext::handleExpr( 472 AssertionInfo const& info, 473 ITransientExpression const& expr, 474 AssertionReaction& reaction 475 ) { 476 m_reporter->assertionStarting( info ); 477 478 bool negated = isFalseTest( info.resultDisposition ); 479 bool result = expr.getResult() != negated; 480 481 if( result ) { 482 if (!m_includeSuccessfulResults) { 483 assertionPassed(); 484 } 485 else { 486 reportExpr(info, ResultWas::Ok, &expr, negated); 487 } 488 } 489 else { 490 reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); 491 populateReaction( reaction ); 492 } 493 } reportExpr(AssertionInfo const & info,ResultWas::OfType resultType,ITransientExpression const * expr,bool negated)494 void RunContext::reportExpr( 495 AssertionInfo const &info, 496 ResultWas::OfType resultType, 497 ITransientExpression const *expr, 498 bool negated ) { 499 500 m_lastAssertionInfo = info; 501 AssertionResultData data( resultType, LazyExpression( negated ) ); 502 503 AssertionResult assertionResult{ info, data }; 504 assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; 505 506 assertionEnded( assertionResult ); 507 } 508 handleMessage(AssertionInfo const & info,ResultWas::OfType resultType,StringRef const & message,AssertionReaction & reaction)509 void RunContext::handleMessage( 510 AssertionInfo const& info, 511 ResultWas::OfType resultType, 512 StringRef const& message, 513 AssertionReaction& reaction 514 ) { 515 m_reporter->assertionStarting( info ); 516 517 m_lastAssertionInfo = info; 518 519 AssertionResultData data( resultType, LazyExpression( false ) ); 520 data.message = static_cast<std::string>(message); 521 AssertionResult assertionResult{ m_lastAssertionInfo, data }; 522 assertionEnded( assertionResult ); 523 if( !assertionResult.isOk() ) 524 populateReaction( reaction ); 525 } handleUnexpectedExceptionNotThrown(AssertionInfo const & info,AssertionReaction & reaction)526 void RunContext::handleUnexpectedExceptionNotThrown( 527 AssertionInfo const& info, 528 AssertionReaction& reaction 529 ) { 530 handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); 531 } 532 handleUnexpectedInflightException(AssertionInfo const & info,std::string const & message,AssertionReaction & reaction)533 void RunContext::handleUnexpectedInflightException( 534 AssertionInfo const& info, 535 std::string const& message, 536 AssertionReaction& reaction 537 ) { 538 m_lastAssertionInfo = info; 539 540 AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); 541 data.message = message; 542 AssertionResult assertionResult{ info, data }; 543 assertionEnded( assertionResult ); 544 populateReaction( reaction ); 545 } 546 populateReaction(AssertionReaction & reaction)547 void RunContext::populateReaction( AssertionReaction& reaction ) { 548 reaction.shouldDebugBreak = m_config->shouldDebugBreak(); 549 reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); 550 } 551 handleIncomplete(AssertionInfo const & info)552 void RunContext::handleIncomplete( 553 AssertionInfo const& info 554 ) { 555 m_lastAssertionInfo = info; 556 557 AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); 558 data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; 559 AssertionResult assertionResult{ info, data }; 560 assertionEnded( assertionResult ); 561 } handleNonExpr(AssertionInfo const & info,ResultWas::OfType resultType,AssertionReaction & reaction)562 void RunContext::handleNonExpr( 563 AssertionInfo const &info, 564 ResultWas::OfType resultType, 565 AssertionReaction &reaction 566 ) { 567 m_lastAssertionInfo = info; 568 569 AssertionResultData data( resultType, LazyExpression( false ) ); 570 AssertionResult assertionResult{ info, data }; 571 assertionEnded( assertionResult ); 572 573 if( !assertionResult.isOk() ) 574 populateReaction( reaction ); 575 } 576 577 getResultCapture()578 IResultCapture& getResultCapture() { 579 if (auto* capture = getCurrentContext().getResultCapture()) 580 return *capture; 581 else 582 CATCH_INTERNAL_ERROR("No result capture instance"); 583 } 584 seedRng(IConfig const & config)585 void seedRng(IConfig const& config) { 586 if (config.rngSeed() != 0) { 587 std::srand(config.rngSeed()); 588 rng().seed(config.rngSeed()); 589 } 590 } 591 rngSeed()592 unsigned int rngSeed() { 593 return getCurrentContext().getConfig()->rngSeed(); 594 } 595 596 } 597