Make SequencedWorkerPool a TaskRunner

Add basic tests for TaskRunner implementations, and
instantiate it for SequencedWorkerPool.

BUG=114329,114327
TEST=

Review URL: https://chromiumcodereview.appspot.com/9401032

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@124087 0039d316-1c4b-4281-b951-d872f2087c98


CrOS-Libchrome-Original-Commit: 506fc2356fd959269b75c4ffe817f669ca5fffc2
diff --git a/base/base.gyp b/base/base.gyp
index bb85d8a..b5e4c71 100644
--- a/base/base.gyp
+++ b/base/base.gyp
@@ -223,6 +223,8 @@
         'sys_string_conversions_mac_unittest.mm',
         'sys_string_conversions_unittest.cc',
         'system_monitor/system_monitor_unittest.cc',
+        'task_runner_test_template.cc',
+        'task_runner_test_template.h',
         'template_util_unittest.cc',
         'test/trace_event_analyzer_unittest.cc',
         'threading/non_thread_safe_unittest.cc',
diff --git a/base/task_runner_test_template.cc b/base/task_runner_test_template.cc
new file mode 100644
index 0000000..064eac3
--- /dev/null
+++ b/base/task_runner_test_template.cc
@@ -0,0 +1,23 @@
+// 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/task_runner_test_template.h"
+
+namespace base {
+
+TaskTracker::TaskTracker() {}
+
+TaskTracker::~TaskTracker() {}
+
+void TaskTracker::RunTask(int i) {
+  AutoLock lock(task_run_counts_lock_);
+  ++task_run_counts_[i];
+}
+
+std::map<int, int> TaskTracker::GetTaskRunCounts() const {
+  AutoLock lock(task_run_counts_lock_);
+  return task_run_counts_;
+}
+
+}  // namespace base
diff --git a/base/task_runner_test_template.h b/base/task_runner_test_template.h
new file mode 100644
index 0000000..012fa88
--- /dev/null
+++ b/base/task_runner_test_template.h
@@ -0,0 +1,157 @@
+// 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.
+
+// This class defines tests that implementations of TaskRunner should
+// pass in order to be conformant.  Here's how you use it to test your
+// implementation.
+//
+// Say your class is called MyTaskRunner.  Then you need to define a
+// class called MyTaskRunnerTestDelegate in my_task_runner_unittest.cc
+// like this:
+//
+//   class MyTaskRunnerTestDelegate {
+//    public:
+//     // Tasks posted to the task runner after this and before
+//     // StopTaskRunner() is called is called should run successfully.
+//     void StartTaskRunner() {
+//       ...
+//     }
+//
+//     // Should return the task runner implementation.  Only called
+//     // after StartTaskRunner and before StopTaskRunner.
+//     scoped_refptr<MyTaskRunner> GetTaskRunner() {
+//       ...
+//     }
+//
+//     // Stop the task runner and make sure all tasks posted before
+//     // this is called are run.
+//     void StopTaskRunner() {
+//       ...
+//     }
+//
+//     // Returns whether or not the task runner obeys non-zero delays.
+//     bool TaskRunnerHandlesNonZeroDelays() const {
+//       return true;
+//     }
+//   };
+//
+// The TaskRunnerTest test harness will have a member variable of
+// this delegate type and will call its functions in the various
+// tests.
+//
+// Then you simply #include this file as well as gtest.h and add the
+// following statement to my_task_runner_unittest.cc:
+//
+//   INSTANTIATE_TYPED_TEST_CASE_P(
+//       MyTaskRunner, TaskRunnerTest, MyTaskRunnerTestDelegate);
+//
+// Easy!
+
+#ifndef BASE_TASK_RUNNER_TEST_TEMPLATE_H_
+#define BASE_TASK_RUNNER_TEST_TEMPLATE_H_
+#pragma once
+
+#include <cstddef>
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner.h"
+#include "base/tracked_objects.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+// Utility class used in the tests below.
+class TaskTracker : public RefCountedThreadSafe<TaskTracker> {
+ public:
+  TaskTracker();
+
+  void RunTask(int i);
+
+  std::map<int, int> GetTaskRunCounts() const;
+
+ private:
+  friend class RefCountedThreadSafe<TaskTracker>;
+
+  ~TaskTracker();
+
+  mutable Lock task_run_counts_lock_;
+  std::map<int, int> task_run_counts_;
+
+  DISALLOW_COPY_AND_ASSIGN(TaskTracker);
+};
+
+template <typename TaskRunnerTestDelegate>
+class TaskRunnerTest : public testing::Test {
+ protected:
+  TaskRunnerTest() : task_tracker_(new TaskTracker()) {}
+
+  const scoped_refptr<TaskTracker> task_tracker_;
+  TaskRunnerTestDelegate delegate_;
+};
+
+TYPED_TEST_CASE_P(TaskRunnerTest);
+
+// We can't really test much, since TaskRunner provides very few
+// guarantees.
+
+// Post a bunch of tasks to the task runner.  They should all
+// complete.
+TYPED_TEST_P(TaskRunnerTest, Basic) {
+  std::map<int, int> expected_task_run_counts;
+
+  this->delegate_.StartTaskRunner();
+  scoped_refptr<TaskRunner> task_runner = this->delegate_.GetTaskRunner();
+  // Post each ith task i+1 times.
+  for (int i = 0; i < 20; ++i) {
+    Closure task = Bind(&TaskTracker::RunTask, this->task_tracker_, i);
+    for (int j = 0; j < i + 1; ++j) {
+      task_runner->PostTask(FROM_HERE, task);
+      ++expected_task_run_counts[i];
+    }
+  }
+  this->delegate_.StopTaskRunner();
+
+  EXPECT_EQ(expected_task_run_counts,
+            this->task_tracker_->GetTaskRunCounts());
+}
+
+// Post a bunch of delayed tasks to the task runner.  They should all
+// complete.
+TYPED_TEST_P(TaskRunnerTest, Delayed) {
+  if (!this->delegate_.TaskRunnerHandlesNonZeroDelays()) {
+    DLOG(INFO) << "This TaskRunner doesn't handle non-zero delays; skipping";
+    return;
+  }
+
+  std::map<int, int> expected_task_run_counts;
+
+  this->delegate_.StartTaskRunner();
+  scoped_refptr<TaskRunner> task_runner = this->delegate_.GetTaskRunner();
+  // Post each ith task i+1 times with delays from 0-i.
+  for (int i = 0; i < 20; ++i) {
+    Closure task = Bind(&TaskTracker::RunTask, this->task_tracker_, i);
+    for (int j = 0; j < i + 1; ++j) {
+      task_runner->PostDelayedTask(FROM_HERE, task, j);
+      ++expected_task_run_counts[i];
+    }
+  }
+  this->delegate_.StopTaskRunner();
+
+  EXPECT_EQ(expected_task_run_counts,
+            this->task_tracker_->GetTaskRunCounts());
+}
+
+// TODO(akalin): Add test to verify RunsTaskOnCurrentThread() returns
+// true for tasks runs on the TaskRunner and returns false on a
+// separate PlatformThread.
+
+REGISTER_TYPED_TEST_CASE_P(TaskRunnerTest, Basic, Delayed);
+
+}  // namespace base
+
+#endif  //#define BASE_TASK_RUNNER_TEST_TEMPLATE_H_
diff --git a/base/threading/sequenced_worker_pool.cc b/base/threading/sequenced_worker_pool.cc
index 7679c15..48bb2eb 100644
--- a/base/threading/sequenced_worker_pool.cc
+++ b/base/threading/sequenced_worker_pool.cc
@@ -689,6 +689,31 @@
                           from_here, task);
 }
 
+bool SequencedWorkerPool::PostDelayedTask(
+    const tracked_objects::Location& from_here,
+    const Closure& task,
+    int64 delay_ms) {
+  // TODO(akalin): Add support for non-zero delays.
+  DCHECK_EQ(delay_ms, 0);
+  return PostWorkerTask(from_here, task);
+}
+
+bool SequencedWorkerPool::PostDelayedTask(
+    const tracked_objects::Location& from_here,
+    const Closure& task,
+    TimeDelta delay) {
+  // TODO(akalin): Add support for non-zero delays.
+  DCHECK_EQ(delay.InMillisecondsRoundedUp(), 0);
+  return PostWorkerTask(from_here, task);
+}
+
+bool SequencedWorkerPool::RunsTasksOnCurrentThread() const {
+  // TODO(akalin): Keep track of the thread IDs of our worker threads
+  // and use those to implement this function.
+  NOTREACHED();
+  return true;
+}
+
 void SequencedWorkerPool::FlushForTesting() {
   inner_->FlushForTesting();
 }
diff --git a/base/threading/sequenced_worker_pool.h b/base/threading/sequenced_worker_pool.h
index c840f07..de31828 100644
--- a/base/threading/sequenced_worker_pool.h
+++ b/base/threading/sequenced_worker_pool.h
@@ -14,6 +14,7 @@
 #include "base/callback_forward.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_ptr.h"
+#include "base/task_runner.h"
 
 namespace tracked_objects {
 class Location;
@@ -51,8 +52,10 @@
 // not enforce shutdown semantics or allow us to specify how many worker
 // threads to run. For the typical use case of random background work, we don't
 // necessarily want to be super aggressive about creating threads.
-class BASE_EXPORT SequencedWorkerPool
-    : public RefCountedThreadSafe<SequencedWorkerPool> {
+//
+// Note that SequencedWorkerPool is RefCountedThreadSafe (inherited
+// from TaskRunner).
+class BASE_EXPORT SequencedWorkerPool : public TaskRunner {
  public:
   // Defines what should happen to a task posted to the worker pool on shutdown.
   enum WorkerShutdown {
@@ -191,6 +194,15 @@
       const Closure& task,
       WorkerShutdown shutdown_behavior);
 
+  // TaskRunner implementation.  Forwards to PostWorkerTask().
+  virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
+                               const Closure& task,
+                               int64 delay_ms) OVERRIDE;
+  virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
+                               const Closure& task,
+                               TimeDelta delay) OVERRIDE;
+  virtual bool RunsTasksOnCurrentThread() const OVERRIDE;
+
   // Blocks until all pending tasks are complete. This should only be called in
   // unit tests when you want to validate something that should have happened.
   //
@@ -210,14 +222,15 @@
   // and ownership of the pointer is kept with the caller.
   void SetTestingObserver(TestingObserver* observer);
 
+ protected:
+  virtual ~SequencedWorkerPool();
+
  private:
   friend class RefCountedThreadSafe<SequencedWorkerPool>;
 
   class Inner;
   class Worker;
 
-  ~SequencedWorkerPool();
-
   // Avoid pulling in too many headers by putting everything into
   // |inner_|.
   const scoped_ptr<Inner> inner_;
diff --git a/base/threading/sequenced_worker_pool_unittest.cc b/base/threading/sequenced_worker_pool_unittest.cc
index a7e9e93..02cb1c4 100644
--- a/base/threading/sequenced_worker_pool_unittest.cc
+++ b/base/threading/sequenced_worker_pool_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/synchronization/condition_variable.h"
 #include "base/synchronization/lock.h"
+#include "base/task_runner_test_template.h"
 #include "base/threading/platform_thread.h"
 #include "base/threading/sequenced_worker_pool.h"
 #include "base/tracked_objects.h"
@@ -223,8 +224,6 @@
   blocker->Unblock(threads_to_awake);
 }
 
-}  // namespace
-
 // Tests that same-named tokens have the same ID.
 TEST_F(SequencedWorkerPoolTest, NamedTokens) {
   const std::string name1("hello");
@@ -403,4 +402,40 @@
   EXPECT_EQ(1u, result.size());
 }
 
+class SequencedWorkerPoolTaskRunnerTestDelegate {
+ public:
+  SequencedWorkerPoolTaskRunnerTestDelegate() {}
+
+  ~SequencedWorkerPoolTaskRunnerTestDelegate() {}
+
+  void StartTaskRunner() {
+    worker_pool_ =
+        new SequencedWorkerPool(10, "SequencedWorkerPoolTaskRunnerTest");
+  }
+
+  scoped_refptr<SequencedWorkerPool> GetTaskRunner() {
+    return worker_pool_;
+  }
+
+  void StopTaskRunner() {
+    worker_pool_->Shutdown();
+    worker_pool_ = NULL;
+  }
+
+  bool TaskRunnerHandlesNonZeroDelays() const {
+    // TODO(akalin): Set this to true once SequencedWorkerPool handles
+    // non-zero delays.
+    return false;
+  }
+
+ private:
+  scoped_refptr<SequencedWorkerPool> worker_pool_;
+};
+
+INSTANTIATE_TYPED_TEST_CASE_P(
+    SequencedWorkerPool, TaskRunnerTest,
+    SequencedWorkerPoolTaskRunnerTestDelegate);
+
+}  // namespace
+
 }  // namespace base