blob: 49112bc754834cd91a5b6c13ae4cdf82d5f5454b [file] [log] [blame]
Brian Silverman797e71e2013-09-06 17:29:39 -07001#include "aos/common/condition.h"
2
3#include <unistd.h>
4#include <sys/types.h>
5#include <sys/wait.h>
6
7#include "gtest/gtest.h"
8
9#include "aos/common/util/thread.h"
10#include "aos/common/time.h"
11#include "aos/common/mutex.h"
12#include "aos/common/queue_testutils.h"
13#include "aos/common/type_traits.h"
14#include "aos/atom_code/ipc_lib/core_lib.h"
15#include "aos/common/logging/logging.h"
16
17using ::aos::time::Time;
18using ::aos::common::testing::GlobalCoreInstance;
19
20namespace aos {
21namespace testing {
22
23class ConditionTest : public ::testing::Test {
24 public:
25 struct Shared {
26 Shared() : condition(&mutex) {}
27
28 Mutex mutex;
29 Condition condition;
30 };
31 static_assert(shm_ok<Shared>::value,
32 "it's going to get shared between forked processes");
33
34 ConditionTest() : shared_(static_cast<Shared *>(shm_malloc(sizeof(Shared)))) {
35 new (shared_) Shared();
36 }
37
38 GlobalCoreInstance my_core;
39
40 Shared *const shared_;
41
42 void Settle() {
Brian Silverman4c04efd2013-09-07 14:51:33 -070043 time::SleepFor(::Time::InSeconds(0.008));
Brian Silverman797e71e2013-09-06 17:29:39 -070044 }
45};
46
47class ConditionTestProcess {
48 public:
49 enum class Action {
50 kWaitLockStart, // lock, delay, wait, unlock
51 kWait, // delay, lock, wait, unlock
52 kWaitNoUnlock, // delay, lock, wait
53 kSignal, // delay, signal
54 kBroadcast, // delay, broadcast
55 };
56
57 // This amount gets added to any passed in delay to make the test repeatable.
58 static constexpr ::Time kMinimumDelay = ::Time::InSeconds(0.015);
Brian Silverman4c04efd2013-09-07 14:51:33 -070059 static constexpr ::Time kDefaultTimeout = ::Time::InSeconds(0.09);
Brian Silverman797e71e2013-09-06 17:29:39 -070060
61 // delay is how long to wait before doing action to condition.
62 // timeout is how long to wait after delay before deciding that it's hung.
63 ConditionTestProcess(const ::Time &delay, Action action, Condition *condition,
64 const ::Time &timeout = kDefaultTimeout)
65 : delay_(kMinimumDelay + delay), action_(action), condition_(condition),
66 timeout_(delay_ + timeout), child_(-1),
67 shared_(static_cast<Shared *>(shm_malloc(sizeof(Shared)))) {
68 new (shared_) Shared();
69 }
70 ~ConditionTestProcess() {
71 assert(child_ == -1);
72 }
73
74 void Start() {
75 ASSERT_FALSE(shared_->started);
76
77 child_ = fork();
78 if (child_ == 0) { // in child
79 Run();
80 exit(EXIT_SUCCESS);
81 } else { // in parent
82 assert(child_ != -1);
83
84 shared_->ready.Lock();
85
86 shared_->started = true;
87 }
88 }
89
90 bool IsFinished() {
91 return shared_->finished;
92 }
93
94 ::testing::AssertionResult Hung() {
95 if (!shared_->started) {
96 ADD_FAILURE();
97 return ::testing::AssertionFailure() << "not started yet";
98 }
99 if (shared_->finished) {
100 Join();
101 return ::testing::AssertionFailure() << "already returned";
102 }
103 if (shared_->delayed) {
104 if (shared_->start_time > ::Time::Now() + timeout_) {
105 Kill();
106 return ::testing::AssertionSuccess() << "already been too long";
107 }
108 } else {
109 shared_->done_delaying.Lock();
110 }
111 time::SleepFor(::Time::InSeconds(0.01));
112 if (!shared_->finished) time::SleepUntil(shared_->start_time + timeout_);
113 if (shared_->finished) {
114 Join();
115 return ::testing::AssertionFailure() << "completed within timeout";
116 } else {
117 Kill();
118 return ::testing::AssertionSuccess() << "took too long";
119 }
120 }
121 ::testing::AssertionResult Test() {
122 Start();
123 return Hung();
124 }
125
126 private:
127 struct Shared {
128 Shared()
129 : started(false), delayed(false), start_time(0, 0), finished(false) {
130 done_delaying.Lock();
131 ready.Lock();
132 }
133
134 bool started;
135 bool delayed;
136 Mutex done_delaying;
137 ::Time start_time;
138 bool finished;
139 Mutex ready;
140 };
141 static_assert(shm_ok<Shared>::value,
142 "it's going to get shared between forked processes");
143
144 void Run() {
145 if (action_ == Action::kWaitLockStart) {
146 shared_->ready.Unlock();
147 condition_->m()->Lock();
148 }
149 time::SleepFor(delay_);
150 shared_->start_time = ::Time::Now();
151 shared_->delayed = true;
152 shared_->done_delaying.Unlock();
153 switch (action_) {
154 case Action::kWait:
155 case Action::kWaitNoUnlock:
156 shared_->ready.Unlock();
157 condition_->m()->Lock();
158 case Action::kWaitLockStart:
159 condition_->Wait();
160 break;
161 case Action::kSignal:
162 shared_->ready.Unlock();
163 condition_->Signal();
164 break;
165 case Action::kBroadcast:
166 shared_->ready.Unlock();
167 condition_->Broadcast();
168 break;
169 }
170 shared_->finished = true;
171 if (action_ == Action::kWait || action_ == Action::kWaitLockStart) {
172 condition_->m()->Unlock();
173 }
174 }
175
176 void Join() {
177 assert(child_ != -1);
178 int status;
179 do {
180 assert(waitpid(child_, &status, 0) == child_);
181 } while (!(WIFEXITED(status) || WIFSIGNALED(status)));
182 child_ = -1;
183 }
184 void Kill() {
185 assert(child_ != -1);
186 assert(kill(child_, SIGTERM) == 0);
187 Join();
188 }
189
190 const ::Time delay_;
191 const Action action_;
192 Condition *const condition_;
193 const ::Time timeout_;
194
195 pid_t child_;
196
197 Shared *const shared_;
198};
199constexpr ::Time ConditionTestProcess::kMinimumDelay;
200constexpr ::Time ConditionTestProcess::kDefaultTimeout;
201
202// Makes sure that the testing framework and everything work for a really simple
203// Wait() and then Signal().
204TEST_F(ConditionTest, Basic) {
205 ConditionTestProcess thread(::Time(0, 0),
206 ConditionTestProcess::Action::kWait,
207 &shared_->condition);
208 thread.Start();
209 Settle();
210 EXPECT_FALSE(thread.IsFinished());
211 shared_->condition.Signal();
212 EXPECT_FALSE(thread.Hung());
213}
214
215// Makes sure that the worker thread locks before it tries to Wait() etc.
216TEST_F(ConditionTest, Locking) {
217 ConditionTestProcess thread(::Time(0, 0),
218 ConditionTestProcess::Action::kWait,
219 &shared_->condition);
220 shared_->mutex.Lock();
221 thread.Start();
222 Settle();
223 // This Signal() shouldn't do anything because the thread should still be
224 // waiting to lock the mutex.
225 shared_->condition.Signal();
226 Settle();
227 shared_->mutex.Unlock();
228 EXPECT_TRUE(thread.Hung());
229}
230
231// Tests that the work thread only catches a Signal() after the mutex gets
232// unlocked.
233TEST_F(ConditionTest, LockFirst) {
234 ConditionTestProcess thread(::Time(0, 0),
235 ConditionTestProcess::Action::kWait,
236 &shared_->condition);
237 shared_->mutex.Lock();
238 thread.Start();
239 Settle();
240 shared_->condition.Signal();
241 Settle();
242 EXPECT_FALSE(thread.IsFinished());
243 shared_->mutex.Unlock();
244 Settle();
245 EXPECT_FALSE(thread.IsFinished());
246 shared_->condition.Signal();
247 EXPECT_FALSE(thread.Hung());
248}
249
250// Tests that the mutex gets relocked after Wait() returns.
251TEST_F(ConditionTest, Relocking) {
252 ConditionTestProcess thread(::Time(0, 0),
253 ConditionTestProcess::Action::kWaitNoUnlock,
254 &shared_->condition);
255 thread.Start();
256 Settle();
257 shared_->condition.Signal();
258 EXPECT_FALSE(thread.Hung());
259 EXPECT_FALSE(shared_->mutex.TryLock());
260}
261
262} // namespace testing
263} // namespace aos