File indexing completed on 2024-05-12 17:15:21
0001 /* 0002 Copyright (C) 2013 Andreas Hartmetz <ahartmetz@gmail.com> 0003 0004 This library is free software; you can redistribute it and/or 0005 modify it under the terms of the GNU Library General Public 0006 License as published by the Free Software Foundation; either 0007 version 2 of the License, or (at your option) any later version. 0008 0009 This library is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 Library General Public License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this library; see the file COPYING.LGPL. If not, write to 0016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0017 Boston, MA 02110-1301, USA. 0018 0019 Alternatively, this file is available under the Mozilla Public License 0020 Version 1.1. You may obtain a copy of the License at 0021 http://www.mozilla.org/MPL/ 0022 */ 0023 0024 #include "eventdispatcher.h" 0025 #include "icompletionlistener.h" 0026 #include "platformtime.h" 0027 #include "timer.h" 0028 0029 #include "../testutil.h" 0030 0031 #include <cstring> 0032 #include <iostream> 0033 0034 class BamPrinter : public ICompletionListener 0035 { 0036 public: 0037 BamPrinter(const char *customMessage, uint64 startTime) 0038 : m_customMessage(customMessage), m_startTime(startTime) {} 0039 void handleCompletion(void *task) override 0040 { 0041 uint64 timeDiff = PlatformTime::monotonicMsecs() - m_startTime; 0042 std::cout << "BAM " << task << ' ' << timeDiff << ' ' << m_customMessage << " #" << m_counter++ << '\n'; 0043 } 0044 const char *m_customMessage; 0045 uint64 m_startTime; 0046 int m_counter = 0; 0047 }; 0048 0049 // supposed to print some output to prove timers are working, and not crash :) 0050 static void testBasic() 0051 { 0052 EventDispatcher dispatcher; 0053 uint64 baseTime = PlatformTime::monotonicMsecs(); 0054 0055 const char *customMessage1 = "Hello, world 1!"; 0056 BamPrinter printer1(customMessage1, baseTime); 0057 0058 Timer t(&dispatcher); 0059 t.setCompletionListener(&printer1); 0060 t.setInterval(231); 0061 t.setRunning(true); 0062 0063 const char *customMessage2 = "Hello, world 2!"; 0064 BamPrinter printer2(customMessage2, baseTime); 0065 0066 Timer t2(&dispatcher); 0067 t2.setCompletionListener(&printer2); 0068 t2.setInterval(100); 0069 t2.setRunning(true); 0070 0071 0072 const char *customMessage3 = "Hello, other world!"; 0073 int booCounter = 0; 0074 CompletionFunc booPrinter([baseTime, customMessage3, &booCounter, &dispatcher, &t] (void *task) 0075 { 0076 uint64 timeDiff = PlatformTime::monotonicMsecs() - baseTime; 0077 std::cout << "boo " << task << ' ' << timeDiff << ' ' << customMessage3 << " #" << booCounter 0078 << " - Timer 1 remaining time: " << t.remainingTime() << '\n'; 0079 if (booCounter >= 4) { 0080 dispatcher.interrupt(); 0081 } 0082 booCounter++; 0083 }); 0084 0085 Timer t3(&dispatcher); 0086 t3.setCompletionListener(&booPrinter); 0087 t3.setInterval(420); 0088 t3.setRunning(true); 0089 0090 while (dispatcher.poll()) { 0091 } 0092 } 0093 0094 class AccuracyTester : public ICompletionListener 0095 { 0096 public: 0097 AccuracyTester() 0098 : m_lastTriggerTime(PlatformTime::monotonicMsecs()) 0099 {} 0100 void handleCompletion(void *task) override 0101 { 0102 Timer *timer = reinterpret_cast<Timer *>(task); 0103 uint64 currentTime = PlatformTime::monotonicMsecs(); 0104 int timeDiff = int64(currentTime) - int64(m_lastTriggerTime); 0105 m_lastTriggerTime = currentTime; 0106 0107 std::cout << timer->interval() << ' ' << timeDiff << std::endl; 0108 // ### This test is somewhat unreliable. It is supposed to catch wildly wrong timer trigger times, 0109 // but it can fail randomly due to high system load or simply Windows scheduling timeouts with only 0110 // about 15 ms resolution (for energy efficiency). 0111 #ifdef _WIN32 0112 TEST(std::abs(timeDiff - timer->interval()) < 35); // this seems to prevent most random failures :> 0113 #else 0114 TEST(std::abs(timeDiff - timer->interval()) < 5); 0115 #endif 0116 m_count++; 0117 TEST(m_count < 26); // event loop should have stopped right at 25 0118 0119 if (m_count == 25) { 0120 timer->eventDispatcher()->interrupt(); 0121 } 0122 } 0123 uint64 m_lastTriggerTime; 0124 uint m_count = 0; 0125 }; 0126 0127 static void testAccuracy() 0128 { 0129 // this test is likely to fail spuriously on a machine under load 0130 EventDispatcher dispatcher; 0131 0132 AccuracyTester at1; 0133 Timer t1(&dispatcher); 0134 t1.setCompletionListener(&at1); 0135 t1.setInterval(225); 0136 t1.setRunning(true); 0137 0138 AccuracyTester at2; 0139 Timer t2(&dispatcher); 0140 t2.setCompletionListener(&at2); 0141 t2.setInterval(42); 0142 t2.setRunning(true); 0143 0144 while (dispatcher.poll()) { 0145 } 0146 } 0147 0148 // this not only bounds how long the dispatcher runs, it also creates another timer to make the 0149 // situation more interesting 0150 class EventDispatcherInterruptor : public ICompletionListener 0151 { 0152 public: 0153 EventDispatcherInterruptor(EventDispatcher *ed, int timeout) 0154 : m_ttl(ed) 0155 { 0156 m_ttl.setInterval(timeout); 0157 m_ttl.setCompletionListener(this); 0158 m_ttl.setRunning(true); 0159 } 0160 void handleCompletion(void * /*task*/) override 0161 { 0162 m_ttl.eventDispatcher()->interrupt(); 0163 m_ttl.setRunning(false); 0164 } 0165 Timer m_ttl; 0166 }; 0167 0168 static void testDeleteOrDisableInTrigger(bool deleteTimer) 0169 { 0170 EventDispatcher dispatcher; 0171 0172 bool alreadyCalled = false; 0173 CompletionFunc deleter([&alreadyCalled, deleteTimer] (void *task) 0174 { 0175 TEST(!alreadyCalled); 0176 alreadyCalled = true; 0177 Timer *timer = reinterpret_cast<Timer *>(task); 0178 if (deleteTimer) { 0179 delete timer; 0180 } else { 0181 timer->setRunning(false); 0182 } 0183 }); 0184 0185 Timer *t1 = new Timer(&dispatcher); 0186 t1->setCompletionListener(&deleter); 0187 t1->setRunning(true); 0188 0189 EventDispatcherInterruptor interruptor(&dispatcher, 50); 0190 0191 while (dispatcher.poll()) { 0192 } 0193 0194 if (!deleteTimer) { 0195 delete t1; 0196 } 0197 } 0198 0199 static void testDeleteInTrigger() 0200 { 0201 testDeleteOrDisableInTrigger(true); 0202 } 0203 0204 static void testDisableInTrigger() 0205 { 0206 testDeleteOrDisableInTrigger(false); 0207 } 0208 0209 static void testAddInTrigger() 0210 { 0211 // A timer added from the callback of another timer should not trigger in the same event loop 0212 // iteration, otherwise there could be an (accidental or intended) infinite cascade of zero interval 0213 // timers adding zero interval timers 0214 0215 // since this test has a (small) false negative (note: negative == no problem found) rate - if 0216 // the current millisecond changes at certain points, it can mask a problem - just run it a couple 0217 // of times... 0218 for (int i = 0; i < 5; i++) { 0219 EventDispatcher dispatcher; 0220 int dispatchCounter = 0; 0221 int t2Counter = 0; 0222 0223 CompletionFunc iterChecker([&dispatchCounter, &t2Counter] (void * /*task*/) 0224 { 0225 TEST(dispatchCounter > 0); 0226 t2Counter++; 0227 }); 0228 0229 Timer t1(&dispatcher); 0230 Timer *t2 = nullptr; 0231 CompletionFunc adder([&dispatcher, &t2, &iterChecker] (void * /*task*/) 0232 { 0233 if (!t2) { 0234 t2 = new Timer(&dispatcher); 0235 t2->setCompletionListener(&iterChecker); 0236 t2->setRunning(true); 0237 // this could go wrong because we manipulate the due time in EventDispatcher::addTimer(), 0238 // but should be caught in Timer::remainingTime() 0239 TEST(t2->remainingTime() == 0); 0240 } 0241 }); 0242 0243 t1.setInterval(10); 0244 t1.setRunning(true); 0245 t1.setCompletionListener(&adder); 0246 0247 EventDispatcherInterruptor interruptor(&dispatcher, 50); 0248 0249 while (dispatcher.poll()) { 0250 dispatchCounter++; 0251 } 0252 TEST(t2Counter > 1); 0253 delete t2; 0254 } 0255 } 0256 0257 static void testReAddInTrigger() 0258 { 0259 // - Add a timer 0260 // - Remove it 0261 // - Remove it, then add it 0262 // - Remove, add, remove 0263 // - Remove, add, remove, add 0264 // - Check timer's isRunning() considering whether last action was add or remove 0265 // - Check if the timer triggers next time or not, consistent with previous point 0266 0267 0268 // Repeat the tests that include re-adding with "pointer aliased" timers, i.e. add a new timer created 0269 // at the same memory location as the old one. That tests whether a known difficulty of the chosen 0270 // implementation is handled correctly. 0271 0272 // Use the array to ensure we have pointer aliasing or no pointer aliasing 0273 std::aligned_storage<sizeof(Timer)>::type timerStorage[2]; 0274 memset(timerStorage, 0, sizeof(timerStorage)); 0275 0276 Timer *const timerArray = reinterpret_cast<Timer *>(timerStorage); 0277 0278 for (int i = 0; i < 2; i++) { 0279 const bool withAliasing = i == 1; 0280 0281 for (int j = 0; j < 5; j++) { // j = number of add / remove ops 0282 EventDispatcher dispatcher; 0283 0284 Timer *t = &timerArray[0]; 0285 bool removeTimer = false; 0286 bool checkTrigger = false; 0287 bool didTrigger = false; 0288 0289 CompletionFunc addRemove([&] (void * /*task*/) { 0290 if (checkTrigger) { 0291 didTrigger = true; 0292 return; 0293 } 0294 0295 for (int k = 0; k < j; k++) { 0296 removeTimer = (k & 1) == 0; 0297 if (removeTimer) { 0298 TEST(t->isRunning()); 0299 t->~Timer(); 0300 // ensure that it can't trigger - of course if Timer 0301 // relies on that we should find it in valgrind... 0302 memset(static_cast<void *>(t), 0, sizeof(Timer)); 0303 } else { 0304 if (!withAliasing) { 0305 if (t == &timerArray[0]) { 0306 t = &timerArray[1]; 0307 } else { 0308 t = &timerArray[0]; 0309 } 0310 } 0311 new(t) Timer(&dispatcher); 0312 t->setCompletionListener(&addRemove); 0313 t->start(0); 0314 TEST(t->isRunning()); 0315 } 0316 } 0317 }); 0318 0319 0320 Timer dummy1(&dispatcher); 0321 dummy1.start(0); 0322 0323 new(t) Timer(&dispatcher); 0324 t->start(0); 0325 0326 Timer dummy2(&dispatcher); 0327 dummy2.start(0); 0328 0329 dispatcher.poll(); // this seems like a good idea for the test... 0330 0331 // run and test the add / remove sequence 0332 t->setCompletionListener(&addRemove); 0333 dispatcher.poll(); 0334 0335 // Test that the timer triggers when it should. Triggering when it should not will likely 0336 // cause a segfault or other error because the Timer's memory has been cleared. 0337 0338 checkTrigger = true; 0339 dispatcher.poll(); 0340 TEST(didTrigger != removeTimer); 0341 0342 // clean up 0343 if (!removeTimer) { 0344 t->~Timer(); 0345 } 0346 memset(timerStorage, 0, sizeof(timerStorage)); 0347 } 0348 } 0349 } 0350 0351 // Test that all 0 msec timers trigger equally often regardless how long their triggered handler takes 0352 static void testTriggerOnlyOncePerDispatch() 0353 { 0354 EventDispatcher dispatcher; 0355 int dispatchCounter = 0; 0356 int triggerCounter1 = 0; 0357 int triggerCounter2 = 0; 0358 int hardWorkCounter = 0; 0359 0360 Timer counter1Timer(&dispatcher); 0361 counter1Timer.setRunning(true); 0362 0363 Timer hardWorkTimer(&dispatcher); 0364 hardWorkTimer.setRunning(true); 0365 0366 Timer counter2Timer(&dispatcher); 0367 counter2Timer.setRunning(true); 0368 0369 CompletionFunc countTriggers([&triggerCounter1, &triggerCounter2, &dispatchCounter, 0370 &counter1Timer, &counter2Timer] (void *task) { 0371 if (task == &counter1Timer) { 0372 TEST(triggerCounter1 == dispatchCounter); 0373 triggerCounter1++; 0374 } else { 0375 TEST(task == &counter2Timer); 0376 TEST(triggerCounter2 == dispatchCounter); 0377 triggerCounter2++; 0378 } 0379 }); 0380 counter1Timer.setCompletionListener(&countTriggers); 0381 counter2Timer.setCompletionListener(&countTriggers); 0382 0383 CompletionFunc hardWorker([&hardWorkCounter, &dispatchCounter] (void * /*task*/) 0384 { 0385 TEST(hardWorkCounter == dispatchCounter); 0386 uint64 startTime = PlatformTime::monotonicMsecs(); 0387 // waste ten milliseconds, trying not to spend all time in PlatformTime::monotonicMsecs() 0388 do { 0389 for (volatile int i = 0; i < 20000; i++) {} 0390 } while (PlatformTime::monotonicMsecs() < startTime + 10); 0391 hardWorkCounter++; 0392 }); 0393 hardWorkTimer.setCompletionListener(&hardWorker); 0394 0395 EventDispatcherInterruptor interruptor(&dispatcher, 200); 0396 0397 while (dispatcher.poll()) { 0398 dispatchCounter++; 0399 } 0400 0401 TEST(triggerCounter1 == dispatchCounter || triggerCounter1 == dispatchCounter - 1); 0402 TEST(triggerCounter2 == dispatchCounter || triggerCounter2 == dispatchCounter - 1); 0403 TEST(hardWorkCounter == dispatchCounter || hardWorkCounter == dispatchCounter - 1); 0404 } 0405 0406 static void testReEnableNonRepeatingInTrigger() 0407 { 0408 EventDispatcher dispatcher; 0409 0410 int slowCounter = 0; 0411 CompletionFunc slowReEnabler([&slowCounter] (void *task) 0412 { 0413 slowCounter++; 0414 Timer *timer = reinterpret_cast<Timer *>(task); 0415 TEST(!timer->isRunning()); 0416 timer->setRunning(true); 0417 TEST(timer->isRunning()); 0418 TEST(timer->interval() == 5); 0419 }); 0420 0421 Timer slow(&dispatcher); 0422 slow.setCompletionListener(&slowReEnabler); 0423 slow.setRepeating(false); 0424 slow.setInterval(5); 0425 slow.setRunning(true); 0426 0427 int fastCounter = 0; 0428 CompletionFunc fastReEnabler([&fastCounter] (void *task) { 0429 fastCounter++; 0430 Timer *timer = reinterpret_cast<Timer *>(task); 0431 TEST(!timer->isRunning()); 0432 timer->setRunning(true); 0433 TEST(timer->isRunning()); 0434 TEST(timer->interval() == 0); 0435 }); 0436 0437 Timer fast(&dispatcher); 0438 fast.setCompletionListener(&fastReEnabler); 0439 fast.setRepeating(false); 0440 fast.setInterval(0); 0441 fast.setRunning(true); 0442 0443 // also make sure that setRepeating(false) has any effect at all... 0444 int noRepeatCounter = 0; 0445 CompletionFunc noRepeatCheck([&noRepeatCounter] (void * /*task*/) { 0446 noRepeatCounter++; 0447 }); 0448 Timer noRepeat(&dispatcher); 0449 noRepeat.setCompletionListener(&noRepeatCheck); 0450 noRepeat.setRepeating(false); 0451 noRepeat.setInterval(10); 0452 noRepeat.setRunning(true); 0453 0454 EventDispatcherInterruptor interruptor(&dispatcher, 50); 0455 0456 while (dispatcher.poll()) { 0457 } 0458 0459 //std::cout << "\nfastCounter: " << fastCounter << " slowCounter: " << slowCounter << '\n'; 0460 0461 TEST(noRepeatCounter == 1); 0462 #ifdef _WIN32 0463 TEST(slowCounter >= 4 && slowCounter <= 12); 0464 #else 0465 TEST(slowCounter >= 8 && slowCounter <= 12); 0466 #endif 0467 TEST(fastCounter >= 200); // ### hopefully low enough even for really slow machines and / or valgrind 0468 } 0469 0470 static void testSerialWraparound() 0471 { 0472 EventDispatcher dispatcher; 0473 0474 constexpr int timersCount = 17; 0475 Timer* timers[timersCount]; 0476 int lastTriggeredTimer; 0477 0478 CompletionFunc orderCheck([&timers, &lastTriggeredTimer] (void *task) { 0479 int timerIndex = 0; 0480 for (; timerIndex < timersCount; timerIndex++) { 0481 if (timers[timerIndex] == task) { 0482 break; 0483 } 0484 } 0485 TEST(timerIndex < timersCount); 0486 TEST(++lastTriggeredTimer == timerIndex); 0487 if (timerIndex % 4 == 0) { 0488 delete timers[timerIndex]; 0489 timers[timerIndex] = nullptr; 0490 } else if (timerIndex % 2 == 0) { 0491 timers[timerIndex]->setRunning(false); 0492 } 0493 }); 0494 0495 // Glassbox testing: we know that the maximum timer serials is 1023, so testing 10k * 17 timers 0496 // is plenty. This should be adapted if / when the implementation changes. 0497 for (int i = 0; i < 10000; i++) { 0498 for (int j = 0; j < 17; j++) { 0499 timers[j] = new Timer(&dispatcher); 0500 timers[j]->setCompletionListener(&orderCheck); 0501 timers[j]->setRunning(true); 0502 } 0503 0504 lastTriggeredTimer = -1; 0505 0506 dispatcher.poll(); 0507 0508 TEST(lastTriggeredTimer == timersCount - 1); 0509 0510 for (int j = 0; j < 17; j++) { 0511 delete timers[j]; 0512 timers[j] = nullptr; 0513 } 0514 } 0515 } 0516 0517 int main(int, char *[]) 0518 { 0519 testBasic(); 0520 testAccuracy(); 0521 testDeleteInTrigger(); 0522 testDisableInTrigger(); 0523 testAddInTrigger(); 0524 testReAddInTrigger(); 0525 testTriggerOnlyOncePerDispatch(); 0526 testReEnableNonRepeatingInTrigger(); 0527 testSerialWraparound(); 0528 std::cout << "Passed!\n"; 0529 }