1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include "utils/MultiConditionTrigger.h"
17
18 #include <gtest/gtest.h>
19
20 #include <chrono>
21 #include <set>
22 #include <thread>
23 #include <vector>
24
25 #include "tests/statsd_test_util.h"
26
27 #ifdef __ANDROID__
28
29 using namespace std;
30 using std::this_thread::sleep_for;
31
32 namespace android {
33 namespace os {
34 namespace statsd {
35
TEST(MultiConditionTrigger,TestMultipleConditions)36 TEST(MultiConditionTrigger, TestMultipleConditions) {
37 int numConditions = 5;
38 string t1 = "t1", t2 = "t2", t3 = "t3", t4 = "t4", t5 = "t5";
39 set<string> conditionNames = {t1, t2, t3, t4, t5};
40
41 mutex lock;
42 condition_variable cv;
43 bool triggerCalled = false;
44
45 // Mark done as true and notify in the done.
46 MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] {
47 {
48 lock_guard lg(lock);
49 triggerCalled = true;
50 }
51 cv.notify_all();
52 });
53
54 vector<thread> threads;
55 vector<int> done(numConditions, 0);
56
57 int i = 0;
58 for (const string& conditionName : conditionNames) {
59 threads.emplace_back([&done, &conditionName, &trigger, i] {
60 sleep_for(chrono::milliseconds(3));
61 done[i] = 1;
62 trigger.markComplete(conditionName);
63 });
64 i++;
65 }
66
67 unique_lock<mutex> unique_lk(lock);
68 cv.wait(unique_lk, [&triggerCalled] {
69 return triggerCalled;
70 });
71
72 for (i = 0; i < numConditions; i++) {
73 EXPECT_EQ(done[i], 1);
74 }
75
76 for (i = 0; i < numConditions; i++) {
77 threads[i].join();
78 }
79 }
80
TEST(MultiConditionTrigger,TestNoConditions)81 TEST(MultiConditionTrigger, TestNoConditions) {
82 mutex lock;
83 condition_variable cv;
84 bool triggerCalled = false;
85
86 MultiConditionTrigger trigger({}, [&lock, &cv, &triggerCalled] {
87 {
88 lock_guard lg(lock);
89 triggerCalled = true;
90 }
91 cv.notify_all();
92 });
93
94 unique_lock<mutex> unique_lk(lock);
95 cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
96 EXPECT_TRUE(triggerCalled);
97 // Ensure that trigger occurs immediately if no events need to be completed.
98 }
99
TEST(MultiConditionTrigger,TestMarkCompleteCalledBySameCondition)100 TEST(MultiConditionTrigger, TestMarkCompleteCalledBySameCondition) {
101 string t1 = "t1", t2 = "t2";
102 set<string> conditionNames = {t1, t2};
103
104 mutex lock;
105 condition_variable cv;
106 bool triggerCalled = false;
107
108 MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] {
109 {
110 lock_guard lg(lock);
111 triggerCalled = true;
112 }
113 cv.notify_all();
114 });
115
116 trigger.markComplete(t1);
117 trigger.markComplete(t1);
118
119 // Ensure that the trigger still hasn't fired.
120 {
121 lock_guard lg(lock);
122 EXPECT_FALSE(triggerCalled);
123 }
124
125 trigger.markComplete(t2);
126 unique_lock<mutex> unique_lk(lock);
127 cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
128 EXPECT_TRUE(triggerCalled);
129 }
130
TEST(MultiConditionTrigger,TestTriggerOnlyCalledOnce)131 TEST(MultiConditionTrigger, TestTriggerOnlyCalledOnce) {
132 string t1 = "t1";
133 set<string> conditionNames = {t1};
134
135 mutex lock;
136 condition_variable cv;
137 bool triggerCalled = false;
138 int triggerCount = 0;
139
140 MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled, &triggerCount] {
141 {
142 lock_guard lg(lock);
143 triggerCount++;
144 triggerCalled = true;
145 }
146 cv.notify_all();
147 });
148
149 trigger.markComplete(t1);
150
151 // Ensure that the trigger fired.
152 {
153 unique_lock<mutex> unique_lk(lock);
154 cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
155 EXPECT_TRUE(triggerCalled);
156 EXPECT_EQ(triggerCount, 1);
157 triggerCalled = false;
158 }
159
160 trigger.markComplete(t1);
161
162 // Ensure that the trigger does not fire again.
163 {
164 unique_lock<mutex> unique_lk(lock);
165 cv.wait_for(unique_lk, chrono::milliseconds(5), [&triggerCalled] { return triggerCalled; });
166 EXPECT_FALSE(triggerCalled);
167 EXPECT_EQ(triggerCount, 1);
168 }
169 }
170
171 namespace {
172
173 class TriggerDependency {
174 public:
TriggerDependency(mutex & lock,condition_variable & cv,bool & triggerCalled,int & triggerCount)175 TriggerDependency(mutex& lock, condition_variable& cv, bool& triggerCalled, int& triggerCount)
176 : mLock(lock), mCv(cv), mTriggerCalled(triggerCalled), mTriggerCount(triggerCount) {
177 }
178
someMethod()179 void someMethod() {
180 lock_guard lg(mLock);
181 mTriggerCount++;
182 mTriggerCalled = true;
183 mCv.notify_all();
184 }
185
186 private:
187 mutex& mLock;
188 condition_variable& mCv;
189 bool& mTriggerCalled;
190 int& mTriggerCount;
191 };
192
193 } // namespace
194
TEST(MultiConditionTrigger,TestTriggerHasSleep)195 TEST(MultiConditionTrigger, TestTriggerHasSleep) {
196 const string t1 = "t1";
197 set<string> conditionNames = {t1};
198
199 mutex lock;
200 condition_variable cv;
201 bool triggerCalled = false;
202 int triggerCount = 0;
203
204 {
205 TriggerDependency dependency(lock, cv, triggerCalled, triggerCount);
206 MultiConditionTrigger trigger(conditionNames, [&dependency] {
207 std::this_thread::sleep_for(std::chrono::milliseconds(50));
208 dependency.someMethod();
209 });
210 trigger.markComplete(t1);
211
212 // Here dependency instance will go out of scope and the thread within MultiConditionTrigger
213 // after delay will try to call method of already destroyed class instance
214 // with leading crash if trigger execution thread is detached in MultiConditionTrigger
215 // Instead since the MultiConditionTrigger destructor happens before TriggerDependency
216 // destructor, MultiConditionTrigger destructor is waiting on execution thread termination
217 // with thread::join
218 }
219 // At this moment the executor thread guaranteed terminated by MultiConditionTrigger destructor
220
221 // Ensure that the trigger fired.
222 {
223 unique_lock<mutex> unique_lk(lock);
224 cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
225 EXPECT_TRUE(triggerCalled);
226 EXPECT_EQ(triggerCount, 1);
227 }
228 }
229
TEST(MultiConditionTrigger,TestTriggerHasSleepEarlyTermination)230 TEST(MultiConditionTrigger, TestTriggerHasSleepEarlyTermination) {
231 const string t1 = "t1";
232 set<string> conditionNames = {t1};
233
234 mutex lock;
235 condition_variable cv;
236 bool triggerCalled = false;
237 int triggerCount = 0;
238
239 std::condition_variable triggerTerminationFlag;
240 std::mutex triggerTerminationFlagMutex;
241 bool terminationRequested = false;
242
243 // used for error threshold tolerance due to wait_for() is involved
244 const int64_t errorThresholdMs = 25;
245 const int64_t triggerEarlyTerminationDelayMs = 100;
246 const int64_t triggerStartNs = getElapsedRealtimeNs();
247 {
248 TriggerDependency dependency(lock, cv, triggerCalled, triggerCount);
249 MultiConditionTrigger trigger(
250 conditionNames, [&dependency, &triggerTerminationFlag, &triggerTerminationFlagMutex,
251 &lock, &triggerCalled, &cv, &terminationRequested] {
252 std::unique_lock<std::mutex> lk(triggerTerminationFlagMutex);
253 if (triggerTerminationFlag.wait_for(
254 lk, std::chrono::seconds(1),
255 [&terminationRequested] { return terminationRequested; })) {
256 // triggerTerminationFlag was notified - early termination is requested
257 lock_guard lg(lock);
258 triggerCalled = true;
259 cv.notify_all();
260 return;
261 }
262 dependency.someMethod();
263 });
264 trigger.markComplete(t1);
265
266 // notify to terminate trigger executor thread after triggerEarlyTerminationDelayMs
267 std::this_thread::sleep_for(std::chrono::milliseconds(triggerEarlyTerminationDelayMs));
268 {
269 std::unique_lock<std::mutex> lk(triggerTerminationFlagMutex);
270 terminationRequested = true;
271 }
272 triggerTerminationFlag.notify_all();
273 }
274 // At this moment the executor thread guaranteed terminated by MultiConditionTrigger destructor
275
276 // check that test duration is closer to 100ms rather to 1s
277 const int64_t triggerEndNs = getElapsedRealtimeNs();
278 EXPECT_LE(NanoToMillis(triggerEndNs - triggerStartNs),
279 triggerEarlyTerminationDelayMs + errorThresholdMs);
280
281 // Ensure that the trigger fired but not the dependency.someMethod().
282 {
283 unique_lock<mutex> unique_lk(lock);
284 cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
285 EXPECT_TRUE(triggerCalled);
286 EXPECT_EQ(triggerCount, 0);
287 }
288 }
289
290 } // namespace statsd
291 } // namespace os
292 } // namespace android
293 #else
294 GTEST_LOG_(INFO) << "This test does nothing.\n";
295 #endif
296