| // 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 SequencedTaskRunner should |
| // pass in order to be conformant. See task_runner_test_template.h for a |
| // description of how to use the constructs in this file; these work the same. |
| |
| #ifndef BASE_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_ |
| #define BASE_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_ |
| |
| #include <cstddef> |
| #include <iosfwd> |
| #include <vector> |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/synchronization/lock.h" |
| #include "base/time.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace base { |
| |
| namespace internal { |
| |
| struct TaskEvent { |
| enum Type { POST, START, END }; |
| TaskEvent(int i, Type type); |
| int i; |
| Type type; |
| }; |
| |
| // Utility class used in the tests below. |
| class SequencedTaskTracker : public RefCountedThreadSafe<SequencedTaskTracker> { |
| public: |
| SequencedTaskTracker(); |
| |
| // Posts the non-nestable task |task|, and records its post event. |
| void PostWrappedNonNestableTask( |
| const scoped_refptr<SequencedTaskRunner>& task_runner, |
| const Closure& task); |
| |
| // Posts the nestable task |task|, and records its post event. |
| void PostWrappedNestableTask( |
| const scoped_refptr<SequencedTaskRunner>& task_runner, |
| const Closure& task); |
| |
| // Posts the delayed non-nestable task |task|, and records its post event. |
| void PostWrappedDelayedNonNestableTask( |
| const scoped_refptr<SequencedTaskRunner>& task_runner, |
| const Closure& task, |
| TimeDelta delay); |
| |
| // Posts |task_count| non-nestable tasks. |
| void PostNonNestableTasks( |
| const scoped_refptr<SequencedTaskRunner>& task_runner, |
| int task_count); |
| |
| const std::vector<TaskEvent>& GetTaskEvents() const; |
| |
| private: |
| friend class RefCountedThreadSafe<SequencedTaskTracker>; |
| |
| ~SequencedTaskTracker(); |
| |
| // A task which runs |task|, recording the start and end events. |
| void RunTask(const Closure& task, int task_i); |
| |
| // Records a post event for task |i|. The owner is expected to be holding |
| // |lock_| (unlike |TaskStarted| and |TaskEnded|). |
| void TaskPosted(int i); |
| |
| // Records a start event for task |i|. |
| void TaskStarted(int i); |
| |
| // Records a end event for task |i|. |
| void TaskEnded(int i); |
| |
| // Protects events_ and next_post_i_. |
| Lock lock_; |
| |
| // The events as they occurred for each task (protected by lock_). |
| std::vector<TaskEvent> events_; |
| |
| // The ordinal to be used for the next task-posting task (protected by |
| // lock_). |
| int next_post_i_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SequencedTaskTracker); |
| }; |
| |
| void PrintTo(const TaskEvent& event, std::ostream* os); |
| |
| // Checks the non-nestable task invariants for all tasks in |events|. |
| // |
| // The invariants are: |
| // 1) Events started and ended in the same order that they were posted. |
| // 2) Events for an individual tasks occur in the order {POST, START, END}, |
| // and there is only one instance of each event type for a task. |
| // 3) The only events between a task's START and END events are the POSTs of |
| // other tasks. I.e. tasks were run sequentially, not interleaved. |
| ::testing::AssertionResult CheckNonNestableInvariants( |
| const std::vector<TaskEvent>& events, |
| int task_count); |
| |
| } // namespace internal |
| |
| template <typename TaskRunnerTestDelegate> |
| class SequencedTaskRunnerTest : public testing::Test { |
| protected: |
| SequencedTaskRunnerTest() |
| : task_tracker_(new internal::SequencedTaskTracker()) {} |
| |
| const scoped_refptr<internal::SequencedTaskTracker> task_tracker_; |
| TaskRunnerTestDelegate delegate_; |
| }; |
| |
| TYPED_TEST_CASE_P(SequencedTaskRunnerTest); |
| |
| // This test posts N non-nestable tasks in sequence, and expects them to run |
| // in FIFO order, with no part of any two tasks' execution |
| // overlapping. I.e. that each task starts only after the previously-posted |
| // one has finished. |
| TYPED_TEST_P(SequencedTaskRunnerTest, SequentialNonNestable) { |
| const int kTaskCount = 1000; |
| |
| this->delegate_.StartTaskRunner(); |
| const scoped_refptr<SequencedTaskRunner> task_runner = |
| this->delegate_.GetTaskRunner(); |
| |
| this->task_tracker_->PostWrappedNonNestableTask( |
| task_runner, Bind(&PlatformThread::Sleep, TimeDelta::FromSeconds(1))); |
| for (int i = 1; i < kTaskCount; ++i) { |
| this->task_tracker_->PostWrappedNonNestableTask(task_runner, Closure()); |
| } |
| |
| this->delegate_.StopTaskRunner(); |
| |
| EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), |
| kTaskCount)); |
| } |
| |
| // This test posts N nestable tasks in sequence. It has the same expectations |
| // as SequentialNonNestable because even though the tasks are nestable, they |
| // will not be run nestedly in this case. |
| TYPED_TEST_P(SequencedTaskRunnerTest, SequentialNestable) { |
| const int kTaskCount = 1000; |
| |
| this->delegate_.StartTaskRunner(); |
| const scoped_refptr<SequencedTaskRunner> task_runner = |
| this->delegate_.GetTaskRunner(); |
| |
| this->task_tracker_->PostWrappedNestableTask( |
| task_runner, |
| Bind(&PlatformThread::Sleep, TimeDelta::FromSeconds(1))); |
| for (int i = 1; i < kTaskCount; ++i) { |
| this->task_tracker_->PostWrappedNestableTask(task_runner, Closure()); |
| } |
| |
| this->delegate_.StopTaskRunner(); |
| |
| EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), |
| kTaskCount)); |
| } |
| |
| // This test posts non-nestable tasks in order of increasing delay, and checks |
| // that that the tasks are run in FIFO order and that there is no execution |
| // overlap whatsoever between any two tasks. |
| TYPED_TEST_P(SequencedTaskRunnerTest, SequentialDelayedNonNestable) { |
| // TODO(akalin): Remove this check (http://crbug.com/149144). |
| if (!this->delegate_.TaskRunnerHandlesNonZeroDelays()) { |
| DLOG(INFO) << "This SequencedTaskRunner doesn't handle " |
| "non-zero delays; skipping"; |
| return; |
| } |
| |
| const int kTaskCount = 20; |
| const int kDelayIncrementMs = 50; |
| |
| this->delegate_.StartTaskRunner(); |
| const scoped_refptr<SequencedTaskRunner> task_runner = |
| this->delegate_.GetTaskRunner(); |
| |
| for (int i = 0; i < kTaskCount; ++i) { |
| this->task_tracker_->PostWrappedDelayedNonNestableTask( |
| task_runner, |
| Closure(), |
| TimeDelta::FromMilliseconds(kDelayIncrementMs * i)); |
| } |
| |
| this->delegate_.StopTaskRunner(); |
| |
| EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), |
| kTaskCount)); |
| } |
| |
| // This test posts a fast, non-nestable task from within each of a number of |
| // slow, non-nestable tasks and checks that they all run in the sequence they |
| // were posted in and that there is no execution overlap whatsoever. |
| TYPED_TEST_P(SequencedTaskRunnerTest, NonNestablePostFromNonNestableTask) { |
| const int kParentCount = 10; |
| const int kChildrenPerParent = 10; |
| |
| this->delegate_.StartTaskRunner(); |
| const scoped_refptr<SequencedTaskRunner> task_runner = |
| this->delegate_.GetTaskRunner(); |
| |
| for (int i = 0; i < kParentCount; ++i) { |
| Closure task = Bind( |
| &internal::SequencedTaskTracker::PostNonNestableTasks, |
| this->task_tracker_, |
| task_runner, |
| kChildrenPerParent); |
| this->task_tracker_->PostWrappedNonNestableTask(task_runner, task); |
| } |
| |
| this->delegate_.StopTaskRunner(); |
| |
| EXPECT_TRUE(CheckNonNestableInvariants( |
| this->task_tracker_->GetTaskEvents(), |
| kParentCount * (kChildrenPerParent + 1))); |
| } |
| |
| // This test posts a delayed task, and checks that the task is run later than |
| // the specified time. |
| TYPED_TEST_P(SequencedTaskRunnerTest, DelayedTaskBasic) { |
| // TODO(akalin): Remove this check (http://crbug.com/149144). |
| if (!this->delegate_.TaskRunnerHandlesNonZeroDelays()) { |
| DLOG(INFO) << "This SequencedTaskRunner doesn't handle " |
| "non-zero delays; skipping"; |
| return; |
| } |
| |
| const int kTaskCount = 1; |
| const TimeDelta kDelay = TimeDelta::FromMilliseconds(100); |
| |
| this->delegate_.StartTaskRunner(); |
| const scoped_refptr<SequencedTaskRunner> task_runner = |
| this->delegate_.GetTaskRunner(); |
| |
| Time time_before_run = Time::Now(); |
| this->task_tracker_->PostWrappedDelayedNonNestableTask( |
| task_runner, Closure(), kDelay); |
| this->delegate_.StopTaskRunner(); |
| Time time_after_run = Time::Now(); |
| |
| EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), |
| kTaskCount)); |
| EXPECT_LE(kDelay, time_after_run - time_before_run); |
| } |
| |
| // This test posts two tasks with the same delay, and checks that the tasks are |
| // run in the order in which they were posted. |
| // |
| // NOTE: This is actually an approximate test since the API only takes a |
| // "delay" parameter, so we are not exactly simulating two tasks that get |
| // posted at the exact same time. It would be nice if the API allowed us to |
| // specify the desired run time. |
| TYPED_TEST_P(SequencedTaskRunnerTest, DelayedTasksSameDelay) { |
| // TODO(akalin): Remove this check (http://crbug.com/149144). |
| if (!this->delegate_.TaskRunnerHandlesNonZeroDelays()) { |
| DLOG(INFO) << "This SequencedTaskRunner doesn't handle " |
| "non-zero delays; skipping"; |
| return; |
| } |
| |
| const int kTaskCount = 2; |
| const TimeDelta kDelay = TimeDelta::FromMilliseconds(100); |
| |
| this->delegate_.StartTaskRunner(); |
| const scoped_refptr<SequencedTaskRunner> task_runner = |
| this->delegate_.GetTaskRunner(); |
| |
| this->task_tracker_->PostWrappedDelayedNonNestableTask( |
| task_runner, Closure(), kDelay); |
| this->task_tracker_->PostWrappedDelayedNonNestableTask( |
| task_runner, Closure(), kDelay); |
| this->delegate_.StopTaskRunner(); |
| |
| EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), |
| kTaskCount)); |
| } |
| |
| // This test posts a normal task and a delayed task, and checks that the |
| // delayed task runs after the normal task even if the normal task takes |
| // a long time to run. |
| TYPED_TEST_P(SequencedTaskRunnerTest, DelayedTaskAfterLongTask) { |
| // TODO(akalin): Remove this check (http://crbug.com/149144). |
| if (!this->delegate_.TaskRunnerHandlesNonZeroDelays()) { |
| DLOG(INFO) << "This SequencedTaskRunner doesn't handle " |
| "non-zero delays; skipping"; |
| return; |
| } |
| |
| const int kTaskCount = 2; |
| |
| this->delegate_.StartTaskRunner(); |
| const scoped_refptr<SequencedTaskRunner> task_runner = |
| this->delegate_.GetTaskRunner(); |
| |
| this->task_tracker_->PostWrappedNonNestableTask( |
| task_runner, base::Bind(&PlatformThread::Sleep, |
| TimeDelta::FromMilliseconds(50))); |
| this->task_tracker_->PostWrappedDelayedNonNestableTask( |
| task_runner, Closure(), TimeDelta::FromMilliseconds(10)); |
| this->delegate_.StopTaskRunner(); |
| |
| EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), |
| kTaskCount)); |
| } |
| |
| // Test that a pile of normal tasks and a delayed task run in the |
| // time-to-run order. |
| TYPED_TEST_P(SequencedTaskRunnerTest, DelayedTaskAfterManyLongTasks) { |
| // TODO(akalin): Remove this check (http://crbug.com/149144). |
| if (!this->delegate_.TaskRunnerHandlesNonZeroDelays()) { |
| DLOG(INFO) << "This SequencedTaskRunner doesn't handle " |
| "non-zero delays; skipping"; |
| return; |
| } |
| |
| const int kTaskCount = 11; |
| |
| this->delegate_.StartTaskRunner(); |
| const scoped_refptr<SequencedTaskRunner> task_runner = |
| this->delegate_.GetTaskRunner(); |
| |
| for (int i = 0; i < kTaskCount - 1; i++) { |
| this->task_tracker_->PostWrappedNonNestableTask( |
| task_runner, base::Bind(&PlatformThread::Sleep, |
| TimeDelta::FromMilliseconds(50))); |
| } |
| this->task_tracker_->PostWrappedDelayedNonNestableTask( |
| task_runner, Closure(), TimeDelta::FromMilliseconds(10)); |
| this->delegate_.StopTaskRunner(); |
| |
| EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), |
| kTaskCount)); |
| } |
| |
| |
| // TODO(francoisk777@gmail.com) Add a test, similiar to the above, which runs |
| // some tasked nestedly (which should be implemented in the test |
| // delegate). Also add, to the the test delegate, a predicate which checks |
| // whether the implementation supports nested tasks. |
| // |
| |
| REGISTER_TYPED_TEST_CASE_P(SequencedTaskRunnerTest, |
| SequentialNonNestable, |
| SequentialNestable, |
| SequentialDelayedNonNestable, |
| NonNestablePostFromNonNestableTask, |
| DelayedTaskBasic, |
| DelayedTasksSameDelay, |
| DelayedTaskAfterLongTask, |
| DelayedTaskAfterManyLongTasks); |
| |
| } // namespace base |
| |
| #endif // BASE_TASK_RUNNER_TEST_TEMPLATE_H_ |