diff options
Diffstat (limited to '')
-rw-r--r-- | xbmc/threads/test/TestEvent.cpp | 628 |
1 files changed, 628 insertions, 0 deletions
diff --git a/xbmc/threads/test/TestEvent.cpp b/xbmc/threads/test/TestEvent.cpp new file mode 100644 index 0000000..c4526b3 --- /dev/null +++ b/xbmc/threads/test/TestEvent.cpp @@ -0,0 +1,628 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "threads/Event.h" +#include "threads/IRunnable.h" +#include "threads/test/TestHelpers.h" + +#include <memory> +#include <stdio.h> + +using namespace XbmcThreads; +using namespace std::chrono_literals; + +//============================================================================= +// Helper classes +//============================================================================= + +class waiter : public IRunnable +{ + CEvent& event; +public: + bool& result; + + volatile bool waiting; + + waiter(CEvent& o, bool& flag) : event(o), result(flag), waiting(false) {} + + void Run() override + { + waiting = true; + result = event.Wait(); + waiting = false; + } +}; + +class timed_waiter : public IRunnable +{ + CEvent& event; + std::chrono::milliseconds waitTime; + +public: + int& result; + + volatile bool waiting; + + timed_waiter(CEvent& o, int& flag, std::chrono::milliseconds waitTimeMillis) + : event(o), waitTime(waitTimeMillis), result(flag), waiting(false) + { + } + + void Run() override + { + waiting = true; + result = 0; + result = event.Wait(waitTime) ? 1 : -1; + waiting = false; + } +}; + +class group_wait : public IRunnable +{ + CEventGroup& event; + std::chrono::milliseconds timeout; + +public: + CEvent* result; + bool waiting; + + group_wait(CEventGroup& o) : event(o), timeout(-1ms), result(NULL), waiting(false) {} + + group_wait(CEventGroup& o, std::chrono::milliseconds timeout_) + : event(o), timeout(timeout_), result(NULL), waiting(false) + { + } + + void Run() override + { + waiting = true; + if (timeout == -1ms) + result = event.wait(); + else + result = event.wait(timeout); + waiting = false; + } +}; + +//============================================================================= + +TEST(TestEvent, General) +{ + CEvent event; + bool result = false; + waiter w1(event,result); + thread waitThread(w1); + + EXPECT_TRUE(waitForWaiters(event, 1, 10000ms)); + + EXPECT_TRUE(!result); + + event.Set(); + + EXPECT_TRUE(waitThread.timed_join(10000ms)); + + EXPECT_TRUE(result); +} + +TEST(TestEvent, TwoWaits) +{ + CEvent event; + bool result1 = false; + bool result2 = false; + waiter w1(event,result1); + waiter w2(event,result2); + thread waitThread1(w1); + thread waitThread2(w2); + + EXPECT_TRUE(waitForWaiters(event, 2, 10000ms)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + event.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread2.timed_join(10000ms)); + + EXPECT_TRUE(result1); + EXPECT_TRUE(result2); + +} + +TEST(TestEvent, TimedWaits) +{ + CEvent event; + int result1 = 10; + timed_waiter w1(event, result1, 100ms); + thread waitThread1(w1); + + EXPECT_TRUE(waitForWaiters(event, 1, 10000ms)); + + EXPECT_TRUE(result1 == 0); + + event.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + + EXPECT_TRUE(result1 == 1); +} + +TEST(TestEvent, TimedWaitsTimeout) +{ + CEvent event; + int result1 = 10; + timed_waiter w1(event, result1, 50ms); + thread waitThread1(w1); + + EXPECT_TRUE(waitForWaiters(event, 1, 100ms)); + + EXPECT_TRUE(result1 == 0); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + + EXPECT_TRUE(result1 == -1); +} + +TEST(TestEvent, Group) +{ + CEvent event1; + CEvent event2; + + CEventGroup group{&event1,&event2}; + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(event1, 1, 10000ms)); + EXPECT_TRUE(waitForWaiters(event2, 1, 10000ms)); + EXPECT_TRUE(waitForWaiters(group, 1, 10000ms)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + event1.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread3.timed_join(10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!w1.waiting); + EXPECT_TRUE(!result2); + EXPECT_TRUE(w2.waiting); + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(10000ms)); +} + +/* Test disabled for now, because it deadlocks +TEST(TestEvent, GroupLimitedGroupScope) +{ + CEvent event1; + CEvent event2; + + { + CEventGroup group(&event1,&event2,NULL); + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(event1,1,10000ms)); + EXPECT_TRUE(waitForWaiters(event2,1,10000ms)); + EXPECT_TRUE(waitForWaiters(group,1,10000ms)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + event1.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread3.timed_join(10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!w1.waiting); + EXPECT_TRUE(!result2); + EXPECT_TRUE(w2.waiting); + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + } + + event2.Set(); + + std::this_thread::sleep_for(50ms); // give thread 2 a chance to exit +}*/ + +TEST(TestEvent, TwoGroups) +{ + CEvent event1; + CEvent event2; + + CEventGroup group1{&event1,&event2}; + CEventGroup group2{&event1,&event2}; + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group1); + group_wait w4(group2); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + thread waitThread4(w4); + + EXPECT_TRUE(waitForWaiters(event1, 1, 10000ms)); + EXPECT_TRUE(waitForWaiters(event2, 1, 10000ms)); + EXPECT_TRUE(waitForWaiters(group1, 1, 10000ms)); + EXPECT_TRUE(waitForWaiters(group2, 1, 10000ms)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(w3.waiting); + EXPECT_EQ(w3.result,(void*)NULL); + EXPECT_TRUE(w4.waiting); + EXPECT_EQ(w4.result,(void*)NULL); + + event1.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread3.timed_join(10000ms)); + EXPECT_TRUE(waitThread4.timed_join(10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!w1.waiting); + EXPECT_TRUE(!result2); + EXPECT_TRUE(w2.waiting); + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + EXPECT_TRUE(!w4.waiting); + EXPECT_TRUE(w4.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(10000ms)); +} + +TEST(TestEvent, AutoResetBehavior) +{ + CEvent event; + + EXPECT_TRUE(!event.Wait(1ms)); + + event.Set(); // event will remain signaled if there are no waits + + EXPECT_TRUE(event.Wait(1ms)); +} + +TEST(TestEvent, ManualReset) +{ + CEvent event(true); + bool result = false; + waiter w1(event,result); + thread waitThread(w1); + + EXPECT_TRUE(waitForWaiters(event, 1, 10000ms)); + + EXPECT_TRUE(!result); + + event.Set(); + + EXPECT_TRUE(waitThread.timed_join(10000ms)); + + EXPECT_TRUE(result); + + // with manual reset, the state should remain signaled + EXPECT_TRUE(event.Wait(1ms)); + + event.Reset(); + + EXPECT_TRUE(!event.Wait(1ms)); +} + +TEST(TestEvent, InitVal) +{ + CEvent event(false,true); + EXPECT_TRUE(event.Wait(50ms)); +} + +TEST(TestEvent, SimpleTimeout) +{ + CEvent event; + EXPECT_TRUE(!event.Wait(50ms)); +} + +TEST(TestEvent, GroupChildSet) +{ + CEvent event1(true); + CEvent event2; + + event1.Set(); + CEventGroup group{&event1,&event2}; + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(event2, 1, 10000ms)); + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread3.timed_join(10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(10000ms)); +} + +TEST(TestEvent, GroupChildSet2) +{ + CEvent event1(true,true); + CEvent event2; + + CEventGroup group{&event1,&event2}; + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(event2, 1, 10000ms)); + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread3.timed_join(10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(10000ms)); +} + +TEST(TestEvent, GroupWaitResetsChild) +{ + CEvent event1; + CEvent event2; + + CEventGroup group{&event1,&event2}; + + group_wait w3(group); + + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(group, 1, 10000ms)); + + EXPECT_TRUE(w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + event2.Set(); + + EXPECT_TRUE(waitThread3.timed_join(10000ms)); + + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event2); + // event2 should have been reset. + EXPECT_TRUE(event2.Wait(1ms) == false); +} + +TEST(TestEvent, GroupTimedWait) +{ + CEvent event1; + CEvent event2; + CEventGroup group{&event1,&event2}; + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + + thread waitThread1(w1); + thread waitThread2(w2); + + EXPECT_TRUE(waitForWaiters(event1, 1, 10000ms)); + EXPECT_TRUE(waitForWaiters(event2, 1, 10000ms)); + + EXPECT_TRUE(group.wait(20ms) == NULL); // waited ... got nothing + + group_wait w3(group, 50ms); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(group, 1, 10000ms)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + // this should end given the wait is for only 50 millis + EXPECT_TRUE(waitThread3.timed_join(200ms)); + + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + group_wait w4(group, 50ms); + thread waitThread4(w4); + + EXPECT_TRUE(waitForWaiters(group, 1, 10000ms)); + + EXPECT_TRUE(w4.waiting); + EXPECT_TRUE(w4.result == NULL); + + event1.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread4.timed_join(10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(!w4.waiting); + EXPECT_TRUE(w4.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(10000ms)); +} + +#define TESTNUM 100000l +#define NUMTHREADS 100l + +CEvent* g_event = NULL; +std::atomic<long> g_mutex; + +class mass_waiter : public IRunnable +{ +public: + CEvent& event; + bool result; + + volatile bool waiting = false; + + mass_waiter() : event(*g_event) {} + + void Run() override + { + waiting = true; + AtomicGuard g(&g_mutex); + result = event.Wait(); + waiting = false; + } +}; + +class poll_mass_waiter : public IRunnable +{ +public: + CEvent& event; + bool result; + + volatile bool waiting = false; + + poll_mass_waiter() : event(*g_event) {} + + void Run() override + { + waiting = true; + AtomicGuard g(&g_mutex); + while ((result = event.Wait(0ms)) == false) + ; + waiting = false; + } +}; + +template <class W> void RunMassEventTest(std::vector<std::shared_ptr<W>>& m, bool canWaitOnEvent) +{ + std::vector<std::shared_ptr<thread>> t(NUMTHREADS); + for(size_t i=0; i<NUMTHREADS; i++) + t[i].reset(new thread(*m[i])); + + EXPECT_TRUE(waitForThread(g_mutex, NUMTHREADS, 10000ms)); + if (canWaitOnEvent) + { + EXPECT_TRUE(waitForWaiters(*g_event, NUMTHREADS, 10000ms)); + } + + std::this_thread::sleep_for(100ms); // give them a little more time + + for(size_t i=0; i<NUMTHREADS; i++) + { + EXPECT_TRUE(m[i]->waiting); + } + + g_event->Set(); + + for(size_t i=0; i<NUMTHREADS; i++) + { + EXPECT_TRUE(t[i]->timed_join(10000ms)); + } + + for(size_t i=0; i<NUMTHREADS; i++) + { + EXPECT_TRUE(!m[i]->waiting); + EXPECT_TRUE(m[i]->result); + } +} + + +TEST(TestMassEvent, General) +{ + g_event = new CEvent(); + + std::vector<std::shared_ptr<mass_waiter>> m(NUMTHREADS); + for(size_t i=0; i<NUMTHREADS; i++) + m[i].reset(new mass_waiter()); + + RunMassEventTest(m,true); + delete g_event; +} + +TEST(TestMassEvent, Polling) +{ + g_event = new CEvent(true); // polling needs to avoid the auto-reset + + std::vector<std::shared_ptr<poll_mass_waiter>> m(NUMTHREADS); + for(size_t i=0; i<NUMTHREADS; i++) + m[i].reset(new poll_mass_waiter()); + + RunMassEventTest(m,false); + delete g_event; +} + |