/* * 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 "system_wrappers/source/event_timer_posix.h" #include "rtc_base/criticalsection.h" #include "rtc_base/event.h" #include "test/gtest.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(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