| // 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. |
| |
| // SequencedTaskRunnerTest 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_TEST_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_ |
| #define BASE_TEST_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_ |
| |
| #include <cstddef> |
| #include <iosfwd> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/synchronization/condition_variable.h" |
| #include "base/synchronization/lock.h" |
| #include "base/time/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; |
| }; |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SequencedTaskTracker); |
| // 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(SequencedTaskRunner* task_runner, |
| const Closure& task); |
| |
| // Posts the nestable task |task|, and records its post event. |
| void PostWrappedNestableTask(SequencedTaskRunner* task_runner, |
| const Closure& task); |
| |
| // Posts the delayed non-nestable task |task|, and records its post event. |
| void PostWrappedDelayedNonNestableTask(SequencedTaskRunner* task_runner, |
| const Closure& task, |
| TimeDelta delay); |
| |
| // Posts |task_count| non-nestable tasks. |
| void PostNonNestableTasks(SequencedTaskRunner* task_runner, int task_count); |
| |
| const std::vector<TaskEvent>& GetTaskEvents() const; |
| |
| // Returns after the tracker observes a total of |count| task completions. |
| void WaitForCompletedTasks(int count); |
| |
| 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_, next_post_i_, task_end_count_ and task_end_cv_. |
| 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_; |
| |
| // The number of task end events we've received. |
| int task_end_count_; |
| ConditionVariable task_end_cv_; |
| |
| 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 |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SequencedTaskRunnerTest); |
| 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.get(), |
| Bind(&PlatformThread::Sleep, TimeDelta::FromSeconds(1))); |
| for (int i = 1; i < kTaskCount; ++i) { |
| this->task_tracker_->PostWrappedNonNestableTask(task_runner.get(), |
| 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.get(), |
| Bind(&PlatformThread::Sleep, TimeDelta::FromSeconds(1))); |
| for (int i = 1; i < kTaskCount; ++i) { |
| this->task_tracker_->PostWrappedNestableTask(task_runner.get(), 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) { |
| 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.get(), Closure(), |
| TimeDelta::FromMilliseconds(kDelayIncrementMs * i)); |
| } |
| |
| this->task_tracker_->WaitForCompletedTasks(kTaskCount); |
| 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_, |
| RetainedRef(task_runner), |
| kChildrenPerParent); |
| this->task_tracker_->PostWrappedNonNestableTask(task_runner.get(), task); |
| } |
| |
| this->delegate_.StopTaskRunner(); |
| |
| EXPECT_TRUE(CheckNonNestableInvariants( |
| this->task_tracker_->GetTaskEvents(), |
| kParentCount * (kChildrenPerParent + 1))); |
| } |
| |
| // 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) { |
| 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.get(), |
| Closure(), kDelay); |
| this->task_tracker_->PostWrappedDelayedNonNestableTask(task_runner.get(), |
| Closure(), kDelay); |
| this->task_tracker_->WaitForCompletedTasks(kTaskCount); |
| 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) { |
| const int kTaskCount = 2; |
| |
| this->delegate_.StartTaskRunner(); |
| const scoped_refptr<SequencedTaskRunner> task_runner = |
| this->delegate_.GetTaskRunner(); |
| |
| this->task_tracker_->PostWrappedNonNestableTask( |
| task_runner.get(), |
| base::Bind(&PlatformThread::Sleep, TimeDelta::FromMilliseconds(50))); |
| this->task_tracker_->PostWrappedDelayedNonNestableTask( |
| task_runner.get(), Closure(), TimeDelta::FromMilliseconds(10)); |
| this->task_tracker_->WaitForCompletedTasks(kTaskCount); |
| 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) { |
| 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.get(), |
| base::Bind(&PlatformThread::Sleep, TimeDelta::FromMilliseconds(50))); |
| } |
| this->task_tracker_->PostWrappedDelayedNonNestableTask( |
| task_runner.get(), Closure(), TimeDelta::FromMilliseconds(10)); |
| this->task_tracker_->WaitForCompletedTasks(kTaskCount); |
| 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. |
| // |
| |
| // The SequencedTaskRunnerTest test case verifies behaviour that is expected |
| // from a sequenced task runner in order to be conformant. |
| REGISTER_TYPED_TEST_CASE_P(SequencedTaskRunnerTest, |
| SequentialNonNestable, |
| SequentialNestable, |
| SequentialDelayedNonNestable, |
| NonNestablePostFromNonNestableTask, |
| DelayedTasksSameDelay, |
| DelayedTaskAfterLongTask, |
| DelayedTaskAfterManyLongTasks); |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SequencedTaskRunnerDelayedTest); |
| template <typename TaskRunnerTestDelegate> |
| class SequencedTaskRunnerDelayedTest |
| : public SequencedTaskRunnerTest<TaskRunnerTestDelegate> {}; |
| |
| TYPED_TEST_CASE_P(SequencedTaskRunnerDelayedTest); |
| |
| // This test posts a delayed task, and checks that the task is run later than |
| // the specified time. |
| TYPED_TEST_P(SequencedTaskRunnerDelayedTest, DelayedTaskBasic) { |
| 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.get(), |
| Closure(), kDelay); |
| this->task_tracker_->WaitForCompletedTasks(kTaskCount); |
| 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); |
| } |
| |
| // SequencedTaskRunnerDelayedTest tests that the |delay| parameter of |
| // is used to actually wait for |delay| ms before executing the task. |
| // This is not mandatory for a SequencedTaskRunner to be compliant. |
| REGISTER_TYPED_TEST_CASE_P(SequencedTaskRunnerDelayedTest, DelayedTaskBasic); |
| |
| } // namespace base |
| |
| #endif // BASE_TEST_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_ |