summaryrefslogtreecommitdiffstats
path: root/vcl/qa/cppunit/timer.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--vcl/qa/cppunit/timer.cxx566
1 files changed, 566 insertions, 0 deletions
diff --git a/vcl/qa/cppunit/timer.cxx b/vcl/qa/cppunit/timer.cxx
new file mode 100644
index 000000000..4071cd0fd
--- /dev/null
+++ b/vcl/qa/cppunit/timer.cxx
@@ -0,0 +1,566 @@
+/* -*- 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 <test/bootstrapfixture.hxx>
+
+#include <osl/thread.hxx>
+#include <chrono>
+
+#include <vcl/timer.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/scheduler.hxx>
+#include <svdata.hxx>
+#include <salinst.hxx>
+
+// #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_uLong nMS, bool &rBool ) :
+ Timer( "TimerBool" ), mrBool( rBool )
+ {
+ SetTimeout( nMS );
+ Start();
+ mrBool = false;
+ }
+ virtual void Invoke() override
+ {
+ mrBool = true;
+ Application::EndYield();
+ }
+};
+
+}
+
+void TimerTest::testDurations()
+{
+ static const sal_uLong aDurations[] = { 0, 1, 500, 1000 };
+ for (size_t i = 0; i < SAL_N_ELEMENTS( aDurations ); i++)
+ {
+ bool bDone = false;
+ TimerBool aTimer( aDurations[i], 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_uLong 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<double, std::milli>(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<double, std::milli>(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_uLong 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_uLong 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 &mrProcesed;
+public:
+ IdleSerializer(const char *pDebugName, TaskPriority ePrio,
+ sal_uInt32 nPosition, sal_uInt32 &rProcesed)
+ : Idle( pDebugName )
+ , mnPosition( nPosition )
+ , mrProcesed( rProcesed )
+ {
+ SetPriority(ePrio);
+ Start();
+ }
+ virtual void Invoke() override
+ {
+ ++mrProcesed;
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Ignored prio", mnPosition, mrProcesed );
+ }
+};
+
+}
+
+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( 3 == nCount1 && 3 == nCount2 );
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TimerTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */