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