blob: 4f4514dbccce0497fdc57cc64032291b781069d9 [file] [log] [blame]
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "webrtc/system_wrappers/source/event_timer_posix.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "webrtc/base/event.h"
#include "webrtc/base/criticalsection.h"
namespace webrtc {
enum class ThreadState {
kNotStarted,
kWaiting,
kRequestProcessCall,
kCallingProcess,
kProcessDone,
kContinue,
kExiting,
kDead
};
class EventTimerPosixTest : public testing::Test, public EventTimerPosix {
public:
EventTimerPosixTest()
: thread_state_(ThreadState::kNotStarted),
process_event_(false, true),
main_event_(false, true),
process_thread_id_(0),
process_thread_(nullptr) {}
virtual ~EventTimerPosixTest() {}
rtc::PlatformThread* CreateThread() override {
EXPECT_TRUE(process_thread_ == nullptr);
process_thread_ =
new rtc::PlatformThread(Run, this, "EventTimerPosixTestThread");
return process_thread_;
}
static bool Run(void* obj) {
return static_cast<EventTimerPosixTest*>(obj)->Process();
}
bool Process() {
bool res = ProcessInternal();
if (!res) {
rtc::CritScope cs(&lock_);
thread_state_ = ThreadState::kDead;
main_event_.Set();
}
return res;
}
bool ProcessInternal() {
{
rtc::CritScope cs(&lock_);
if (thread_state_ == ThreadState::kNotStarted) {
if (!ChangeThreadState(ThreadState::kNotStarted,
ThreadState::kContinue)) {
ADD_FAILURE() << "Unable to start process thread";
return false;
}
process_thread_id_ = rtc::CurrentThreadId();
}
}
if (!ChangeThreadState(ThreadState::kContinue, ThreadState::kWaiting))
return false;
if (!AwaitThreadState(ThreadState::kRequestProcessCall,
rtc::Event::kForever))
return false;
if (!ChangeThreadState(ThreadState::kRequestProcessCall,
ThreadState::kCallingProcess))
return false;
EventTimerPosix::Process();
if (!ChangeThreadState(ThreadState::kCallingProcess,
ThreadState::kProcessDone))
return false;
if (!AwaitThreadState(ThreadState::kContinue, rtc::Event::kForever))
return false;
return true;
}
bool IsProcessThread() {
rtc::CritScope cs(&lock_);
return process_thread_id_ == rtc::CurrentThreadId();
}
bool ChangeThreadState(ThreadState prev_state, ThreadState new_state) {
rtc::CritScope cs(&lock_);
if (thread_state_ != prev_state)
return false;
thread_state_ = new_state;
if (IsProcessThread()) {
main_event_.Set();
} else {
process_event_.Set();
}
return true;
}
bool AwaitThreadState(ThreadState state, int timeout) {
rtc::Event* event = IsProcessThread() ? &process_event_ : &main_event_;
do {
rtc::CritScope cs(&lock_);
if (state != ThreadState::kDead && thread_state_ == ThreadState::kExiting)
return false;
if (thread_state_ == state)
return true;
} while (event->Wait(timeout));
return false;
}
bool CallProcess(int timeout_ms) {
return AwaitThreadState(ThreadState::kWaiting, timeout_ms) &&
ChangeThreadState(ThreadState::kWaiting,
ThreadState::kRequestProcessCall);
}
bool AwaitProcessDone(int timeout_ms) {
return AwaitThreadState(ThreadState::kProcessDone, timeout_ms) &&
ChangeThreadState(ThreadState::kProcessDone, ThreadState::kContinue);
}
void TearDown() override {
if (process_thread_) {
{
rtc::CritScope cs(&lock_);
if (thread_state_ != ThreadState::kDead) {
thread_state_ = ThreadState::kExiting;
process_event_.Set();
}
}
ASSERT_TRUE(AwaitThreadState(ThreadState::kDead, 5000));
}
}
ThreadState thread_state_;
rtc::CriticalSection lock_;
rtc::Event process_event_;
rtc::Event main_event_;
rtc::PlatformThreadId process_thread_id_;
rtc::PlatformThread* process_thread_;
};
TEST_F(EventTimerPosixTest, WaiterBlocksUntilTimeout) {
const int kTimerIntervalMs = 100;
const int kTimeoutMs = 5000;
ASSERT_TRUE(StartTimer(false, kTimerIntervalMs));
ASSERT_TRUE(CallProcess(kTimeoutMs));
EventTypeWrapper res = Wait(kTimeoutMs);
EXPECT_EQ(kEventSignaled, res);
ASSERT_TRUE(AwaitProcessDone(kTimeoutMs));
}
TEST_F(EventTimerPosixTest, WaiterWakesImmediatelyAfterTimeout) {
const int kTimerIntervalMs = 100;
const int kTimeoutMs = 5000;
ASSERT_TRUE(StartTimer(false, kTimerIntervalMs));
ASSERT_TRUE(CallProcess(kTimeoutMs));
ASSERT_TRUE(AwaitProcessDone(kTimeoutMs));
EventTypeWrapper res = Wait(0);
EXPECT_EQ(kEventSignaled, res);
}
TEST_F(EventTimerPosixTest, WaiterBlocksUntilTimeoutProcessInactiveOnStart) {
const int kTimerIntervalMs = 100;
const int kTimeoutMs = 5000;
// First call to StartTimer initializes thread.
ASSERT_TRUE(StartTimer(false, kTimerIntervalMs));
// Process thread currently _not_ blocking on Process() call.
ASSERT_TRUE(AwaitThreadState(ThreadState::kWaiting, kTimeoutMs));
// Start new one-off timer, then call Process().
ASSERT_TRUE(StartTimer(false, kTimerIntervalMs));
ASSERT_TRUE(CallProcess(kTimeoutMs));
EventTypeWrapper res = Wait(kTimeoutMs);
EXPECT_EQ(kEventSignaled, res);
ASSERT_TRUE(AwaitProcessDone(kTimeoutMs));
}
} // namespace webrtc