| // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "base/timer/timer.h" | |
| #include <memory> | |
| #include "base/bind.h" | |
| #include "base/bind_helpers.h" | |
| #include "base/callback.h" | |
| #include "base/macros.h" | |
| #include "base/memory/ptr_util.h" | |
| #include "base/memory/ref_counted.h" | |
| #include "base/message_loop/message_loop.h" | |
| #include "base/metrics/statistics_recorder.h" | |
| #include "base/run_loop.h" | |
| #include "base/sequenced_task_runner.h" | |
| #include "base/synchronization/waitable_event.h" | |
| #include "base/task/post_task.h" | |
| #include "base/test/scoped_task_environment.h" | |
| #include "base/test/test_mock_time_task_runner.h" | |
| #include "base/threading/platform_thread.h" | |
| #include "base/threading/sequenced_task_runner_handle.h" | |
| #include "base/threading/thread.h" | |
| #include "base/time/tick_clock.h" | |
| #include "base/time/time.h" | |
| #include "build/build_config.h" | |
| #include "starboard/types.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| namespace base { | |
| namespace { | |
| // The message loops on which each timer should be tested. | |
| const MessageLoop::Type testing_message_loops[] = { | |
| MessageLoop::TYPE_DEFAULT, MessageLoop::TYPE_IO, | |
| #if !defined(OS_IOS) // iOS does not allow direct running of the UI loop. | |
| MessageLoop::TYPE_UI, | |
| #endif | |
| }; | |
| const int kNumTestingMessageLoops = arraysize(testing_message_loops); | |
| class Receiver { | |
| public: | |
| Receiver() : count_(0) {} | |
| void OnCalled() { count_++; } | |
| bool WasCalled() { return count_ > 0; } | |
| int TimesCalled() { return count_; } | |
| private: | |
| int count_; | |
| }; | |
| // A basic helper class that can start a one-shot timer and signal a | |
| // WaitableEvent when this timer fires. | |
| class OneShotTimerTesterBase { | |
| public: | |
| // |did_run|, if provided, will be signaled when Run() fires. | |
| explicit OneShotTimerTesterBase( | |
| WaitableEvent* did_run = nullptr, | |
| const TimeDelta& delay = TimeDelta::FromMilliseconds(10)) | |
| : did_run_(did_run), delay_(delay) {} | |
| virtual ~OneShotTimerTesterBase() = default; | |
| void Start() { | |
| started_time_ = TimeTicks::Now(); | |
| timer_->Start(FROM_HERE, delay_, this, &OneShotTimerTesterBase::Run); | |
| } | |
| bool IsRunning() { return timer_->IsRunning(); } | |
| TimeTicks started_time() const { return started_time_; } | |
| TimeDelta delay() const { return delay_; } | |
| protected: | |
| virtual void Run() { | |
| if (did_run_) { | |
| EXPECT_FALSE(did_run_->IsSignaled()); | |
| did_run_->Signal(); | |
| } | |
| } | |
| std::unique_ptr<OneShotTimer> timer_ = std::make_unique<OneShotTimer>(); | |
| private: | |
| WaitableEvent* const did_run_; | |
| const TimeDelta delay_; | |
| TimeTicks started_time_; | |
| DISALLOW_COPY_AND_ASSIGN(OneShotTimerTesterBase); | |
| }; | |
| // Extends functionality of OneShotTimerTesterBase with the abilities to wait | |
| // until the timer fires and to change task runner for the timer. | |
| class OneShotTimerTester : public OneShotTimerTesterBase { | |
| public: | |
| // |did_run|, if provided, will be signaled when Run() fires. | |
| explicit OneShotTimerTester( | |
| WaitableEvent* did_run = nullptr, | |
| const TimeDelta& delay = TimeDelta::FromMilliseconds(10)) | |
| : OneShotTimerTesterBase(did_run, delay), | |
| quit_closure_(run_loop_.QuitClosure()) {} | |
| ~OneShotTimerTester() override = default; | |
| void SetTaskRunner(scoped_refptr<SequencedTaskRunner> task_runner) { | |
| timer_->SetTaskRunner(std::move(task_runner)); | |
| // Run() will be invoked on |task_runner| but |run_loop_|'s QuitClosure | |
| // needs to run on this thread (where the MessageLoop lives). | |
| quit_closure_ = BindOnce(IgnoreResult(&SequencedTaskRunner::PostTask), | |
| SequencedTaskRunnerHandle::Get(), FROM_HERE, | |
| run_loop_.QuitClosure()); | |
| } | |
| // Blocks until Run() executes and confirms that Run() didn't fire before | |
| // |delay_| expired. | |
| void WaitAndConfirmTimerFiredAfterDelay() { | |
| run_loop_.Run(); | |
| EXPECT_NE(TimeTicks(), started_time()); | |
| EXPECT_GE(TimeTicks::Now() - started_time(), delay()); | |
| } | |
| protected: | |
| // Overridable method to do things on Run() before signaling events/closures | |
| // managed by this helper. | |
| virtual void OnRun() {} | |
| private: | |
| void Run() override { | |
| OnRun(); | |
| OneShotTimerTesterBase::Run(); | |
| std::move(quit_closure_).Run(); | |
| } | |
| RunLoop run_loop_; | |
| OnceClosure quit_closure_; | |
| DISALLOW_COPY_AND_ASSIGN(OneShotTimerTester); | |
| }; | |
| class OneShotSelfDeletingTimerTester : public OneShotTimerTester { | |
| protected: | |
| void OnRun() override { timer_.reset(); } | |
| }; | |
| constexpr int kNumRepeats = 10; | |
| class RepeatingTimerTester { | |
| public: | |
| explicit RepeatingTimerTester(WaitableEvent* did_run, const TimeDelta& delay) | |
| : counter_(kNumRepeats), | |
| quit_closure_(run_loop_.QuitClosure()), | |
| did_run_(did_run), | |
| delay_(delay) {} | |
| void Start() { | |
| started_time_ = TimeTicks::Now(); | |
| timer_.Start(FROM_HERE, delay_, this, &RepeatingTimerTester::Run); | |
| } | |
| void WaitAndConfirmTimerFiredRepeatedlyAfterDelay() { | |
| run_loop_.Run(); | |
| EXPECT_NE(TimeTicks(), started_time_); | |
| EXPECT_GE(TimeTicks::Now() - started_time_, kNumRepeats * delay_); | |
| } | |
| private: | |
| void Run() { | |
| if (--counter_ == 0) { | |
| if (did_run_) { | |
| EXPECT_FALSE(did_run_->IsSignaled()); | |
| did_run_->Signal(); | |
| } | |
| timer_.Stop(); | |
| quit_closure_.Run(); | |
| } | |
| } | |
| RepeatingTimer timer_; | |
| int counter_; | |
| RunLoop run_loop_; | |
| Closure quit_closure_; | |
| WaitableEvent* const did_run_; | |
| const TimeDelta delay_; | |
| TimeTicks started_time_; | |
| DISALLOW_COPY_AND_ASSIGN(RepeatingTimerTester); | |
| }; | |
| // Basic test with same setup as RunTest_OneShotTimers_Cancel below to confirm | |
| // that |did_run_a| would be signaled in that test if it wasn't for the | |
| // deletion. | |
| void RunTest_OneShotTimers(MessageLoop::Type message_loop_type) { | |
| #if defined(STARBOARD) | |
| if (message_loop_type == MessageLoop::TYPE_UI) { | |
| return; | |
| } | |
| #endif | |
| MessageLoop loop(message_loop_type); | |
| WaitableEvent did_run_a(WaitableEvent::ResetPolicy::MANUAL, | |
| WaitableEvent::InitialState::NOT_SIGNALED); | |
| OneShotTimerTester a(&did_run_a); | |
| a.Start(); | |
| OneShotTimerTester b; | |
| b.Start(); | |
| b.WaitAndConfirmTimerFiredAfterDelay(); | |
| EXPECT_TRUE(did_run_a.IsSignaled()); | |
| } | |
| void RunTest_OneShotTimers_Cancel(MessageLoop::Type message_loop_type) { | |
| #if defined(STARBOARD) | |
| if (message_loop_type == MessageLoop::TYPE_UI) { | |
| return; | |
| } | |
| #endif | |
| MessageLoop loop(message_loop_type); | |
| WaitableEvent did_run_a(WaitableEvent::ResetPolicy::MANUAL, | |
| WaitableEvent::InitialState::NOT_SIGNALED); | |
| OneShotTimerTester* a = new OneShotTimerTester(&did_run_a); | |
| // This should run before the timer expires. | |
| SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, a); | |
| // Now start the timer. | |
| a->Start(); | |
| OneShotTimerTester b; | |
| b.Start(); | |
| b.WaitAndConfirmTimerFiredAfterDelay(); | |
| EXPECT_FALSE(did_run_a.IsSignaled()); | |
| } | |
| void RunTest_OneShotSelfDeletingTimer(MessageLoop::Type message_loop_type) { | |
| #if defined(STARBOARD) | |
| if (message_loop_type == MessageLoop::TYPE_UI) { | |
| return; | |
| } | |
| #endif | |
| MessageLoop loop(message_loop_type); | |
| OneShotSelfDeletingTimerTester f; | |
| f.Start(); | |
| f.WaitAndConfirmTimerFiredAfterDelay(); | |
| } | |
| void RunTest_RepeatingTimer(MessageLoop::Type message_loop_type, | |
| const TimeDelta& delay) { | |
| #if defined(STARBOARD) | |
| if (message_loop_type == MessageLoop::TYPE_UI) { | |
| return; | |
| } | |
| #endif | |
| MessageLoop loop(message_loop_type); | |
| RepeatingTimerTester f(nullptr, delay); | |
| f.Start(); | |
| f.WaitAndConfirmTimerFiredRepeatedlyAfterDelay(); | |
| } | |
| void RunTest_RepeatingTimer_Cancel(MessageLoop::Type message_loop_type, | |
| const TimeDelta& delay) { | |
| #if defined(STARBOARD) | |
| if (message_loop_type == MessageLoop::TYPE_UI) { | |
| return; | |
| } | |
| #endif | |
| MessageLoop loop(message_loop_type); | |
| WaitableEvent did_run_a(WaitableEvent::ResetPolicy::MANUAL, | |
| WaitableEvent::InitialState::NOT_SIGNALED); | |
| RepeatingTimerTester* a = new RepeatingTimerTester(&did_run_a, delay); | |
| // This should run before the timer expires. | |
| SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, a); | |
| // Now start the timer. | |
| a->Start(); | |
| RepeatingTimerTester b(nullptr, delay); | |
| b.Start(); | |
| b.WaitAndConfirmTimerFiredRepeatedlyAfterDelay(); | |
| // |a| should not have fired despite |b| starting after it on the same | |
| // sequence and being complete by now. | |
| EXPECT_FALSE(did_run_a.IsSignaled()); | |
| } | |
| class DelayTimerTarget { | |
| public: | |
| bool signaled() const { return signaled_; } | |
| void Signal() { | |
| ASSERT_FALSE(signaled_); | |
| signaled_ = true; | |
| } | |
| private: | |
| bool signaled_ = false; | |
| }; | |
| void RunTest_DelayTimer_NoCall(MessageLoop::Type message_loop_type) { | |
| #if defined(STARBOARD) | |
| if (message_loop_type == MessageLoop::TYPE_UI) { | |
| return; | |
| } | |
| #endif | |
| MessageLoop loop(message_loop_type); | |
| // If Delay is never called, the timer shouldn't go off. | |
| DelayTimerTarget target; | |
| DelayTimer timer(FROM_HERE, TimeDelta::FromMilliseconds(1), &target, | |
| &DelayTimerTarget::Signal); | |
| OneShotTimerTester tester; | |
| tester.Start(); | |
| tester.WaitAndConfirmTimerFiredAfterDelay(); | |
| ASSERT_FALSE(target.signaled()); | |
| } | |
| void RunTest_DelayTimer_OneCall(MessageLoop::Type message_loop_type) { | |
| #if defined(STARBOARD) | |
| if (message_loop_type == MessageLoop::TYPE_UI) { | |
| return; | |
| } | |
| #endif | |
| MessageLoop loop(message_loop_type); | |
| DelayTimerTarget target; | |
| DelayTimer timer(FROM_HERE, TimeDelta::FromMilliseconds(1), &target, | |
| &DelayTimerTarget::Signal); | |
| timer.Reset(); | |
| OneShotTimerTester tester(nullptr, TimeDelta::FromMilliseconds(100)); | |
| tester.Start(); | |
| tester.WaitAndConfirmTimerFiredAfterDelay(); | |
| ASSERT_TRUE(target.signaled()); | |
| } | |
| struct ResetHelper { | |
| ResetHelper(DelayTimer* timer, DelayTimerTarget* target) | |
| : timer_(timer), target_(target) {} | |
| void Reset() { | |
| ASSERT_FALSE(target_->signaled()); | |
| timer_->Reset(); | |
| } | |
| private: | |
| DelayTimer* const timer_; | |
| DelayTimerTarget* const target_; | |
| }; | |
| void RunTest_DelayTimer_Reset(MessageLoop::Type message_loop_type) { | |
| #if defined(STARBOARD) | |
| if (message_loop_type == MessageLoop::TYPE_UI) { | |
| return; | |
| } | |
| #endif | |
| MessageLoop loop(message_loop_type); | |
| // If Delay is never called, the timer shouldn't go off. | |
| DelayTimerTarget target; | |
| DelayTimer timer(FROM_HERE, TimeDelta::FromMilliseconds(50), &target, | |
| &DelayTimerTarget::Signal); | |
| timer.Reset(); | |
| ResetHelper reset_helper(&timer, &target); | |
| OneShotTimer timers[20]; | |
| for (size_t i = 0; i < arraysize(timers); ++i) { | |
| timers[i].Start(FROM_HERE, TimeDelta::FromMilliseconds(i * 10), | |
| &reset_helper, &ResetHelper::Reset); | |
| } | |
| OneShotTimerTester tester(nullptr, TimeDelta::FromMilliseconds(300)); | |
| tester.Start(); | |
| tester.WaitAndConfirmTimerFiredAfterDelay(); | |
| ASSERT_TRUE(target.signaled()); | |
| } | |
| class DelayTimerFatalTarget { | |
| public: | |
| void Signal() { | |
| ASSERT_TRUE(false); | |
| } | |
| }; | |
| void RunTest_DelayTimer_Deleted(MessageLoop::Type message_loop_type) { | |
| #if defined(STARBOARD) | |
| if (message_loop_type == MessageLoop::TYPE_UI) { | |
| return; | |
| } | |
| #endif | |
| MessageLoop loop(message_loop_type); | |
| DelayTimerFatalTarget target; | |
| { | |
| DelayTimer timer(FROM_HERE, TimeDelta::FromMilliseconds(50), &target, | |
| &DelayTimerFatalTarget::Signal); | |
| timer.Reset(); | |
| } | |
| // When the timer is deleted, the DelayTimerFatalTarget should never be | |
| // called. | |
| PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); | |
| } | |
| } // namespace | |
| //----------------------------------------------------------------------------- | |
| // Each test is run against each type of MessageLoop. That way we are sure | |
| // that timers work properly in all configurations. | |
| TEST(TimerTest, OneShotTimers) { | |
| for (int i = 0; i < kNumTestingMessageLoops; i++) { | |
| RunTest_OneShotTimers(testing_message_loops[i]); | |
| } | |
| } | |
| TEST(TimerTest, OneShotTimers_Cancel) { | |
| for (int i = 0; i < kNumTestingMessageLoops; i++) { | |
| RunTest_OneShotTimers_Cancel(testing_message_loops[i]); | |
| } | |
| } | |
| // If underline timer does not handle properly, we will crash or fail | |
| // in full page heap environment. | |
| TEST(TimerTest, OneShotSelfDeletingTimer) { | |
| for (int i = 0; i < kNumTestingMessageLoops; i++) { | |
| RunTest_OneShotSelfDeletingTimer(testing_message_loops[i]); | |
| } | |
| } | |
| TEST(TimerTest, OneShotTimer_CustomTaskRunner) { | |
| // A MessageLoop is required for the timer events on the other thread to | |
| // communicate back to the Timer under test. | |
| MessageLoop loop; | |
| Thread other_thread("OneShotTimer_CustomTaskRunner"); | |
| other_thread.Start(); | |
| WaitableEvent did_run(WaitableEvent::ResetPolicy::MANUAL, | |
| WaitableEvent::InitialState::NOT_SIGNALED); | |
| OneShotTimerTester f(&did_run); | |
| f.SetTaskRunner(other_thread.task_runner()); | |
| f.Start(); | |
| EXPECT_TRUE(f.IsRunning() || did_run.IsSignaled()); | |
| f.WaitAndConfirmTimerFiredAfterDelay(); | |
| EXPECT_TRUE(did_run.IsSignaled()); | |
| // |f| should already have communicated back to this |loop| before invoking | |
| // Run() and as such this thread should already be aware that |f| is no longer | |
| // running. | |
| EXPECT_TRUE(loop.IsIdleForTesting()); | |
| EXPECT_FALSE(f.IsRunning()); | |
| } | |
| TEST(TimerTest, OneShotTimerWithTickClock) { | |
| scoped_refptr<TestMockTimeTaskRunner> task_runner( | |
| new TestMockTimeTaskRunner(Time::Now(), TimeTicks::Now())); | |
| MessageLoop message_loop; | |
| message_loop.SetTaskRunner(task_runner); | |
| Receiver receiver; | |
| OneShotTimer timer(task_runner->GetMockTickClock()); | |
| timer.Start(FROM_HERE, TimeDelta::FromSeconds(1), | |
| BindOnce(&Receiver::OnCalled, Unretained(&receiver))); | |
| task_runner->FastForwardBy(TimeDelta::FromSeconds(1)); | |
| EXPECT_TRUE(receiver.WasCalled()); | |
| } | |
| TEST(TimerTest, RepeatingTimer) { | |
| for (int i = 0; i < kNumTestingMessageLoops; i++) { | |
| RunTest_RepeatingTimer(testing_message_loops[i], | |
| TimeDelta::FromMilliseconds(10)); | |
| } | |
| } | |
| TEST(TimerTest, RepeatingTimer_Cancel) { | |
| for (int i = 0; i < kNumTestingMessageLoops; i++) { | |
| RunTest_RepeatingTimer_Cancel(testing_message_loops[i], | |
| TimeDelta::FromMilliseconds(10)); | |
| } | |
| } | |
| TEST(TimerTest, RepeatingTimerZeroDelay) { | |
| for (int i = 0; i < kNumTestingMessageLoops; i++) { | |
| RunTest_RepeatingTimer(testing_message_loops[i], | |
| TimeDelta::FromMilliseconds(0)); | |
| } | |
| } | |
| TEST(TimerTest, RepeatingTimerZeroDelay_Cancel) { | |
| for (int i = 0; i < kNumTestingMessageLoops; i++) { | |
| RunTest_RepeatingTimer_Cancel(testing_message_loops[i], | |
| TimeDelta::FromMilliseconds(0)); | |
| } | |
| } | |
| TEST(TimerTest, RepeatingTimerWithTickClock) { | |
| scoped_refptr<TestMockTimeTaskRunner> task_runner( | |
| new TestMockTimeTaskRunner(Time::Now(), TimeTicks::Now())); | |
| MessageLoop message_loop; | |
| message_loop.SetTaskRunner(task_runner); | |
| Receiver receiver; | |
| const int expected_times_called = 10; | |
| RepeatingTimer timer(task_runner->GetMockTickClock()); | |
| timer.Start(FROM_HERE, TimeDelta::FromSeconds(1), | |
| BindRepeating(&Receiver::OnCalled, Unretained(&receiver))); | |
| task_runner->FastForwardBy(TimeDelta::FromSeconds(expected_times_called)); | |
| timer.Stop(); | |
| EXPECT_EQ(expected_times_called, receiver.TimesCalled()); | |
| } | |
| TEST(TimerTest, DelayTimer_NoCall) { | |
| for (int i = 0; i < kNumTestingMessageLoops; i++) { | |
| RunTest_DelayTimer_NoCall(testing_message_loops[i]); | |
| } | |
| } | |
| TEST(TimerTest, DelayTimer_OneCall) { | |
| for (int i = 0; i < kNumTestingMessageLoops; i++) { | |
| RunTest_DelayTimer_OneCall(testing_message_loops[i]); | |
| } | |
| } | |
| // It's flaky on the buildbot, http://crbug.com/25038. | |
| TEST(TimerTest, DISABLED_DelayTimer_Reset) { | |
| for (int i = 0; i < kNumTestingMessageLoops; i++) { | |
| RunTest_DelayTimer_Reset(testing_message_loops[i]); | |
| } | |
| } | |
| TEST(TimerTest, DelayTimer_Deleted) { | |
| for (int i = 0; i < kNumTestingMessageLoops; i++) { | |
| RunTest_DelayTimer_Deleted(testing_message_loops[i]); | |
| } | |
| } | |
| TEST(TimerTest, DelayTimerWithTickClock) { | |
| scoped_refptr<TestMockTimeTaskRunner> task_runner( | |
| new TestMockTimeTaskRunner(Time::Now(), TimeTicks::Now())); | |
| MessageLoop message_loop; | |
| message_loop.SetTaskRunner(task_runner); | |
| Receiver receiver; | |
| DelayTimer timer(FROM_HERE, TimeDelta::FromSeconds(1), &receiver, | |
| &Receiver::OnCalled, task_runner->GetMockTickClock()); | |
| task_runner->FastForwardBy(TimeDelta::FromMilliseconds(999)); | |
| EXPECT_FALSE(receiver.WasCalled()); | |
| timer.Reset(); | |
| task_runner->FastForwardBy(TimeDelta::FromMilliseconds(999)); | |
| EXPECT_FALSE(receiver.WasCalled()); | |
| timer.Reset(); | |
| task_runner->FastForwardBy(TimeDelta::FromSeconds(1)); | |
| EXPECT_TRUE(receiver.WasCalled()); | |
| } | |
| TEST(TimerTest, MessageLoopShutdown) { | |
| // This test is designed to verify that shutdown of the | |
| // message loop does not cause crashes if there were pending | |
| // timers not yet fired. It may only trigger exceptions | |
| // if debug heap checking is enabled. | |
| WaitableEvent did_run(WaitableEvent::ResetPolicy::MANUAL, | |
| WaitableEvent::InitialState::NOT_SIGNALED); | |
| { | |
| OneShotTimerTesterBase a(&did_run); | |
| OneShotTimerTesterBase b(&did_run); | |
| OneShotTimerTesterBase c(&did_run); | |
| OneShotTimerTesterBase d(&did_run); | |
| { | |
| MessageLoop loop; | |
| a.Start(); | |
| b.Start(); | |
| } // MessageLoop destructs by falling out of scope. | |
| } // OneShotTimers destruct. SHOULD NOT CRASH, of course. | |
| EXPECT_FALSE(did_run.IsSignaled()); | |
| } | |
| // Ref counted class which owns a Timer. The class passes a reference to itself | |
| // via the |user_task| parameter in Timer::Start(). |Timer::user_task_| might | |
| // end up holding the last reference to the class. | |
| class OneShotSelfOwningTimerTester | |
| : public RefCounted<OneShotSelfOwningTimerTester> { | |
| public: | |
| OneShotSelfOwningTimerTester() = default; | |
| void StartTimer() { | |
| // Start timer with long delay in order to test the timer getting destroyed | |
| // while a timer task is still pending. | |
| timer_.Start(FROM_HERE, TimeDelta::FromDays(1), | |
| BindOnce(&OneShotSelfOwningTimerTester::Run, this)); | |
| } | |
| private: | |
| friend class RefCounted<OneShotSelfOwningTimerTester>; | |
| ~OneShotSelfOwningTimerTester() = default; | |
| void Run() { | |
| ADD_FAILURE() << "Timer unexpectedly fired."; | |
| } | |
| OneShotTimer timer_; | |
| DISALLOW_COPY_AND_ASSIGN(OneShotSelfOwningTimerTester); | |
| }; | |
| TEST(TimerTest, MessageLoopShutdownSelfOwningTimer) { | |
| // This test verifies that shutdown of the message loop does not cause crashes | |
| // if there is a pending timer not yet fired and |Timer::user_task_| owns the | |
| // timer. The test may only trigger exceptions if debug heap checking is | |
| // enabled. | |
| MessageLoop loop; | |
| scoped_refptr<OneShotSelfOwningTimerTester> tester = | |
| new OneShotSelfOwningTimerTester(); | |
| std::move(tester)->StartTimer(); | |
| // |Timer::user_task_| owns sole reference to |tester|. | |
| // MessageLoop destructs by falling out of scope. SHOULD NOT CRASH. | |
| } | |
| void TimerTestCallback() { | |
| } | |
| TEST(TimerTest, NonRepeatIsRunning) { | |
| { | |
| MessageLoop loop; | |
| OneShotTimer timer; | |
| EXPECT_FALSE(timer.IsRunning()); | |
| timer.Start(FROM_HERE, TimeDelta::FromDays(1), | |
| BindOnce(&TimerTestCallback)); | |
| EXPECT_TRUE(timer.IsRunning()); | |
| timer.Stop(); | |
| EXPECT_FALSE(timer.IsRunning()); | |
| } | |
| { | |
| RetainingOneShotTimer timer; | |
| MessageLoop loop; | |
| EXPECT_FALSE(timer.IsRunning()); | |
| timer.Start(FROM_HERE, TimeDelta::FromDays(1), | |
| BindRepeating(&TimerTestCallback)); | |
| EXPECT_TRUE(timer.IsRunning()); | |
| timer.Stop(); | |
| EXPECT_FALSE(timer.IsRunning()); | |
| ASSERT_FALSE(timer.user_task().is_null()); | |
| timer.Reset(); | |
| EXPECT_TRUE(timer.IsRunning()); | |
| } | |
| } | |
| TEST(TimerTest, NonRepeatMessageLoopDeath) { | |
| OneShotTimer timer; | |
| { | |
| MessageLoop loop; | |
| EXPECT_FALSE(timer.IsRunning()); | |
| timer.Start(FROM_HERE, TimeDelta::FromDays(1), | |
| BindOnce(&TimerTestCallback)); | |
| EXPECT_TRUE(timer.IsRunning()); | |
| } | |
| EXPECT_FALSE(timer.IsRunning()); | |
| } | |
| TEST(TimerTest, RetainRepeatIsRunning) { | |
| MessageLoop loop; | |
| RepeatingTimer timer(FROM_HERE, TimeDelta::FromDays(1), | |
| BindRepeating(&TimerTestCallback)); | |
| EXPECT_FALSE(timer.IsRunning()); | |
| timer.Reset(); | |
| EXPECT_TRUE(timer.IsRunning()); | |
| timer.Stop(); | |
| EXPECT_FALSE(timer.IsRunning()); | |
| timer.Reset(); | |
| EXPECT_TRUE(timer.IsRunning()); | |
| } | |
| TEST(TimerTest, RetainNonRepeatIsRunning) { | |
| MessageLoop loop; | |
| RetainingOneShotTimer timer(FROM_HERE, TimeDelta::FromDays(1), | |
| BindRepeating(&TimerTestCallback)); | |
| EXPECT_FALSE(timer.IsRunning()); | |
| timer.Reset(); | |
| EXPECT_TRUE(timer.IsRunning()); | |
| timer.Stop(); | |
| EXPECT_FALSE(timer.IsRunning()); | |
| timer.Reset(); | |
| EXPECT_TRUE(timer.IsRunning()); | |
| } | |
| //----------------------------------------------------------------------------- | |
| namespace { | |
| bool g_callback_happened1 = false; | |
| bool g_callback_happened2 = false; | |
| void ClearAllCallbackHappened() { | |
| g_callback_happened1 = false; | |
| g_callback_happened2 = false; | |
| } | |
| void SetCallbackHappened1() { | |
| g_callback_happened1 = true; | |
| RunLoop::QuitCurrentWhenIdleDeprecated(); | |
| } | |
| void SetCallbackHappened2() { | |
| g_callback_happened2 = true; | |
| RunLoop::QuitCurrentWhenIdleDeprecated(); | |
| } | |
| } // namespace | |
| TEST(TimerTest, ContinuationStopStart) { | |
| { | |
| ClearAllCallbackHappened(); | |
| MessageLoop loop; | |
| OneShotTimer timer; | |
| timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(10), | |
| BindOnce(&SetCallbackHappened1)); | |
| timer.Stop(); | |
| timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(40), | |
| BindOnce(&SetCallbackHappened2)); | |
| RunLoop().Run(); | |
| EXPECT_FALSE(g_callback_happened1); | |
| EXPECT_TRUE(g_callback_happened2); | |
| } | |
| } | |
| TEST(TimerTest, ContinuationReset) { | |
| { | |
| ClearAllCallbackHappened(); | |
| MessageLoop loop; | |
| OneShotTimer timer; | |
| timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(10), | |
| BindOnce(&SetCallbackHappened1)); | |
| timer.Reset(); | |
| // // Since Reset happened before task ran, the user_task must not be | |
| // cleared: ASSERT_FALSE(timer.user_task().is_null()); | |
| RunLoop().Run(); | |
| EXPECT_TRUE(g_callback_happened1); | |
| } | |
| } | |
| namespace { | |
| // Fixture for tests requiring ScopedTaskEnvironment. Includes a WaitableEvent | |
| // so that cases may Wait() on one thread and Signal() (explicitly, or | |
| // implicitly via helper methods) on another. | |
| class TimerSequenceTest : public testing::Test { | |
| public: | |
| TimerSequenceTest() | |
| : recorder_for_testing_(StatisticsRecorder::CreateTemporaryForTesting()), | |
| event_(WaitableEvent::ResetPolicy::AUTOMATIC, | |
| WaitableEvent::InitialState::NOT_SIGNALED) {} | |
| // Block until Signal() is called on another thread. | |
| void Wait() { event_.Wait(); } | |
| void Signal() { event_.Signal(); } | |
| // Helper to augment a task with a subsequent call to Signal(). | |
| OnceClosure TaskWithSignal(OnceClosure task) { | |
| return BindOnce(&TimerSequenceTest::RunTaskAndSignal, Unretained(this), | |
| std::move(task)); | |
| } | |
| // Create the timer. | |
| void CreateTimer() { timer_.reset(new OneShotTimer); } | |
| // Schedule an event on the timer. | |
| void StartTimer(TimeDelta delay, OnceClosure task) { | |
| timer_->Start(FROM_HERE, delay, std::move(task)); | |
| } | |
| void SetTaskRunnerForTimer(scoped_refptr<SequencedTaskRunner> task_runner) { | |
| timer_->SetTaskRunner(std::move(task_runner)); | |
| } | |
| // Tell the timer to abandon the task. | |
| void AbandonTask() { | |
| EXPECT_TRUE(timer_->IsRunning()); | |
| // Reset() to call Timer::AbandonScheduledTask() | |
| timer_->Reset(); | |
| EXPECT_TRUE(timer_->IsRunning()); | |
| timer_->Stop(); | |
| EXPECT_FALSE(timer_->IsRunning()); | |
| } | |
| static void VerifyAffinity(const SequencedTaskRunner* task_runner) { | |
| EXPECT_TRUE(task_runner->RunsTasksInCurrentSequence()); | |
| } | |
| // Delete the timer. | |
| void DeleteTimer() { timer_.reset(); } | |
| private: | |
| void RunTaskAndSignal(OnceClosure task) { | |
| std::move(task).Run(); | |
| Signal(); | |
| } | |
| std::unique_ptr<StatisticsRecorder> recorder_for_testing_; | |
| base::test::ScopedTaskEnvironment scoped_task_environment_; | |
| WaitableEvent event_; | |
| std::unique_ptr<OneShotTimer> timer_; | |
| DISALLOW_COPY_AND_ASSIGN(TimerSequenceTest); | |
| }; | |
| } // namespace | |
| TEST_F(TimerSequenceTest, OneShotTimerTaskOnPoolSequence) { | |
| scoped_refptr<SequencedTaskRunner> task_runner = | |
| base::CreateSequencedTaskRunnerWithTraits({}); | |
| base::RunLoop run_loop_; | |
| // Timer is created on this thread. | |
| CreateTimer(); | |
| // Task will execute on a pool thread. | |
| SetTaskRunnerForTimer(task_runner); | |
| StartTimer(TimeDelta::FromMilliseconds(1), | |
| BindOnce(IgnoreResult(&SequencedTaskRunner::PostTask), | |
| SequencedTaskRunnerHandle::Get(), FROM_HERE, | |
| run_loop_.QuitClosure())); | |
| // Spin the loop so that the delayed task fires on it, which will forward it | |
| // to |task_runner|. And since the Timer's task is one that posts back to this | |
| // MessageLoop to quit, we finally unblock. | |
| run_loop_.Run(); | |
| // Timer will be destroyed on this thread. | |
| DeleteTimer(); | |
| } | |
| TEST_F(TimerSequenceTest, OneShotTimerUsedOnPoolSequence) { | |
| scoped_refptr<SequencedTaskRunner> task_runner = | |
| base::CreateSequencedTaskRunnerWithTraits({}); | |
| // Timer is created on this thread. | |
| CreateTimer(); | |
| // Task will be scheduled from a pool thread. | |
| task_runner->PostTask( | |
| FROM_HERE, | |
| BindOnce(&TimerSequenceTest::StartTimer, Unretained(this), | |
| TimeDelta::FromMilliseconds(1), | |
| BindOnce(&TimerSequenceTest::Signal, Unretained(this)))); | |
| Wait(); | |
| // Timer must be destroyed on pool thread, too. | |
| task_runner->PostTask(FROM_HERE, | |
| TaskWithSignal(BindOnce(&TimerSequenceTest::DeleteTimer, | |
| Unretained(this)))); | |
| Wait(); | |
| } | |
| TEST_F(TimerSequenceTest, OneShotTimerTwoSequencesAbandonTask) { | |
| scoped_refptr<SequencedTaskRunner> task_runner1 = | |
| base::CreateSequencedTaskRunnerWithTraits({}); | |
| scoped_refptr<SequencedTaskRunner> task_runner2 = | |
| base::CreateSequencedTaskRunnerWithTraits({}); | |
| // Create timer on sequence #1. | |
| task_runner1->PostTask( | |
| FROM_HERE, TaskWithSignal(BindOnce(&TimerSequenceTest::CreateTimer, | |
| Unretained(this)))); | |
| Wait(); | |
| // And tell it to execute on a different sequence (#2). | |
| task_runner1->PostTask( | |
| FROM_HERE, | |
| TaskWithSignal(BindOnce(&TimerSequenceTest::SetTaskRunnerForTimer, | |
| Unretained(this), task_runner2))); | |
| Wait(); | |
| // Task will be scheduled from sequence #1. | |
| task_runner1->PostTask( | |
| FROM_HERE, BindOnce(&TimerSequenceTest::StartTimer, Unretained(this), | |
| TimeDelta::FromHours(1), DoNothing().Once())); | |
| // Abandon task - must be called from scheduling sequence (#1). | |
| task_runner1->PostTask( | |
| FROM_HERE, TaskWithSignal(BindOnce(&TimerSequenceTest::AbandonTask, | |
| Unretained(this)))); | |
| Wait(); | |
| // Timer must be destroyed on the sequence it was scheduled from (#1). | |
| task_runner1->PostTask( | |
| FROM_HERE, TaskWithSignal(BindOnce(&TimerSequenceTest::DeleteTimer, | |
| Unretained(this)))); | |
| Wait(); | |
| } | |
| TEST_F(TimerSequenceTest, OneShotTimerUsedAndTaskedOnDifferentSequences) { | |
| scoped_refptr<SequencedTaskRunner> task_runner1 = | |
| base::CreateSequencedTaskRunnerWithTraits({}); | |
| scoped_refptr<SequencedTaskRunner> task_runner2 = | |
| base::CreateSequencedTaskRunnerWithTraits({}); | |
| // Create timer on sequence #1. | |
| task_runner1->PostTask( | |
| FROM_HERE, TaskWithSignal(BindOnce(&TimerSequenceTest::CreateTimer, | |
| Unretained(this)))); | |
| Wait(); | |
| // And tell it to execute on a different sequence (#2). | |
| task_runner1->PostTask( | |
| FROM_HERE, | |
| TaskWithSignal(BindOnce(&TimerSequenceTest::SetTaskRunnerForTimer, | |
| Unretained(this), task_runner2))); | |
| Wait(); | |
| // Task will be scheduled from sequence #1. | |
| task_runner1->PostTask( | |
| FROM_HERE, | |
| BindOnce(&TimerSequenceTest::StartTimer, Unretained(this), | |
| TimeDelta::FromMilliseconds(1), | |
| TaskWithSignal(BindOnce(&TimerSequenceTest::VerifyAffinity, | |
| Unretained(task_runner2.get()))))); | |
| Wait(); | |
| // Timer must be destroyed on the sequence it was scheduled from (#1). | |
| task_runner1->PostTask( | |
| FROM_HERE, TaskWithSignal(BindOnce(&TimerSequenceTest::DeleteTimer, | |
| Unretained(this)))); | |
| Wait(); | |
| } | |
| } // namespace base |