204 lines
5.1 KiB
C++
204 lines
5.1 KiB
C++
#include "gtest/gtest.h"
|
|
#include "piconditionvar.h"
|
|
#include "pithread.h"
|
|
#include "testutil.h"
|
|
|
|
class ConditionVariable : public ::testing::Test, public TestUtil {
|
|
public:
|
|
~ConditionVariable() {
|
|
delete variable;
|
|
}
|
|
PIMutex m;
|
|
PIConditionVariable* variable;
|
|
|
|
protected:
|
|
void SetUp() override {
|
|
variable = new PIConditionVariable();
|
|
adapterFunctionDefault = [&](){
|
|
m.lock();
|
|
variable->wait(m);
|
|
m.unlock();
|
|
};
|
|
}
|
|
};
|
|
|
|
TEST_F(ConditionVariable, wait_is_block) {
|
|
createThread();
|
|
ASSERT_FALSE(thread->waitForFinish(WAIT_THREAD_TIME_MS));
|
|
}
|
|
|
|
TEST_F(ConditionVariable, wait_is_block_when_notifyOne_before_wait) {
|
|
variable->notifyOne();
|
|
createThread();
|
|
ASSERT_FALSE(thread->waitForFinish(WAIT_THREAD_TIME_MS));
|
|
}
|
|
|
|
TEST_F(ConditionVariable, wait_is_block_when_notifyAll_before_wait) {
|
|
variable->notifyAll();
|
|
createThread();
|
|
ASSERT_FALSE(thread->waitForFinish(WAIT_THREAD_TIME_MS));
|
|
}
|
|
|
|
TEST_F(ConditionVariable, wait_is_unblock_when_notifyOne_after_wait) {
|
|
createThread();
|
|
variable->notifyOne();
|
|
ASSERT_TRUE(thread->waitForFinish(WAIT_THREAD_TIME_MS));
|
|
}
|
|
|
|
TEST_F(ConditionVariable, wait_is_unblock_when_notifyAll_after_wait) {
|
|
PIVector<PIThread*> threads;
|
|
|
|
for (int i = 0; i < THREAD_COUNT; ++i) {
|
|
threads.push_back(new PIThread([=](){ adapterFunctionDefault(); }));
|
|
}
|
|
|
|
piForeach(PIThread* thread, threads) thread->startOnce();
|
|
piMSleep(WAIT_THREAD_TIME_MS * THREAD_COUNT);
|
|
variable->notifyAll();
|
|
PITimeMeasurer measurer;
|
|
piForeach(PIThread* thread, threads) {
|
|
int timeout = WAIT_THREAD_TIME_MS * THREAD_COUNT - (int)measurer.elapsed_m();
|
|
thread->waitForFinish(timeout > 0 ? timeout : 0);
|
|
}
|
|
for (size_t i = 0; i < threads.size(); ++i) EXPECT_FALSE(threads[i]->isRunning()) << "Thread " << i << " still running";
|
|
piForeach(PIThread* thread, threads) delete thread;
|
|
}
|
|
|
|
TEST_F(ConditionVariable, wait_is_one_unblock_when_notifyOne) {
|
|
PIVector<PIThread*> threads;
|
|
|
|
for (int i = 0; i < THREAD_COUNT; ++i) {
|
|
threads.push_back(new PIThread(adapterFunctionDefault));
|
|
}
|
|
|
|
piForeach(PIThread* thread, threads) thread->startOnce();
|
|
piMSleep(WAIT_THREAD_TIME_MS * THREAD_COUNT);
|
|
variable->notifyOne();
|
|
piMSleep(WAIT_THREAD_TIME_MS * THREAD_COUNT);
|
|
int runningThreadCount = 0;
|
|
piForeach(PIThread* thread, threads) if (thread->isRunning()) runningThreadCount++;
|
|
ASSERT_EQ(runningThreadCount, THREAD_COUNT - 1);
|
|
}
|
|
|
|
TEST_F(ConditionVariable, wait_is_protected_unblock_when_notifyOne) {
|
|
createThread([&](){
|
|
m.lock();
|
|
variable->wait(m);
|
|
piMSleep(2 * WAIT_THREAD_TIME_MS);
|
|
// Missing unlock
|
|
});
|
|
variable->notifyOne();
|
|
piMSleep(WAIT_THREAD_TIME_MS);
|
|
ASSERT_FALSE(m.tryLock());
|
|
}
|
|
|
|
TEST_F(ConditionVariable, wait_condition_is_block) {
|
|
createThread([&](){
|
|
m.lock();
|
|
variable->wait(m, [](){ return false; });
|
|
m.unlock();
|
|
});
|
|
ASSERT_FALSE(thread->waitForFinish(WAIT_THREAD_TIME_MS));
|
|
}
|
|
|
|
TEST_F(ConditionVariable, wait_condition_is_check_condition_before_block) {
|
|
bool isConditionChecked = false;
|
|
createThread([&](){
|
|
m.lock();
|
|
variable->wait(m, [&](){
|
|
isConditionChecked = true;
|
|
return false;
|
|
});
|
|
m.unlock();
|
|
});
|
|
m.lock();
|
|
ASSERT_TRUE(isConditionChecked);
|
|
m.unlock();
|
|
}
|
|
|
|
TEST_F(ConditionVariable, wait_condition_is_check_condition_when_notifyOne) {
|
|
bool isConditionChecked;
|
|
createThread([&](){
|
|
m.lock();
|
|
variable->wait(m, [&](){
|
|
isConditionChecked = true;
|
|
return false;
|
|
});
|
|
m.unlock();
|
|
});
|
|
m.lock();
|
|
isConditionChecked = false;
|
|
m.unlock();
|
|
variable->notifyOne();
|
|
piMSleep(threadStartTime + 1);
|
|
m.lock();
|
|
ASSERT_TRUE(isConditionChecked);
|
|
m.unlock();
|
|
}
|
|
|
|
TEST_F(ConditionVariable, wait_condition_is_unblock_when_condition_and_notifyOne) {
|
|
bool condition = false;
|
|
createThread([&](){
|
|
m.lock();
|
|
variable->wait(m, [&](){ return condition; });
|
|
m.unlock();
|
|
});
|
|
m.lock();
|
|
condition = true;
|
|
m.unlock();
|
|
variable->notifyOne();
|
|
ASSERT_TRUE(thread->waitForFinish(WAIT_THREAD_TIME_MS));
|
|
}
|
|
|
|
TEST_F(ConditionVariable, DISABLED_waitFor_is_block_before_timeout) {
|
|
createThread([&](){
|
|
PITimeMeasurer measurer;
|
|
m.lock();
|
|
variable->waitFor(m, WAIT_THREAD_TIME_MS * 2);
|
|
m.unlock();
|
|
// Not reliable because spurious wakeup may happen
|
|
ASSERT_GE(measurer.elapsed_m(), WAIT_THREAD_TIME_MS);
|
|
});
|
|
EXPECT_TRUE(thread->waitForFinish(WAIT_THREAD_TIME_MS * 3));
|
|
}
|
|
|
|
TEST_F(ConditionVariable, waitFor_is_unblock_when_timeout) {
|
|
std::atomic_bool isUnblock(false);
|
|
createThread([&](){
|
|
m.lock();
|
|
variable->waitFor(m, WAIT_THREAD_TIME_MS);
|
|
isUnblock = true;
|
|
m.unlock();
|
|
});
|
|
// Test failed if suspend forever
|
|
EXPECT_TRUE(thread->waitForFinish(2 * WAIT_THREAD_TIME_MS));
|
|
ASSERT_TRUE(isUnblock);
|
|
}
|
|
|
|
TEST_F(ConditionVariable, waitFor_is_false_when_timeout) {
|
|
bool waitRet = true;
|
|
createThread([&](){
|
|
m.lock();
|
|
waitRet = variable->waitFor(m, WAIT_THREAD_TIME_MS);
|
|
m.unlock();
|
|
});
|
|
EXPECT_TRUE(thread->waitForFinish(2 * WAIT_THREAD_TIME_MS));
|
|
ASSERT_FALSE(waitRet);
|
|
}
|
|
|
|
TEST_F(ConditionVariable, waitFor_is_unblock_when_condition_and_notifyOne) {
|
|
bool condition = false;
|
|
createThread([&](){
|
|
m.lock();
|
|
variable->waitFor(m, 3 * WAIT_THREAD_TIME_MS, [&](){ return condition; });
|
|
m.unlock();
|
|
});
|
|
EXPECT_TRUE(thread->isRunning());
|
|
m.lock();
|
|
condition = true;
|
|
m.unlock();
|
|
variable->notifyOne();
|
|
piMSleep(WAIT_THREAD_TIME_MS);
|
|
ASSERT_FALSE(thread->isRunning());
|
|
}
|