blob: b525f75e6b5f8edfb5c8a62b46d0cd47624365bd [file] [log] [blame]
/*
* 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 <gtest/gtest.h>
#include <chrono>
#include <set>
#include <thread>
#include <vector>
#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<string> 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<thread> threads;
vector<int> 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<mutex> 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<mutex> 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<string> 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<mutex> unique_lk(lock);
cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
EXPECT_TRUE(triggerCalled);
}
TEST(MultiConditionTrigger, TestTriggerOnlyCalledOnce) {
string t1 = "t1";
set<string> 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<mutex> 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<mutex> 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<string> 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<mutex> 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<string> 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<std::mutex> 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<std::mutex> 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<mutex> 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