Skip to content

Commit eb411d0

Browse files
author
Vlada Kanivets
committed
add tests for Semaphore
1 parent bf56e1b commit eb411d0

File tree

3 files changed

+212
-1
lines changed

3 files changed

+212
-1
lines changed

include/kf/Semaphore.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace kf
88
class Semaphore
99
{
1010
public:
11-
Semaphore(LONG count, LONG limit)
11+
Semaphore(LONG count, LONG limit) : m_limit(limit)
1212
{
1313
KeInitializeSemaphore(&m_semaphore, count, limit);
1414
}
@@ -29,10 +29,22 @@ namespace kf
2929

3030
void release(LONG adjustment = 1)
3131
{
32+
LONG currentCount = KeReadStateSemaphore(&m_semaphore);
33+
if (currentCount + adjustment > m_limit)
34+
{
35+
return;
36+
}
3237
KeReleaseSemaphore(&m_semaphore, IO_NO_INCREMENT, adjustment, false);
3338
}
3439

40+
ULONG getState()
41+
{
42+
ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
43+
return KeReadStateSemaphore(&m_semaphore);
44+
}
45+
3546
private:
47+
LONG m_limit = 0;
3648
KSEMAPHORE m_semaphore;
3749
};
3850
}

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ wdk_add_driver(kf-test WINVER NTDDI_WIN10 STL
5959
AutoSpinLockTest.cpp
6060
EResourceSharedLockTest.cpp
6161
RecursiveAutoSpinLockTest.cpp
62+
SemaphoreTest.cpp
6263
)
6364

6465
target_link_libraries(kf-test kf::kf kmtest::kmtest)

test/SemaphoreTest.cpp

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#include "pch.h"
2+
#include <kf/Semaphore.h>
3+
#include <kf/Thread.h>
4+
5+
namespace
6+
{
7+
constexpr int kOneMillisecond = 1000;
8+
9+
struct Context
10+
{
11+
kf::Semaphore* semaphore = nullptr;
12+
NTSTATUS status = STATUS_WAIT_1;
13+
};
14+
15+
void delay()
16+
{
17+
LARGE_INTEGER timeout;
18+
timeout.QuadPart = -kOneMillisecond;
19+
KeDelayExecutionThread(KernelMode, false, &timeout);
20+
}
21+
}
22+
23+
SCENARIO("kf::Semaphore")
24+
{
25+
GIVEN("A semaphore with count = 1 and limit = 1")
26+
{
27+
kf::Semaphore sem(1, 1);
28+
Context ctx{ &sem };
29+
kf::Thread thread;
30+
31+
WHEN("Trying to acquire should succeed immediately")
32+
{
33+
thread.start([](void* context) {
34+
auto ctx = static_cast<Context*>(context);
35+
ctx->status = ctx->semaphore->wait();
36+
}, &ctx);
37+
38+
thread.join();
39+
40+
THEN("Thread doesn't wait")
41+
{
42+
REQUIRE(ctx.status == STATUS_SUCCESS);
43+
}
44+
}
45+
}
46+
47+
GIVEN("A semaphore with count = 0 and limit = 1")
48+
{
49+
kf::Semaphore sem(0, 1);
50+
51+
WHEN("Threads wait with 0")
52+
{
53+
kf::Thread thread;
54+
Context ctx{ &sem };
55+
56+
thread.start([](void* context) {
57+
auto ctx = static_cast<Context*>(context);
58+
LARGE_INTEGER timeout{ 0 };
59+
ctx->status = ctx->semaphore->wait(&timeout);
60+
}, &ctx);
61+
62+
thread.join();
63+
64+
THEN("Thread should be timed out")
65+
{
66+
REQUIRE(ctx.status == STATUS_TIMEOUT);
67+
}
68+
}
69+
70+
WHEN("Threads wait with 100 miliseconds")
71+
{
72+
kf::Thread thread;
73+
Context ctx{ &sem };
74+
75+
thread.start([](void* context) {
76+
auto ctx = static_cast<Context*>(context);
77+
LARGE_INTEGER timeout;
78+
timeout.QuadPart = kOneMillisecond * -100;
79+
ctx->status = ctx->semaphore->wait(&timeout);
80+
}, &ctx);
81+
82+
delay();
83+
84+
THEN("Thread should be waiting")
85+
{
86+
REQUIRE(ctx.status == STATUS_WAIT_1);
87+
}
88+
89+
thread.join();
90+
91+
THEN("Thread should be timed out afted 100 milliseconds")
92+
{
93+
REQUIRE(ctx.status == STATUS_TIMEOUT);
94+
}
95+
}
96+
}
97+
98+
GIVEN("Semaphore with count = 0 and limit = 2")
99+
{
100+
kf::Semaphore sem(0, 2);
101+
Context ctx1{ &sem }, ctx2{ &sem };
102+
kf::Thread thread1, thread2;
103+
104+
thread1.start([](void* context) {
105+
auto ctx = static_cast<Context*>(context);
106+
ctx->status = ctx->semaphore->wait();
107+
}, &ctx1);
108+
109+
thread2.start([](void* context) {
110+
auto ctx = static_cast<Context*>(context);
111+
ctx->status = ctx->semaphore->wait();
112+
}, &ctx2);
113+
114+
delay();
115+
116+
THEN("Both threads are waiting")
117+
{
118+
REQUIRE(ctx1.status == STATUS_WAIT_1);
119+
REQUIRE(ctx2.status == STATUS_WAIT_1);
120+
}
121+
122+
WHEN("We release semaphore with 2")
123+
{
124+
sem.release(2);
125+
thread1.join();
126+
thread2.join();
127+
128+
THEN("Both threads should stop waiting")
129+
{
130+
REQUIRE(ctx1.status == STATUS_SUCCESS);
131+
REQUIRE(ctx2.status == STATUS_SUCCESS);
132+
}
133+
}
134+
}
135+
136+
GIVEN("Semaphore with count = 2 and limit = 2")
137+
{
138+
kf::Semaphore sem(2, 2);
139+
Context ctx{ &sem };
140+
kf::Thread thread;
141+
142+
WHEN("Semaphore released over limit twice")
143+
{
144+
sem.release();
145+
sem.release();
146+
147+
THEN("No crash occures")
148+
{
149+
}
150+
}
151+
}
152+
153+
GIVEN("Semaphore with count = 1 and limit = 1")
154+
{
155+
kf::Semaphore sem(1, 2);
156+
Context ctx1{ &sem }, ctx2{ &sem }, ctx3{ &sem };
157+
kf::Thread thread1, thread2, thread3;
158+
159+
WHEN("Semaphore is released twice, exceeding the limit")
160+
{
161+
sem.release();
162+
sem.release();
163+
164+
THEN("First two threads should succeed, third should wait")
165+
{
166+
thread1.start([](void* context) {
167+
auto ctx = static_cast<Context*>(context);
168+
ctx->status = ctx->semaphore->wait();
169+
}, &ctx1);
170+
171+
thread2.start([](void* context) {
172+
auto ctx = static_cast<Context*>(context);
173+
ctx->status = ctx->semaphore->wait();
174+
}, &ctx2);
175+
176+
thread3.start([](void* context) {
177+
auto ctx = static_cast<Context*>(context);
178+
ctx->status = ctx->semaphore->wait();
179+
}, &ctx3);
180+
181+
delay();
182+
183+
int waitCount = ctx1.status != STATUS_SUCCESS + ctx2.status != STATUS_SUCCESS + ctx3.status != STATUS_SUCCESS;
184+
REQUIRE(waitCount == 1);
185+
186+
sem.release();
187+
thread1.join();
188+
thread2.join();
189+
thread3.join();
190+
191+
THEN("Third thread should succeed after release")
192+
{
193+
REQUIRE(ctx3.status == STATUS_SUCCESS);
194+
}
195+
}
196+
}
197+
}
198+
}

0 commit comments

Comments
 (0)