summaryrefslogtreecommitdiffstats
path: root/base/timer_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'base/timer_unittest.cc')
-rw-r--r--base/timer_unittest.cc332
1 files changed, 332 insertions, 0 deletions
diff --git a/base/timer_unittest.cc b/base/timer_unittest.cc
new file mode 100644
index 0000000..814836b
--- /dev/null
+++ b/base/timer_unittest.cc
@@ -0,0 +1,332 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#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() {
+ static MessageLoop* loop = MessageLoop::current();
+ return loop;
+ }
+
+ 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());
+}
+
+TEST(TimerTest, TimerComparison) {
+ // 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));
+}
+
+TEST(TimerTest, TimerCase) {
+ RunTimerTest();
+}
+
+TEST(TimerTest, BrokenTimerCase) {
+ // 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);
+}
+
+TEST(TimerTest, DeleteFromRun) {
+ // Make sure TimerManager correctly handles a Task that deletes itself when
+ // run.
+ DeletingTask* deleting_task1 = new DeletingTask(50, true);
+ TimerTask timer_task(150, false);
+ DeletingTask* deleting_task2 = new DeletingTask(250, true);
+ TimerTask::RunTimers();
+ EXPECT_EQ(1, timer_task.iterations());
+}
+
+TEST(TimerTest, Reset) {
+ // 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());
+}
+
+TEST(TimerTest, FifoOrder) {
+ // 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:
+ // 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++)
+ Timer* timer = 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);
+} \ No newline at end of file