diff --git a/include/kf/Semaphore.h b/include/kf/Semaphore.h index ee9c3b2..cf63c0d 100644 --- a/include/kf/Semaphore.h +++ b/include/kf/Semaphore.h @@ -21,10 +21,10 @@ namespace kf return &m_semaphore; } - NTSTATUS wait(_In_opt_ PLARGE_INTEGER timeout = nullptr) + NTSTATUS wait(_In_opt_ const LARGE_INTEGER* timeout = nullptr) { ASSERT(KeGetCurrentIrql() <= APC_LEVEL); - return KeWaitForSingleObject(&m_semaphore, Executive, KernelMode, false, timeout); + return KeWaitForSingleObject(&m_semaphore, Executive, KernelMode, false, const_cast(timeout)); } void release(LONG adjustment = 1) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 50de854..ec9140c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -63,6 +63,7 @@ wdk_add_driver(kf-test WINVER NTDDI_WIN10 STL AutoSpinLockTest.cpp EResourceSharedLockTest.cpp RecursiveAutoSpinLockTest.cpp + SemaphoreTest.cpp UStringBuilderTest.cpp USimpleStringTest.cpp ) diff --git a/test/SemaphoreTest.cpp b/test/SemaphoreTest.cpp new file mode 100644 index 0000000..f89fd79 --- /dev/null +++ b/test/SemaphoreTest.cpp @@ -0,0 +1,132 @@ +#include "pch.h" +#include + +SCENARIO("kf::Semaphore") +{ + constexpr LARGE_INTEGER kOneMillisecond{ .QuadPart = -10'000LL }; // 1ms + constexpr LARGE_INTEGER kZeroTimeout{}; + + GIVEN("A semaphore with count = 1 and limit = 1") + { + kf::Semaphore sem(1, 1); + + WHEN("Trying to acquire should succeed immediately") + { + THEN("Thread doesn't wait") + { + REQUIRE(STATUS_SUCCESS == sem.wait(&kZeroTimeout)); + } + } + + WHEN("Acquiring twice should deplete the count and then timeout") + { + REQUIRE(STATUS_SUCCESS == sem.wait(&kZeroTimeout)); + + THEN("Second wait times out") + { + REQUIRE(STATUS_TIMEOUT == sem.wait(&kOneMillisecond)); + } + } + + WHEN("After depleting, release allows acquiring again") + { + REQUIRE(STATUS_SUCCESS == sem.wait(&kZeroTimeout)); + REQUIRE(STATUS_TIMEOUT == sem.wait(&kOneMillisecond)); + + sem.release(); + + THEN("Wait succeeds after release") + { + REQUIRE(STATUS_SUCCESS == sem.wait(&kZeroTimeout)); + } + } + } + + GIVEN("A semaphore with count = 0 and limit = 1") + { + kf::Semaphore sem(0, 1); + + WHEN("Trying to acquire should timeout") + { + THEN("Wait fails with timeout") + { + REQUIRE(STATUS_TIMEOUT == sem.wait(&kOneMillisecond)); + } + } + + WHEN("Release makes next acquire succeed and then timeout again") + { + sem.release(); + + THEN("First wait succeeds immediately and subsequent wait times out") + { + REQUIRE(STATUS_SUCCESS == sem.wait(&kZeroTimeout)); + REQUIRE(STATUS_TIMEOUT == sem.wait(&kOneMillisecond)); + } + } + } + + GIVEN("A semaphore with count = 1000 and limit = 1000") + { + kf::Semaphore sem(1000, 1000); + + WHEN("Acquiring up to count succeeds, extra wait times out") + { + for (int i = 0; i < 1000; ++i) + { + REQUIRE(STATUS_SUCCESS == sem.wait(&kZeroTimeout)); + } + + THEN("Next wait times out") + { + REQUIRE(STATUS_TIMEOUT == sem.wait(&kOneMillisecond)); + } + } + + WHEN("Releasing after depletion allows acquire to succeed") + { + for (int i = 0; i < 1000; ++i) + { + REQUIRE(STATUS_SUCCESS == sem.wait(&kZeroTimeout)); + } + REQUIRE(STATUS_TIMEOUT == sem.wait(&kOneMillisecond)); + + sem.release(); + + THEN("Wait succeeds after release") + { + REQUIRE(STATUS_SUCCESS == sem.wait(&kZeroTimeout)); + } + } + } + + GIVEN("A semaphore with count = 0, limit = 2 and multi-adjust release") + { + kf::Semaphore sem(0, 2); + + WHEN("Release with adjustment 2") + { + sem.release(2); + + THEN("Two immediate acquires succeed, third times out") + { + REQUIRE(STATUS_SUCCESS == sem.wait(&kZeroTimeout)); + REQUIRE(STATUS_SUCCESS == sem.wait(&kZeroTimeout)); + REQUIRE(STATUS_TIMEOUT == sem.wait(&kOneMillisecond)); + } + } + + WHEN("Multiple single releases allow multiple acquires") + { + sem.release(); + sem.release(); + + THEN("Two immediate acquires succeed, next times out") + { + REQUIRE(STATUS_SUCCESS == sem.wait(&kZeroTimeout)); + REQUIRE(STATUS_SUCCESS == sem.wait(&kZeroTimeout)); + REQUIRE(STATUS_TIMEOUT == sem.wait(&kOneMillisecond)); + } + } + } +} \ No newline at end of file