Skip to content

Commit a3db2bb

Browse files
author
Vlada Kanivets
committed
add tests
1 parent bf56e1b commit a3db2bb

3 files changed

Lines changed: 301 additions & 4 deletions

File tree

include/kf/ConditionVariable.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#pragma once
2-
2+
#include <atomic>
33
#include "EResource.h"
44
#include "Semaphore.h"
55

@@ -39,13 +39,17 @@ namespace kf
3939
{
4040
++m_waitersCount;
4141

42-
external.release();
43-
auto status = NT_SUCCESS(m_semaphore.wait(timeout)) ? Status::Success : Status::Timeout;
42+
if (external.isAcquiredExclusive())
43+
{
44+
external.release();
45+
}
46+
auto status = m_semaphore.wait(timeout);
47+
auto result = (NT_SUCCESS(status) && status != STATUS_TIMEOUT) ? Status::Success : Status::Timeout;
4448
external.acquireExclusive();
4549

4650
--m_waitersCount;
4751

48-
return status;
52+
return result;
4953
}
5054

5155
template<class Predicate>

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+
ConditionVariableTest.cpp
6263
)
6364

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

test/ConditionVariableTest.cpp

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
#include "pch.h"
2+
#include <kf/ConditionVariable.h>
3+
#include <kf/Thread.h>
4+
#include <kf/EResource.h>
5+
6+
namespace
7+
{
8+
struct Context
9+
{
10+
kf::EResource* resource = nullptr;
11+
kf::ConditionVariable* cv = nullptr;
12+
LONG counter = 0;
13+
};
14+
15+
void delay()
16+
{
17+
LARGE_INTEGER interval;
18+
interval.QuadPart = -10'000; // 1ms
19+
KeDelayExecutionThread(KernelMode, FALSE, &interval);
20+
}
21+
}
22+
23+
SCENARIO("kf::ConditionVariable")
24+
{
25+
GIVEN("ConditionVariable without timeout and 1 thread")
26+
{
27+
WHEN("waitFor: timeout is nullptr and cv is notified")
28+
{
29+
kf::EResource resource;
30+
kf::ConditionVariable cv;
31+
kf::Thread thread;
32+
thread.start([](void* context) {
33+
auto cv = static_cast<kf::ConditionVariable*>(context);
34+
delay();
35+
cv->notifyOne();
36+
}, &cv);
37+
38+
auto status = cv.waitFor(resource, nullptr);
39+
THEN("waitFor should return success")
40+
{
41+
REQUIRE(status == kf::ConditionVariable::Status::Success);
42+
}
43+
}
44+
45+
WHEN("waitFor: timeout is set to 0")
46+
{
47+
kf::EResource resource;
48+
kf::ConditionVariable cv;
49+
LARGE_INTEGER timeout{ 0 };
50+
auto status = cv.waitFor(resource, &timeout);
51+
THEN("waitFor should return Status::Timeout")
52+
{
53+
REQUIRE(status == kf::ConditionVariable::Status::Timeout);
54+
}
55+
}
56+
57+
WHEN("waitFor: timeout is set to 1ms")
58+
{
59+
kf::EResource resource;
60+
kf::ConditionVariable cv;
61+
LARGE_INTEGER timeout;
62+
timeout.QuadPart = -10'000; // 1ms
63+
auto status = cv.waitFor(resource, &timeout);
64+
THEN("waitFor should return Status::Timeout")
65+
{
66+
REQUIRE(status == kf::ConditionVariable::Status::Timeout);
67+
}
68+
}
69+
}
70+
71+
GIVEN("ConditionVariable wait with predicate immediately true")
72+
{
73+
kf::EResource resource;
74+
kf::ConditionVariable cv;
75+
LONG counter = 1;
76+
77+
cv.wait(resource, [&]() { return counter > 0; });
78+
THEN("Wait should return immediately")
79+
{
80+
REQUIRE(counter == 1);
81+
}
82+
}
83+
84+
GIVEN("ConditionVariable without timeout and 2 Threads waiting for condition variable")
85+
{
86+
kf::EResource resource;
87+
kf::ConditionVariable cv;
88+
Context ctx{ &resource, &cv };
89+
kf::Thread thread1;
90+
kf::Thread thread2;
91+
92+
thread1.start([](void* context) {
93+
auto ctx = static_cast<Context*>(context);
94+
ctx->cv->wait(*ctx->resource, [&]() {
95+
return ctx->counter > 0; });
96+
ctx->counter++;
97+
ctx->resource->release();
98+
}, &ctx);
99+
100+
thread2.start([](void* context) {
101+
auto ctx = static_cast<Context*>(context);
102+
ctx->cv->wait(*ctx->resource, [&]() {
103+
return ctx->counter > 0; });
104+
ctx->counter++;
105+
ctx->resource->release();
106+
}, &ctx);
107+
delay();
108+
109+
WHEN("Predicate is true and called notifyAll")
110+
{
111+
ctx.counter++;
112+
cv.notifyAll();
113+
thread1.join();
114+
thread2.join();
115+
116+
THEN("Both threads should wake up and increment counter")
117+
{
118+
REQUIRE(ctx.counter == 3);
119+
}
120+
}
121+
}
122+
123+
GIVEN("ConditionVariable without timeout and 1 thread waiting for predicate")
124+
{
125+
kf::EResource resource;
126+
kf::ConditionVariable cv;
127+
Context ctx{ &resource, &cv };
128+
kf::Thread thread;
129+
130+
WHEN("Thread is notified")
131+
{
132+
thread.start([](void* context) {
133+
auto ctx = static_cast<Context*>(context);
134+
ctx->cv->wait(*ctx->resource, [&]() { return ctx->counter > 0; });
135+
ctx->counter++;
136+
ctx->resource->release();
137+
}, &ctx);
138+
139+
delay();
140+
141+
THEN("Thread should be waiting for predicate")
142+
{
143+
REQUIRE(ctx.counter == 0);
144+
}
145+
146+
ctx.counter++;
147+
cv.notifyOne();
148+
thread.join();
149+
150+
THEN("Predicate become true and thread should wake up")
151+
{
152+
REQUIRE(ctx.counter == 2);
153+
}
154+
}
155+
}
156+
157+
GIVEN("ConditionVariable with 2 waiting threads and notifyOne is called")
158+
{
159+
kf::EResource resource;
160+
kf::ConditionVariable cv;
161+
Context ctx{ &resource, &cv };
162+
kf::Thread thread1;
163+
kf::Thread thread2;
164+
165+
thread1.start([](void* context) {
166+
auto ctx = static_cast<Context*>(context);
167+
ctx->cv->wait(*ctx->resource, [&]() { return ctx->counter > 0; });
168+
ctx->counter++;
169+
ctx->resource->release();
170+
}, &ctx);
171+
172+
thread2.start([](void* context) {
173+
auto ctx = static_cast<Context*>(context);
174+
ctx->cv->wait(*ctx->resource, [&]() { return ctx->counter > 0; });
175+
ctx->counter++;
176+
ctx->resource->release();
177+
}, &ctx);
178+
179+
delay();
180+
ctx.counter++;
181+
cv.notifyOne();
182+
delay();
183+
184+
THEN("Only one thread should wake up and increment counter")
185+
{
186+
REQUIRE(ctx.counter == 2);
187+
}
188+
189+
cv.notifyOne();
190+
thread1.join();
191+
thread2.join();
192+
193+
THEN("After second notifyOne both threads should finish")
194+
{
195+
REQUIRE(ctx.counter == 3);
196+
}
197+
}
198+
199+
GIVEN("ConditionVariable::waitFor with predicate and timeout")
200+
{
201+
WHEN("Timeout is nullptr and predictate is true")
202+
{
203+
kf::EResource resource;
204+
kf::ConditionVariable cv;
205+
kf::Thread thread;
206+
int counter = 1;
207+
thread.start([](void* context) {
208+
auto cv = static_cast<kf::ConditionVariable*>(context);
209+
delay();
210+
cv->notifyOne();
211+
}, &cv);
212+
213+
THEN("waitFor should return true")
214+
{
215+
REQUIRE(cv.waitFor(resource, nullptr, [&]() { return counter > 0; }));
216+
}
217+
}
218+
219+
WHEN("Timeout is set to 0 and predicate is false")
220+
{
221+
kf::EResource resource;
222+
kf::ConditionVariable cv;
223+
LARGE_INTEGER timeout{ 0 };
224+
int counter = 0;
225+
THEN("waitFor should return false")
226+
{
227+
REQUIRE(!cv.waitFor(resource, &timeout, [&]() { return counter > 0; }));
228+
}
229+
}
230+
231+
WHEN("Timeout is set to 1ms and predicate is false")
232+
{
233+
kf::EResource resource;
234+
kf::ConditionVariable cv;
235+
LARGE_INTEGER timeout;
236+
timeout.QuadPart = -10'000; // 1ms
237+
int counter = 0;
238+
THEN("waitFor should return false")
239+
{
240+
REQUIRE(!cv.waitFor(resource, &timeout, [&]() { return counter > 0; }));
241+
}
242+
}
243+
}
244+
245+
GIVEN("ConditionVariable with no waiters")
246+
{
247+
kf::EResource resource;
248+
kf::ConditionVariable cv;
249+
250+
WHEN("notifyAll is called without waiters")
251+
{
252+
cv.notifyAll();
253+
THEN("Nothing should happen")
254+
{
255+
}
256+
}
257+
258+
WHEN("notifyOne is called without waiters")
259+
{
260+
cv.notifyOne();
261+
THEN("Nothing should happen")
262+
{
263+
}
264+
}
265+
}
266+
267+
GIVEN("ConditionVariable with 1 thread and multiple notifyOne calls")
268+
{
269+
kf::EResource resource;
270+
kf::ConditionVariable cv;
271+
Context ctx{ &resource, &cv };
272+
kf::Thread thread;
273+
274+
thread.start([](void* context) {
275+
auto ctx = static_cast<Context*>(context);
276+
ctx->cv->wait(*ctx->resource, [&]() { return ctx->counter > 0; });
277+
ctx->counter++;
278+
ctx->resource->release();
279+
}, &ctx);
280+
281+
delay();
282+
ctx.counter++;
283+
cv.notifyOne();
284+
cv.notifyOne();
285+
thread.join();
286+
287+
THEN("Thread should wake only once and increment counter")
288+
{
289+
REQUIRE(ctx.counter == 2);
290+
}
291+
}
292+
}

0 commit comments

Comments
 (0)