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 }