/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "utils/MultiConditionTrigger.h" #include #include #include #include #include #include "tests/statsd_test_util.h" #ifdef __ANDROID__ using namespace std; using std::this_thread::sleep_for; namespace android { namespace os { namespace statsd { TEST(MultiConditionTrigger, TestMultipleConditions) { int numConditions = 5; string t1 = "t1", t2 = "t2", t3 = "t3", t4 = "t4", t5 = "t5"; set conditionNames = {t1, t2, t3, t4, t5}; mutex lock; condition_variable cv; bool triggerCalled = false; // Mark done as true and notify in the done. MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] { { lock_guard lg(lock); triggerCalled = true; } cv.notify_all(); }); vector threads; vector done(numConditions, 0); int i = 0; for (const string& conditionName : conditionNames) { threads.emplace_back([&done, &conditionName, &trigger, i] { sleep_for(chrono::milliseconds(3)); done[i] = 1; trigger.markComplete(conditionName); }); i++; } unique_lock unique_lk(lock); cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); for (i = 0; i < numConditions; i++) { EXPECT_EQ(done[i], 1); } for (i = 0; i < numConditions; i++) { threads[i].join(); } } TEST(MultiConditionTrigger, TestNoConditions) { mutex lock; condition_variable cv; bool triggerCalled = false; MultiConditionTrigger trigger({}, [&lock, &cv, &triggerCalled] { { lock_guard lg(lock); triggerCalled = true; } cv.notify_all(); }); unique_lock unique_lk(lock); cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); EXPECT_TRUE(triggerCalled); // Ensure that trigger occurs immediately if no events need to be completed. } TEST(MultiConditionTrigger, TestMarkCompleteCalledBySameCondition) { string t1 = "t1", t2 = "t2"; set conditionNames = {t1, t2}; mutex lock; condition_variable cv; bool triggerCalled = false; MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] { { lock_guard lg(lock); triggerCalled = true; } cv.notify_all(); }); trigger.markComplete(t1); trigger.markComplete(t1); // Ensure that the trigger still hasn't fired. { lock_guard lg(lock); EXPECT_FALSE(triggerCalled); } trigger.markComplete(t2); unique_lock unique_lk(lock); cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); EXPECT_TRUE(triggerCalled); } TEST(MultiConditionTrigger, TestTriggerOnlyCalledOnce) { string t1 = "t1"; set conditionNames = {t1}; mutex lock; condition_variable cv; bool triggerCalled = false; int triggerCount = 0; MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled, &triggerCount] { { lock_guard lg(lock); triggerCount++; triggerCalled = true; } cv.notify_all(); }); trigger.markComplete(t1); // Ensure that the trigger fired. { unique_lock unique_lk(lock); cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); EXPECT_TRUE(triggerCalled); EXPECT_EQ(triggerCount, 1); triggerCalled = false; } trigger.markComplete(t1); // Ensure that the trigger does not fire again. { unique_lock unique_lk(lock); cv.wait_for(unique_lk, chrono::milliseconds(5), [&triggerCalled] { return triggerCalled; }); EXPECT_FALSE(triggerCalled); EXPECT_EQ(triggerCount, 1); } } namespace { class TriggerDependency { public: TriggerDependency(mutex& lock, condition_variable& cv, bool& triggerCalled, int& triggerCount) : mLock(lock), mCv(cv), mTriggerCalled(triggerCalled), mTriggerCount(triggerCount) { } void someMethod() { lock_guard lg(mLock); mTriggerCount++; mTriggerCalled = true; mCv.notify_all(); } private: mutex& mLock; condition_variable& mCv; bool& mTriggerCalled; int& mTriggerCount; }; } // namespace TEST(MultiConditionTrigger, TestTriggerHasSleep) { const string t1 = "t1"; set conditionNames = {t1}; mutex lock; condition_variable cv; bool triggerCalled = false; int triggerCount = 0; { TriggerDependency dependency(lock, cv, triggerCalled, triggerCount); MultiConditionTrigger trigger(conditionNames, [&dependency] { std::this_thread::sleep_for(std::chrono::milliseconds(50)); dependency.someMethod(); }); trigger.markComplete(t1); // Here dependency instance will go out of scope and the thread within MultiConditionTrigger // after delay will try to call method of already destroyed class instance // with leading crash if trigger execution thread is detached in MultiConditionTrigger // Instead since the MultiConditionTrigger destructor happens before TriggerDependency // destructor, MultiConditionTrigger destructor is waiting on execution thread termination // with thread::join } // At this moment the executor thread guaranteed terminated by MultiConditionTrigger destructor // Ensure that the trigger fired. { unique_lock unique_lk(lock); cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); EXPECT_TRUE(triggerCalled); EXPECT_EQ(triggerCount, 1); } } TEST(MultiConditionTrigger, TestTriggerHasSleepEarlyTermination) { const string t1 = "t1"; set conditionNames = {t1}; mutex lock; condition_variable cv; bool triggerCalled = false; int triggerCount = 0; std::condition_variable triggerTerminationFlag; std::mutex triggerTerminationFlagMutex; bool terminationRequested = false; // used for error threshold tolerance due to wait_for() is involved const int64_t errorThresholdMs = 25; const int64_t triggerEarlyTerminationDelayMs = 100; const int64_t triggerStartNs = getElapsedRealtimeNs(); { TriggerDependency dependency(lock, cv, triggerCalled, triggerCount); MultiConditionTrigger trigger( conditionNames, [&dependency, &triggerTerminationFlag, &triggerTerminationFlagMutex, &lock, &triggerCalled, &cv, &terminationRequested] { std::unique_lock lk(triggerTerminationFlagMutex); if (triggerTerminationFlag.wait_for( lk, std::chrono::seconds(1), [&terminationRequested] { return terminationRequested; })) { // triggerTerminationFlag was notified - early termination is requested lock_guard lg(lock); triggerCalled = true; cv.notify_all(); return; } dependency.someMethod(); }); trigger.markComplete(t1); // notify to terminate trigger executor thread after triggerEarlyTerminationDelayMs std::this_thread::sleep_for(std::chrono::milliseconds(triggerEarlyTerminationDelayMs)); { std::unique_lock lk(triggerTerminationFlagMutex); terminationRequested = true; } triggerTerminationFlag.notify_all(); } // At this moment the executor thread guaranteed terminated by MultiConditionTrigger destructor // check that test duration is closer to 100ms rather to 1s const int64_t triggerEndNs = getElapsedRealtimeNs(); EXPECT_LE(NanoToMillis(triggerEndNs - triggerStartNs), triggerEarlyTerminationDelayMs + errorThresholdMs); // Ensure that the trigger fired but not the dependency.someMethod(). { unique_lock unique_lk(lock); cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); EXPECT_TRUE(triggerCalled); EXPECT_EQ(triggerCount, 0); } } } // namespace statsd } // namespace os } // namespace android #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif