blob: e4e12aa08084b1bd3ff4eef63068889378142899 [file] [log] [blame]
Brian Silvermanf7f267a2017-02-04 16:16:08 -08001/*----------------------------------------------------------------------------*/
2/* Copyright (c) FIRST 2016-2017. All Rights Reserved. */
3/* Open Source Software - may be modified and shared by FRC teams. The code */
4/* must be accompanied by the FIRST BSD license file in the root directory of */
5/* the project. */
6/*----------------------------------------------------------------------------*/
7
8#include <atomic>
9#include <chrono>
10#include <condition_variable>
11#include <mutex>
12#include <thread>
13
14#include "HAL/cpp/priority_condition_variable.h"
15#include "HAL/cpp/priority_mutex.h"
16#include "TestBench.h"
17#include "gtest/gtest.h"
18
19namespace wpilib {
20namespace testing {
21
22// Tests that the condition variable class which we wrote ourselves actually
23// does work.
24class ConditionVariableTest : public ::testing::Test {
25 protected:
26 typedef std::unique_lock<priority_mutex> priority_lock;
27
28 // Condition variable to test.
29 priority_condition_variable m_cond;
30
31 // Mutex to pass to condition variable when waiting.
32 priority_mutex m_mutex;
33
34 // flags for testing when threads are completed.
35 std::atomic<bool> m_done1{false}, m_done2{false};
36 // Threads to use for testing. We want multiple threads to ensure that it
37 // behaves correctly when multiple processes are waiting on a signal.
38 std::thread m_watcher1, m_watcher2;
39
40 // Information for when running with predicates.
41 std::atomic<bool> m_pred_var{false};
42
43 void ShortSleep(uint32_t time = 10) {
44 std::this_thread::sleep_for(std::chrono::milliseconds(time));
45 }
46
47 // Start up the given threads with a wait function. The wait function should
48 // call some version of m_cond.wait and should take as an argument a reference
49 // to an std::atomic<bool> which it will set to true when it is ready to have
50 // join called on it.
51 template <class Function>
52 void StartThreads(Function wait) {
53 m_watcher1 = std::thread(wait, std::ref(m_done1));
54 m_watcher2 = std::thread(wait, std::ref(m_done2));
55
56 // Wait briefly to let the lock be unlocked.
57 ShortSleep();
58 bool locked = m_mutex.try_lock();
59 if (locked) m_mutex.unlock();
60 EXPECT_TRUE(locked) << "The condition variable failed to unlock the lock.";
61 }
62
63 void NotifyAll() { m_cond.notify_all(); }
64 void NotifyOne() { m_cond.notify_one(); }
65
66 // Test that all the threads are notified by a notify_all() call.
67 void NotifyAllTest() {
68 NotifyAll();
69 // Wait briefly to let the lock be re-locked.
70 ShortSleep();
71 EXPECT_TRUE(m_done1) << "watcher1 failed to be notified.";
72 EXPECT_TRUE(m_done2) << "watcher2 failed to be notified.";
73 }
74
75 // For use when testing predicates. First tries signalling the threads with
76 // the predicate set to false (and ensures that they do not activate) and then
77 // tests with the predicate set to true.
78 void PredicateTest() {
79 m_pred_var = false;
80 NotifyAll();
81 ShortSleep();
82 EXPECT_FALSE(m_done1) << "watcher1 didn't pay attention to its predicate.";
83 EXPECT_FALSE(m_done2) << "watcher2 didn't pay attention to its predicate.";
84 m_pred_var = true;
85 NotifyAllTest();
86 }
87
88 // Used by the WaitFor and WaitUntil tests to test that, without a predicate,
89 // the timeout works properly.
90 void WaitTimeTest(bool wait_for) {
91 std::atomic<bool> timed_out{true};
92 auto wait_until = [this, &timed_out, wait_for](std::atomic<bool>& done) {
93 priority_lock lock(m_mutex);
94 done = false;
95 if (wait_for) {
96 auto wait_time = std::chrono::milliseconds(100);
97 timed_out = m_cond.wait_for(lock, wait_time) == std::cv_status::timeout;
98 } else {
99 auto wait_time =
100 std::chrono::system_clock::now() + std::chrono::milliseconds(100);
101 timed_out =
102 m_cond.wait_until(lock, wait_time) == std::cv_status::timeout;
103 }
104 EXPECT_TRUE(lock.owns_lock())
105 << "The condition variable should have reacquired the lock.";
106 done = true;
107 };
108
109 // First, test without timing out.
110 timed_out = true;
111 StartThreads(wait_until);
112
113 NotifyAllTest();
114 EXPECT_FALSE(timed_out) << "The watcher should not have timed out.";
115
116 TearDown();
117
118 // Next, test and time out.
119 timed_out = false;
120 StartThreads(wait_until);
121
122 ShortSleep(110);
123
124 EXPECT_TRUE(m_done1) << "watcher1 should have timed out.";
125 EXPECT_TRUE(m_done2) << "watcher2 should have timed out.";
126 EXPECT_TRUE(timed_out) << "The watcher should have timed out.";
127 }
128
129 // For use with tests that have a timeout and a predicate.
130 void WaitTimePredicateTest(bool wait_for) {
131 // The condition_variable return value from the wait_for or wait_until
132 // function should in the case of having a predicate, by a boolean. If the
133 // predicate is true, then the return value will always be true. If the
134 // condition times out and, at the time of the timeout, the predicate is
135 // false, the return value will be false.
136 std::atomic<bool> retval{true};
137 auto predicate = [this]() -> bool { return m_pred_var; };
138 auto wait_until = [this, &retval, predicate,
139 wait_for](std::atomic<bool>& done) {
140 priority_lock lock(m_mutex);
141 done = false;
142 if (wait_for) {
143 auto wait_time = std::chrono::milliseconds(100);
144 retval = m_cond.wait_for(lock, wait_time, predicate);
145 } else {
146 auto wait_time =
147 std::chrono::system_clock::now() + std::chrono::milliseconds(100);
148 retval = m_cond.wait_until(lock, wait_time, predicate);
149 }
150 EXPECT_TRUE(lock.owns_lock())
151 << "The condition variable should have reacquired the lock.";
152 done = true;
153 };
154
155 // Test without timing out and with the predicate set to true.
156 retval = true;
157 m_pred_var = true;
158 StartThreads(wait_until);
159
160 NotifyAllTest();
161 EXPECT_TRUE(retval) << "The watcher should not have timed out.";
162
163 TearDown();
164
165 // Test with timing out and with the predicate set to true.
166 retval = false;
167 m_pred_var = false;
168 StartThreads(wait_until);
169
170 ShortSleep(110);
171
172 EXPECT_TRUE(m_done1) << "watcher1 should have finished.";
173 EXPECT_TRUE(m_done2) << "watcher2 should have finished.";
174 EXPECT_FALSE(retval) << "The watcher should have timed out.";
175
176 TearDown();
177
178 // Test without timing out and run the PredicateTest().
179 retval = false;
180 StartThreads(wait_until);
181
182 PredicateTest();
183 EXPECT_TRUE(retval) << "The return value should have been true.";
184
185 TearDown();
186
187 // Test with timing out and the predicate set to true while we are waiting
188 // for the condition variable to time out.
189 retval = true;
190 StartThreads(wait_until);
191 ShortSleep();
192 m_pred_var = true;
193 ShortSleep(110);
194 EXPECT_TRUE(retval) << "The return value should have been true.";
195 }
196
197 virtual void TearDown() {
198 // If a thread has not completed, then continuing will cause the tests to
199 // hang forever and could cause issues. If we don't call detach, then
200 // std::terminate is called and all threads are terminated.
201 // Detaching is non-optimal, but should allow the rest of the tests to run
202 // before anything drastic occurs.
203 if (m_done1)
204 m_watcher1.join();
205 else
206 m_watcher1.detach();
207 if (m_done2)
208 m_watcher2.join();
209 else
210 m_watcher2.detach();
211 }
212};
213
214TEST_F(ConditionVariableTest, NotifyAll) {
215 auto wait = [this](std::atomic<bool>& done) {
216 priority_lock lock(m_mutex);
217 done = false;
218 m_cond.wait(lock);
219 EXPECT_TRUE(lock.owns_lock())
220 << "The condition variable should have reacquired the lock.";
221 done = true;
222 };
223
224 StartThreads(wait);
225
226 NotifyAllTest();
227}
228
229TEST_F(ConditionVariableTest, NotifyOne) {
230 auto wait = [this](std::atomic<bool>& done) {
231 priority_lock lock(m_mutex);
232 done = false;
233 m_cond.wait(lock);
234 EXPECT_TRUE(lock.owns_lock())
235 << "The condition variable should have reacquired the lock.";
236 done = true;
237 };
238
239 StartThreads(wait);
240
241 NotifyOne();
242 // Wait briefly to let things settle.
243 ShortSleep();
244 EXPECT_TRUE(m_done1 ^ m_done2) << "Only one thread should've been notified.";
245 NotifyOne();
246 ShortSleep();
247 EXPECT_TRUE(m_done2 && m_done2) << "Both threads should've been notified.";
248}
249
250TEST_F(ConditionVariableTest, WaitWithPredicate) {
251 auto predicate = [this]() -> bool { return m_pred_var; };
252 auto wait_predicate = [this, predicate](std::atomic<bool>& done) {
253 priority_lock lock(m_mutex);
254 done = false;
255 m_cond.wait(lock, predicate);
256 EXPECT_TRUE(lock.owns_lock())
257 << "The condition variable should have reacquired the lock.";
258 done = true;
259 };
260
261 StartThreads(wait_predicate);
262
263 PredicateTest();
264}
265
266TEST_F(ConditionVariableTest, WaitUntil) { WaitTimeTest(false); }
267
268TEST_F(ConditionVariableTest, WaitUntilWithPredicate) {
269 WaitTimePredicateTest(false);
270}
271
272TEST_F(ConditionVariableTest, WaitFor) { WaitTimeTest(true); }
273
274TEST_F(ConditionVariableTest, WaitForWithPredicate) {
275 WaitTimePredicateTest(true);
276}
277
278TEST_F(ConditionVariableTest, NativeHandle) {
279 auto wait = [this](std::atomic<bool>& done) {
280 priority_lock lock(m_mutex);
281 done = false;
282 m_cond.wait(lock);
283 EXPECT_TRUE(lock.owns_lock())
284 << "The condition variable should have reacquired the lock.";
285 done = true;
286 };
287
288 StartThreads(wait);
289
290 pthread_cond_t* native_handle = m_cond.native_handle();
291 pthread_cond_broadcast(native_handle);
292 ShortSleep();
293 EXPECT_TRUE(m_done1) << "watcher1 failed to be notified.";
294 EXPECT_TRUE(m_done2) << "watcher2 failed to be notified.";
295}
296
297} // namespace testing
298} // namespace wpilib