/ src / common / UnitTests-CommonUtils / Threading.Tests.cpp
Threading.Tests.cpp
  1  #include "pch.h"
  2  #include "TestHelpers.h"
  3  #include <OnThreadExecutor.h>
  4  #include <EventWaiter.h>
  5  #include <EventLocker.h>
  6  
  7  using namespace Microsoft::VisualStudio::CppUnitTestFramework;
  8  
  9  namespace UnitTestsCommonUtils
 10  {
 11      TEST_CLASS(OnThreadExecutorTests)
 12      {
 13      public:
 14          TEST_METHOD(Constructor_CreatesInstance)
 15          {
 16              OnThreadExecutor executor;
 17              // Should not crash
 18              Assert::IsTrue(true);
 19          }
 20  
 21          TEST_METHOD(Submit_SingleTask_Executes)
 22          {
 23              OnThreadExecutor executor;
 24              std::atomic<bool> executed{ false };
 25  
 26              auto future = executor.submit(OnThreadExecutor::task_t([&executed]() {
 27                  executed = true;
 28              }));
 29  
 30              future.wait();
 31              Assert::IsTrue(executed);
 32          }
 33  
 34          TEST_METHOD(Submit_MultipleTasks_ExecutesAll)
 35          {
 36              OnThreadExecutor executor;
 37              std::atomic<int> counter{ 0 };
 38  
 39              std::vector<std::future<void>> futures;
 40              for (int i = 0; i < 10; ++i)
 41              {
 42                  futures.push_back(executor.submit(OnThreadExecutor::task_t([&counter]() {
 43                      counter++;
 44                  })));
 45              }
 46  
 47              for (auto& f : futures)
 48              {
 49                  f.wait();
 50              }
 51  
 52              Assert::AreEqual(10, counter.load());
 53          }
 54  
 55          TEST_METHOD(Submit_TasksExecuteInOrder)
 56          {
 57              OnThreadExecutor executor;
 58              std::vector<int> order;
 59              std::mutex orderMutex;
 60  
 61              std::vector<std::future<void>> futures;
 62              for (int i = 0; i < 5; ++i)
 63              {
 64                  futures.push_back(executor.submit(OnThreadExecutor::task_t([&order, &orderMutex, i]() {
 65                      std::lock_guard lock(orderMutex);
 66                      order.push_back(i);
 67                  })));
 68              }
 69  
 70              for (auto& f : futures)
 71              {
 72                  f.wait();
 73              }
 74  
 75              Assert::AreEqual(static_cast<size_t>(5), order.size());
 76              for (int i = 0; i < 5; ++i)
 77              {
 78                  Assert::AreEqual(i, order[i]);
 79              }
 80          }
 81  
 82          TEST_METHOD(Submit_TaskReturnsResult)
 83          {
 84              OnThreadExecutor executor;
 85              std::atomic<int> result{ 0 };
 86  
 87              auto future = executor.submit(OnThreadExecutor::task_t([&result]() {
 88                  result = 42;
 89              }));
 90  
 91              future.wait();
 92              Assert::AreEqual(42, result.load());
 93          }
 94  
 95          TEST_METHOD(Cancel_ClearsPendingTasks)
 96          {
 97              OnThreadExecutor executor;
 98              std::atomic<int> counter{ 0 };
 99  
100              // Submit a slow task first
101              executor.submit(OnThreadExecutor::task_t([&counter]() {
102                  std::this_thread::sleep_for(std::chrono::milliseconds(100));
103                  counter++;
104              }));
105  
106              // Submit more tasks
107              for (int i = 0; i < 5; ++i)
108              {
109                  executor.submit(OnThreadExecutor::task_t([&counter]() {
110                      counter++;
111                  }));
112              }
113  
114              // Cancel pending tasks
115              executor.cancel();
116  
117              // Wait a bit for any running task to complete
118              std::this_thread::sleep_for(std::chrono::milliseconds(200));
119  
120              // Not all tasks should have executed
121              Assert::IsTrue(counter < 6);
122          }
123  
124          TEST_METHOD(Destructor_WaitsForCompletion)
125          {
126              std::atomic<bool> completed{ false };
127              std::future<void> future;
128  
129              {
130                  OnThreadExecutor executor;
131                  future = executor.submit(OnThreadExecutor::task_t([&completed]() {
132                      std::this_thread::sleep_for(std::chrono::milliseconds(50));
133                      completed = true;
134                  }));
135                  future.wait();
136              } // Destructor no longer required to wait for completion
137  
138              Assert::IsTrue(completed);
139          }
140  
141          TEST_METHOD(Submit_AfterCancel_StillWorks)
142          {
143              OnThreadExecutor executor;
144              std::atomic<int> counter{ 0 };
145  
146              executor.submit(OnThreadExecutor::task_t([&counter]() {
147                  counter++;
148              }));
149              executor.cancel();
150  
151              auto future = executor.submit(OnThreadExecutor::task_t([&counter]() {
152                  counter = 42;
153              }));
154              future.wait();
155  
156              Assert::AreEqual(42, counter.load());
157          }
158      };
159  
160      TEST_CLASS(EventWaiterTests)
161      {
162      public:
163          TEST_METHOD(Constructor_CreatesInstance)
164          {
165              EventWaiter waiter;
166              Assert::IsFalse(waiter.is_listening());
167          }
168  
169          TEST_METHOD(Start_ValidEvent_ReturnsTrue)
170          {
171              EventWaiter waiter;
172              bool result = waiter.start(L"TestEvent_Start", [](DWORD) {});
173              Assert::IsTrue(result);
174              Assert::IsTrue(waiter.is_listening());
175              waiter.stop();
176          }
177  
178          TEST_METHOD(Start_AlreadyListening_ReturnsFalse)
179          {
180              EventWaiter waiter;
181              waiter.start(L"TestEvent_Double1", [](DWORD) {});
182              bool result = waiter.start(L"TestEvent_Double2", [](DWORD) {});
183              Assert::IsFalse(result);
184              waiter.stop();
185          }
186  
187          TEST_METHOD(Stop_WhileListening_StopsListening)
188          {
189              EventWaiter waiter;
190              waiter.start(L"TestEvent_Stop", [](DWORD) {});
191              Assert::IsTrue(waiter.is_listening());
192  
193              waiter.stop();
194              Assert::IsFalse(waiter.is_listening());
195          }
196  
197          TEST_METHOD(Stop_WhenNotListening_DoesNotCrash)
198          {
199              EventWaiter waiter;
200              waiter.stop(); // Should not crash
201              Assert::IsFalse(waiter.is_listening());
202          }
203  
204          TEST_METHOD(Stop_CalledMultipleTimes_DoesNotCrash)
205          {
206              EventWaiter waiter;
207              waiter.start(L"TestEvent_MultiStop", [](DWORD) {});
208              waiter.stop();
209              waiter.stop();
210              waiter.stop();
211              Assert::IsFalse(waiter.is_listening());
212          }
213  
214          TEST_METHOD(Callback_EventSignaled_CallsCallback)
215          {
216              EventWaiter waiter;
217              std::atomic<bool> called{ false };
218              std::atomic<DWORD> errorCode{ 0xFFFFFFFF };
219  
220              // Create a named event we can signal
221              std::wstring eventName = L"TestEvent_Callback_" + std::to_wstring(GetCurrentProcessId());
222              HANDLE signalEvent = CreateEventW(nullptr, FALSE, FALSE, eventName.c_str());
223              Assert::IsNotNull(signalEvent);
224  
225              waiter.start(eventName, [&called, &errorCode](DWORD err) {
226                  errorCode = err;
227                  called = true;
228              });
229  
230              // Signal the event
231              SetEvent(signalEvent);
232  
233              // Wait for callback
234              bool waitResult = TestHelpers::WaitFor([&called]() { return called.load(); }, std::chrono::milliseconds(1000));
235  
236              waiter.stop();
237              CloseHandle(signalEvent);
238  
239              Assert::IsTrue(waitResult);
240              Assert::AreEqual(static_cast<DWORD>(ERROR_SUCCESS), errorCode.load());
241          }
242  
243          TEST_METHOD(Destructor_StopsListening)
244          {
245              std::atomic<bool> isListening{ false };
246              {
247                  EventWaiter waiter;
248                  waiter.start(L"TestEvent_Destructor", [](DWORD) {});
249                  isListening = waiter.is_listening();
250              }
251              // After destruction, the waiter should have stopped
252              Assert::IsTrue(isListening);
253          }
254  
255          TEST_METHOD(IsListening_InitialState_ReturnsFalse)
256          {
257              EventWaiter waiter;
258              Assert::IsFalse(waiter.is_listening());
259          }
260  
261          TEST_METHOD(IsListening_AfterStart_ReturnsTrue)
262          {
263              EventWaiter waiter;
264              waiter.start(L"TestEvent_IsListening", [](DWORD) {});
265              Assert::IsTrue(waiter.is_listening());
266              waiter.stop();
267          }
268  
269          TEST_METHOD(IsListening_AfterStop_ReturnsFalse)
270          {
271              EventWaiter waiter;
272              waiter.start(L"TestEvent_AfterStop", [](DWORD) {});
273              waiter.stop();
274              Assert::IsFalse(waiter.is_listening());
275          }
276      };
277  
278      TEST_CLASS(EventLockerTests)
279      {
280      public:
281          TEST_METHOD(Get_ValidEventName_ReturnsLocker)
282          {
283              std::wstring eventName = L"TestEventLocker_" + std::to_wstring(GetCurrentProcessId());
284              auto locker = EventLocker::Get(eventName);
285              Assert::IsTrue(locker.has_value());
286          }
287  
288          TEST_METHOD(Get_UniqueNames_CreatesSeparateLockers)
289          {
290              auto locker1 = EventLocker::Get(L"TestEventLocker1_" + std::to_wstring(GetCurrentProcessId()));
291              auto locker2 = EventLocker::Get(L"TestEventLocker2_" + std::to_wstring(GetCurrentProcessId()));
292              Assert::IsTrue(locker1.has_value());
293              Assert::IsTrue(locker2.has_value());
294          }
295  
296          TEST_METHOD(Destructor_CleansUpHandle)
297          {
298              std::wstring eventName = L"TestEventLockerCleanup_" + std::to_wstring(GetCurrentProcessId());
299              {
300                  auto locker = EventLocker::Get(eventName);
301                  Assert::IsTrue(locker.has_value());
302              }
303              // After destruction, the event should be cleaned up
304              // Creating a new one should succeed
305              auto newLocker = EventLocker::Get(eventName);
306              Assert::IsTrue(newLocker.has_value());
307          }
308  
309          TEST_METHOD(MoveConstructor_TransfersOwnership)
310          {
311              std::wstring eventName = L"TestEventLockerMove_" + std::to_wstring(GetCurrentProcessId());
312              auto locker1 = EventLocker::Get(eventName);
313              Assert::IsTrue(locker1.has_value());
314  
315              EventLocker locker2 = std::move(*locker1);
316              // Move should transfer ownership without crash
317              Assert::IsTrue(true);
318          }
319  
320          TEST_METHOD(MoveAssignment_TransfersOwnership)
321          {
322              std::wstring eventName1 = L"TestEventLockerMoveAssign1_" + std::to_wstring(GetCurrentProcessId());
323              std::wstring eventName2 = L"TestEventLockerMoveAssign2_" + std::to_wstring(GetCurrentProcessId());
324  
325              auto locker1 = EventLocker::Get(eventName1);
326              auto locker2 = EventLocker::Get(eventName2);
327  
328              Assert::IsTrue(locker1.has_value());
329              Assert::IsTrue(locker2.has_value());
330  
331              *locker1 = std::move(*locker2);
332              // Should not crash
333              Assert::IsTrue(true);
334          }
335      };
336  }