/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * Timers are evil beasts across platforms... */ #include #include #include #include #include #include #include #include #include // #define TEST_WATCHDOG // Enables timer tests that appear to provoke windows under load unduly. //#define TEST_TIMERPRECISION namespace { /// Avoid our timer tests just wedging the build if they fail. class WatchDog : public osl::Thread { sal_Int32 mnSeconds; public: explicit WatchDog(sal_Int32 nSeconds) : Thread(), mnSeconds( nSeconds ) { create(); } virtual void SAL_CALL run() override { osl::Thread::wait( std::chrono::seconds(mnSeconds) ); fprintf(stderr, "ERROR: WatchDog timer thread expired, failing the test!\n"); fflush(stderr); CPPUNIT_ASSERT_MESSAGE("watchdog triggered", false); } }; } static WatchDog * aWatchDog = new WatchDog( 120 ); // random high number in secs class TimerTest : public test::BootstrapFixture { public: TimerTest() : BootstrapFixture(true, false) {} void testIdle(); void testIdleMainloop(); #ifdef TEST_WATCHDOG void testWatchdog(); #endif void testDurations(); #ifdef TEST_TIMERPRECISION void testAutoTimer(); void testMultiAutoTimers(); #endif void testAutoTimerStop(); void testNestedTimer(); void testSlowTimerCallback(); void testTriggerIdleFromIdle(); void testInvokedReStart(); void testPriority(); void testRoundRobin(); CPPUNIT_TEST_SUITE(TimerTest); CPPUNIT_TEST(testIdle); CPPUNIT_TEST(testIdleMainloop); #ifdef TEST_WATCHDOG CPPUNIT_TEST(testWatchdog); #endif CPPUNIT_TEST(testDurations); #ifdef TEST_TIMERPRECISION CPPUNIT_TEST(testAutoTimer); CPPUNIT_TEST(testMultiAutoTimers); #endif CPPUNIT_TEST(testAutoTimerStop); CPPUNIT_TEST(testNestedTimer); CPPUNIT_TEST(testSlowTimerCallback); CPPUNIT_TEST(testTriggerIdleFromIdle); CPPUNIT_TEST(testInvokedReStart); CPPUNIT_TEST(testPriority); CPPUNIT_TEST(testRoundRobin); CPPUNIT_TEST_SUITE_END(); }; #ifdef TEST_WATCHDOG void TimerTest::testWatchdog() { // out-wait the watchdog. osl::Thread::wait( std::chrono::seconds(12) ); } #endif namespace { class IdleBool : public Idle { bool &mrBool; public: explicit IdleBool( bool &rBool ) : Idle( "IdleBool" ), mrBool( rBool ) { SetPriority( TaskPriority::LOWEST ); Start(); mrBool = false; } virtual void Invoke() override { mrBool = true; Application::EndYield(); } }; } void TimerTest::testIdle() { bool bTriggered = false; IdleBool aTest( bTriggered ); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_MESSAGE("idle triggered", bTriggered); } void TimerTest::testIdleMainloop() { bool bTriggered = false; IdleBool aTest( bTriggered ); // coverity[loop_top] - Application::Yield allows the timer to fire and toggle bDone while (!bTriggered) { ImplSVData* pSVData = ImplGetSVData(); // can't test this via Application::Yield since this // also processes all tasks directly via the scheduler. pSVData->maAppData.mnDispatchLevel++; pSVData->mpDefInst->DoYield(true, false); pSVData->maAppData.mnDispatchLevel--; } CPPUNIT_ASSERT_MESSAGE("mainloop idle triggered", bTriggered); } namespace { class TimerBool : public Timer { bool &mrBool; public: TimerBool( sal_uInt64 nMS, bool &rBool ) : Timer( "TimerBool" ), mrBool( rBool ) { SetTimeout( nMS ); Start(); mrBool = false; } virtual void Invoke() override { mrBool = true; Application::EndYield(); } }; } void TimerTest::testDurations() { for (auto const nDuration : { 0, 1, 500, 1000 }) { bool bDone = false; TimerBool aTimer( nDuration, bDone ); // coverity[loop_top] - Application::Yield allows the timer to fire and toggle bDone while( !bDone ) { Application::Yield(); } } } namespace { class AutoTimerCount : public AutoTimer { sal_Int32 &mrCount; const sal_Int32 mnMaxCount; public: AutoTimerCount( sal_uInt64 nMS, sal_Int32 &rCount, const sal_Int32 nMaxCount = -1 ) : AutoTimer( "AutoTimerCount" ) , mrCount( rCount ) , mnMaxCount( nMaxCount ) { SetTimeout( nMS ); Start(); mrCount = 0; } virtual void Invoke() override { ++mrCount; CPPUNIT_ASSERT( mnMaxCount < 0 || mrCount <= mnMaxCount ); if ( mrCount == mnMaxCount ) Stop(); } }; } #ifdef TEST_TIMERPRECISION void TimerTest::testAutoTimer() { const sal_Int32 nDurationMs = 30; const sal_Int32 nEventsCount = 5; const double exp = (nDurationMs * nEventsCount); sal_Int32 nCount = 0; std::ostringstream msg; // Repeat when we have random latencies. // This is expected on non-realtime OSes. for (int i = 0; i < 10; ++i) { const auto start = std::chrono::high_resolution_clock::now(); nCount = 0; AutoTimerCount aCount(nDurationMs, nCount); while (nCount < nEventsCount) { Application::Yield(); } const auto end = std::chrono::high_resolution_clock::now(); double dur = std::chrono::duration(end - start).count(); msg << std::setprecision(2) << std::fixed << "periodic multi-timer - dur: " << dur << " (" << exp << ") ms." << std::endl; // +/- 20% should be reasonable enough a margin. if (dur >= (exp * 0.8) && dur <= (exp * 1.2)) { // Success. return; } } CPPUNIT_FAIL(msg.str().c_str()); } void TimerTest::testMultiAutoTimers() { // The behavior of the timers change drastically // when multiple timers are present. // The worst, in my tests, is when two // timers with 1ms period exist with a // third of much longer period. const sal_Int32 nDurationMsX = 5; const sal_Int32 nDurationMsY = 10; const sal_Int32 nDurationMs = 40; const sal_Int32 nEventsCount = 5; const double exp = (nDurationMs * nEventsCount); const double expX = (exp / nDurationMsX); const double expY = (exp / nDurationMsY); sal_Int32 nCountX = 0; sal_Int32 nCountY = 0; sal_Int32 nCount = 0; std::ostringstream msg; // Repeat when we have random latencies. // This is expected on non-realtime OSes. for (int i = 0; i < 10; ++i) { nCountX = 0; nCountY = 0; nCount = 0; const auto start = std::chrono::high_resolution_clock::now(); AutoTimerCount aCountX(nDurationMsX, nCountX); AutoTimerCount aCountY(nDurationMsY, nCountY); AutoTimerCount aCount(nDurationMs, nCount); // coverity[loop_top] - Application::Yield allows the timer to fire and toggle nCount while (nCount < nEventsCount) { Application::Yield(); } const auto end = std::chrono::high_resolution_clock::now(); double dur = std::chrono::duration(end - start).count(); msg << std::setprecision(2) << std::fixed << "periodic multi-timer - dur: " << dur << " (" << exp << ") ms, nCount: " << nCount << " (" << nEventsCount << "), nCountX: " << nCountX << " (" << expX << "), nCountY: " << nCountY << " (" << expY << ")." << std::endl; // +/- 20% should be reasonable enough a margin. if (dur >= (exp * 0.8) && dur <= (exp * 1.2) && nCountX >= (expX * 0.8) && nCountX <= (expX * 1.2) && nCountY >= (expY * 0.8) && nCountY <= (expY * 1.2)) { // Success. return; } } CPPUNIT_FAIL(msg.str().c_str()); } #endif // TEST_TIMERPRECISION void TimerTest::testAutoTimerStop() { sal_Int32 nTimerCount = 0; const sal_Int32 nMaxCount = 5; AutoTimerCount aAutoTimer( 0, nTimerCount, nMaxCount ); // coverity[loop_top] - Application::Yield allows the timer to fire and increment TimerCount while (nMaxCount != nTimerCount) Application::Yield(); CPPUNIT_ASSERT( !aAutoTimer.IsActive() ); CPPUNIT_ASSERT( !Application::Reschedule() ); } namespace { class YieldTimer : public Timer { public: explicit YieldTimer( sal_uInt64 nMS ) : Timer( "YieldTimer" ) { SetTimeout( nMS ); Start(); } virtual void Invoke() override { for (int i = 0; i < 100; i++) Application::Yield(); } }; } void TimerTest::testNestedTimer() { sal_Int32 nCount = 0; YieldTimer aCount(5); AutoTimerCount aCountUp( 3, nCount ); // coverity[loop_top] - Application::Yield allows the timer to fire and increment nCount while (nCount < 20) Application::Yield(); } namespace { class SlowCallbackTimer : public Timer { bool &mbSlow; public: SlowCallbackTimer( sal_uInt64 nMS, bool &bBeenSlow ) : Timer( "SlowCallbackTimer" ), mbSlow( bBeenSlow ) { SetTimeout( nMS ); Start(); mbSlow = false; } virtual void Invoke() override { osl::Thread::wait( std::chrono::seconds(1) ); mbSlow = true; } }; } void TimerTest::testSlowTimerCallback() { bool bBeenSlow = false; sal_Int32 nCount = 0; AutoTimerCount aHighFreq(1, nCount); SlowCallbackTimer aSlow(250, bBeenSlow); // coverity[loop_top] - Application::Yield allows the timer to fire and toggle bBeenSlow while (!bBeenSlow) Application::Yield(); // coverity[loop_top] - Application::Yield allows the timer to fire and increment nCount while (nCount < 200) Application::Yield(); } namespace { class TriggerIdleFromIdle : public Idle { bool* mpTriggered; TriggerIdleFromIdle* mpOther; public: explicit TriggerIdleFromIdle( bool* pTriggered, TriggerIdleFromIdle* pOther ) : Idle( "TriggerIdleFromIdle" ), mpTriggered(pTriggered), mpOther(pOther) { } virtual void Invoke() override { Start(); if (mpOther) mpOther->Start(); Application::Yield(); if (mpTriggered) *mpTriggered = true; } }; } void TimerTest::testTriggerIdleFromIdle() { bool bTriggered1 = false; bool bTriggered2 = false; TriggerIdleFromIdle aTest2( &bTriggered2, nullptr ); TriggerIdleFromIdle aTest1( &bTriggered1, &aTest2 ); aTest1.Start(); Application::Yield(); CPPUNIT_ASSERT_MESSAGE("idle not triggered", bTriggered1); CPPUNIT_ASSERT_MESSAGE("idle not triggered", bTriggered2); } namespace { class IdleInvokedReStart : public Idle { sal_Int32 &mrCount; public: IdleInvokedReStart( sal_Int32 &rCount ) : Idle( "IdleInvokedReStart" ), mrCount( rCount ) { Start(); } virtual void Invoke() override { mrCount++; if ( mrCount < 2 ) Start(); } }; } void TimerTest::testInvokedReStart() { sal_Int32 nCount = 0; IdleInvokedReStart aIdle( nCount ); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL( sal_Int32(2), nCount ); } namespace { class IdleSerializer : public Idle { sal_uInt32 mnPosition; sal_uInt32 &mrProcessed; public: IdleSerializer(const char *pDebugName, TaskPriority ePrio, sal_uInt32 nPosition, sal_uInt32 &rProcessed) : Idle( pDebugName ) , mnPosition( nPosition ) , mrProcessed( rProcessed ) { SetPriority(ePrio); Start(); } virtual void Invoke() override { ++mrProcessed; CPPUNIT_ASSERT_EQUAL_MESSAGE( "Ignored prio", mnPosition, mrProcessed ); } }; } void TimerTest::testPriority() { // scope, so tasks are deleted { // Start: 1st Idle low, 2nd high sal_uInt32 nProcessed = 0; IdleSerializer aLowPrioIdle("IdleSerializer LowPrio", TaskPriority::LOWEST, 2, nProcessed); IdleSerializer aHighPrioIdle("IdleSerializer HighPrio", TaskPriority::HIGHEST, 1, nProcessed); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL_MESSAGE( "Not all idles processed", sal_uInt32(2), nProcessed ); } { // Start: 1st Idle high, 2nd low sal_uInt32 nProcessed = 0; IdleSerializer aHighPrioIdle("IdleSerializer HighPrio", TaskPriority::HIGHEST, 1, nProcessed); IdleSerializer aLowPrioIdle("IdleSerializer LowPrio", TaskPriority::LOWEST, 2, nProcessed); Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT_EQUAL_MESSAGE( "Not all idles processed", sal_uInt32(2), nProcessed ); } } namespace { class TestAutoIdleRR : public AutoIdle { sal_uInt32 &mrCount; DECL_LINK( IdleRRHdl, Timer *, void ); public: TestAutoIdleRR( sal_uInt32 &rCount, const char *pDebugName ) : AutoIdle( pDebugName ) , mrCount( rCount ) { CPPUNIT_ASSERT_EQUAL( sal_uInt32(0), mrCount ); SetInvokeHandler( LINK( this, TestAutoIdleRR, IdleRRHdl ) ); Start(); } }; } IMPL_LINK_NOARG(TestAutoIdleRR, IdleRRHdl, Timer *, void) { ++mrCount; if ( mrCount == 3 ) Stop(); } void TimerTest::testRoundRobin() { sal_uInt32 nCount1 = 0, nCount2 = 0; TestAutoIdleRR aIdle1( nCount1, "TestAutoIdleRR aIdle1" ), aIdle2( nCount2, "TestAutoIdleRR aIdle2" ); while ( Application::Reschedule() ) { CPPUNIT_ASSERT( nCount1 == nCount2 || nCount1 - 1 == nCount2 ); CPPUNIT_ASSERT( nCount1 <= 3 ); CPPUNIT_ASSERT( nCount2 <= 3 ); } CPPUNIT_ASSERT_EQUAL( sal_uInt32(3), nCount1 ); CPPUNIT_ASSERT_EQUAL( sal_uInt32(3), nCount2 ); } CPPUNIT_TEST_SUITE_REGISTRATION(TimerTest); CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */