// Copyright (c) 2006-2008 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/message_loop.h" #include "base/task.h" #include "base/timer.h" #include "testing/gtest/include/gtest/gtest.h" namespace { class TimerTest : public testing::Test {}; // A base class timer task that sanity-checks timer functionality and counts // the number of times it has run. Handles all message loop and memory // management issues. class TimerTask : public Task { public: // Runs all timers to completion. This returns only after all timers have // finished firing. static void RunTimers(); // Creates a new timer. If |repeating| is true, the timer will repeat 10 // times before terminating. // // All timers are managed on the message loop of the thread that calls this // function the first time. TimerTask(int delay, bool repeating); virtual ~TimerTask(); int iterations() const { return iterations_; } const Timer* timer() const { return timer_; } // Resets the timer, if it exists. void Reset(); // Task virtual void Run(); protected: // Shuts down the message loop if necessary. static void QuitMessageLoop(); private: static MessageLoop* message_loop() { return MessageLoop::current(); } static int timer_count_; static bool loop_running_; bool timer_running_; int delay_; TimeTicks start_ticks_; int iterations_; Timer* timer_; }; // static void TimerTask::RunTimers() { if (timer_count_ && !loop_running_) { loop_running_ = true; message_loop()->Run(); } } TimerTask::TimerTask(int delay, bool repeating) : timer_running_(false), delay_(delay), start_ticks_(TimeTicks::Now()), iterations_(0), timer_(NULL) { Reset(); // This will just set up the variables to indicate we have a // running timer. timer_ = message_loop()->timer_manager()->StartTimer(delay, this, repeating); } TimerTask::~TimerTask() { if (timer_) { message_loop()->timer_manager()->StopTimer(timer_); delete timer_; } if (timer_running_) { timer_running_ = false; if (--timer_count_ <= 0) QuitMessageLoop(); } } void TimerTask::Reset() { if (!timer_running_) { timer_running_ = true; ++timer_count_; } if (timer_) { start_ticks_ = TimeTicks::Now(); message_loop()->timer_manager()->ResetTimer(timer_); } } void TimerTask::Run() { ++iterations_; // Test that we fired on or after the delay, not before. const TimeTicks ticks = TimeTicks::Now(); EXPECT_LE(delay_, (ticks - start_ticks_).InMilliseconds()); // Note: Add the delay rather than using the ticks recorded. // Repeating timers have already started ticking before // this callback; we pretend they started *now*, then // it might seem like they fire early, when they do not. start_ticks_ += TimeDelta::FromMilliseconds(delay_); // If we're done running, shut down the message loop. if (timer_->repeating() && (iterations_ < 10)) return; // Iterate 10 times before terminating. message_loop()->timer_manager()->StopTimer(timer_); timer_running_ = false; if (--timer_count_ <= 0) QuitMessageLoop(); } // static void TimerTask::QuitMessageLoop() { if (loop_running_) { message_loop()->Quit(); loop_running_ = false; } } int TimerTask::timer_count_ = 0; bool TimerTask::loop_running_ = false; // A task that deletes itself when run. class DeletingTask : public TimerTask { public: DeletingTask(int delay, bool repeating) : TimerTask(delay, repeating) { } // Task virtual void Run(); }; void DeletingTask::Run() { delete this; // Can't call TimerTask::Run() here, we've destroyed ourselves. } // A class that resets another TimerTask when run. class ResettingTask : public TimerTask { public: ResettingTask(int delay, bool repeating, TimerTask* task) : TimerTask(delay, repeating), task_(task) { } virtual void Run(); private: TimerTask* task_; }; void ResettingTask::Run() { task_->Reset(); TimerTask::Run(); } // A class that quits the message loop when run. class QuittingTask : public TimerTask { public: QuittingTask(int delay, bool repeating) : TimerTask(delay, repeating) { } virtual void Run(); }; void QuittingTask::Run() { QuitMessageLoop(); TimerTask::Run(); } void RunTimerTest() { // Make sure oneshot timers work correctly. TimerTask task1(100, false); TimerTask::RunTimers(); EXPECT_EQ(1, task1.iterations()); // Make sure repeating timers work correctly. TimerTask task2(10, true); TimerTask task3(100, true); TimerTask::RunTimers(); EXPECT_EQ(10, task2.iterations()); EXPECT_EQ(10, task3.iterations()); } //----------------------------------------------------------------------------- // The timer test cases: void RunTest_TimerComparison(MessageLoop::Type message_loop_type) { MessageLoop loop(message_loop_type); // Make sure TimerComparison sorts correctly. const TimerTask task1(10, false); const Timer* timer1 = task1.timer(); const TimerTask task2(200, false); const Timer* timer2 = task2.timer(); TimerComparison comparison; EXPECT_FALSE(comparison(timer1, timer2)); EXPECT_TRUE(comparison(timer2, timer1)); } void RunTest_BasicTimer(MessageLoop::Type message_loop_type) { MessageLoop loop(message_loop_type); RunTimerTest(); } void RunTest_BrokenTimer(MessageLoop::Type message_loop_type) { MessageLoop loop(message_loop_type); // Simulate faulty early-firing timers. The tasks in RunTimerTest should // nevertheless be invoked after their specified delays, regardless of when // WM_TIMER fires. TimerManager* manager = MessageLoop::current()->timer_manager(); manager->set_use_broken_delay(true); RunTimerTest(); manager->set_use_broken_delay(false); } void RunTest_DeleteFromRun(MessageLoop::Type message_loop_type) { MessageLoop loop(message_loop_type); // Make sure TimerManager correctly handles a Task that deletes itself when // run. new DeletingTask(50, true); TimerTask timer_task(150, false); new DeletingTask(250, true); TimerTask::RunTimers(); EXPECT_EQ(1, timer_task.iterations()); } void RunTest_Reset(MessageLoop::Type message_loop_type) { MessageLoop loop(message_loop_type); // Make sure resetting a timer after it has fired works. TimerTask timer_task1(250, false); TimerTask timer_task2(100, true); ResettingTask resetting_task1(600, false, &timer_task1); TimerTask::RunTimers(); EXPECT_EQ(2, timer_task1.iterations()); EXPECT_EQ(10, timer_task2.iterations()); // Make sure resetting a timer before it has fired works. This will reset // two timers, then stop the message loop between when they should have // finally fired. TimerTask timer_task3(100, false); TimerTask timer_task4(600, false); ResettingTask resetting_task3(50, false, &timer_task3); ResettingTask resetting_task4(50, false, &timer_task4); QuittingTask quitting_task(300, false); TimerTask::RunTimers(); EXPECT_EQ(1, timer_task3.iterations()); EXPECT_EQ(0, timer_task4.iterations()); } void RunTest_FifoOrder(MessageLoop::Type message_loop_type) { MessageLoop loop(message_loop_type); // Creating timers with the same timeout should // always compare to result in FIFO ordering. // Derive from the timer so that we can set it's fire time. // We have to do this, because otherwise, it's possible for // two timers, created back to back, to have different times, // and in that case, we aren't really testing what we want // to test! class MockTimer : public Timer { public: MockTimer(int delay) : Timer(delay, NULL, false) {} void set_fire_time(const Time& t) { fire_time_ = t; } }; class MockTimerManager : public TimerManager { public: MockTimerManager() : TimerManager(MessageLoop::current()) { } // Pops the most-recent to fire timer and returns its timer id. // Returns -1 if there are no timers in the list. int pop() { int rv = -1; Timer* top = PeekTopTimer(); if (top) { rv = top->id(); StopTimer(top); delete top; } return rv; } }; MockTimer t1(0); MockTimer t2(0); t2.set_fire_time(t1.fire_time()); TimerComparison comparison; EXPECT_TRUE(comparison(&t2, &t1)); // Issue a tight loop of timers; most will have the // same timestamp; some will not. Either way, since // all are created with delay(0), the second timer // must always be greater than the first. Then, pop // all the timers and verify that it's a FIFO list. MockTimerManager manager; const int kNumTimers = 1024; for (int i=0; i < kNumTimers; i++) manager.StartTimer(0, NULL, false); int last_id = -1; int new_id = 0; while((new_id = manager.pop()) > 0) EXPECT_GT(new_id, last_id); } } // namespace //----------------------------------------------------------------------------- // Each test is run against each type of MessageLoop. That way we are sure // that timers work properly in all configurations. TEST(TimerTest, TimerComparison) { RunTest_TimerComparison(MessageLoop::TYPE_DEFAULT); RunTest_TimerComparison(MessageLoop::TYPE_UI); RunTest_TimerComparison(MessageLoop::TYPE_IO); } TEST(TimerTest, BasicTimer) { RunTest_BasicTimer(MessageLoop::TYPE_DEFAULT); RunTest_BasicTimer(MessageLoop::TYPE_UI); RunTest_BasicTimer(MessageLoop::TYPE_IO); } TEST(TimerTest, BrokenTimer) { RunTest_BrokenTimer(MessageLoop::TYPE_DEFAULT); RunTest_BrokenTimer(MessageLoop::TYPE_UI); RunTest_BrokenTimer(MessageLoop::TYPE_IO); } TEST(TimerTest, DeleteFromRun) { RunTest_DeleteFromRun(MessageLoop::TYPE_DEFAULT); RunTest_DeleteFromRun(MessageLoop::TYPE_UI); RunTest_DeleteFromRun(MessageLoop::TYPE_IO); } TEST(TimerTest, Reset) { RunTest_Reset(MessageLoop::TYPE_DEFAULT); RunTest_Reset(MessageLoop::TYPE_UI); RunTest_Reset(MessageLoop::TYPE_IO); } TEST(TimerTest, FifoOrder) { RunTest_FifoOrder(MessageLoop::TYPE_DEFAULT); RunTest_FifoOrder(MessageLoop::TYPE_UI); RunTest_FifoOrder(MessageLoop::TYPE_IO); }