Index: base/task_scheduler/thread_pool_unittest.cc |
diff --git a/base/task_scheduler/thread_pool_unittest.cc b/base/task_scheduler/thread_pool_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ce6aebfcb3248b5da2b6d9a16be2f337af8134d1 |
--- /dev/null |
+++ b/base/task_scheduler/thread_pool_unittest.cc |
@@ -0,0 +1,304 @@ |
+// Copyright 2016 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_scheduler/thread_pool.h" |
+ |
+#include "base/bind.h" |
+#include "base/callback_forward.h" |
+#include "base/synchronization/condition_variable.h" |
+#include "base/task_scheduler/scheduler_lock.h" |
+#include "base/task_scheduler/shutdown_manager.h" |
+#include "base/task_scheduler/worker_thread.h" |
+#include "base/threading/platform_thread.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace base { |
+namespace task_scheduler { |
+ |
+namespace { |
+class SingleThreadChecker { |
+ public: |
+ SingleThreadChecker() : ran_all_tasks_on_same_thread_(true) {} |
+ |
+ void RunTask() { |
+ AutoSchedulerLock auto_lock(lock_); |
+ |
+ if (!last_thread_handle_.is_null() && |
+ !PlatformThread::CurrentHandle().is_equal(last_thread_handle_)) { |
+ ran_all_tasks_on_same_thread_ = false; |
+ } |
+ last_thread_handle_ = PlatformThread::CurrentHandle(); |
+ } |
+ |
+ bool ran_all_tasks_on_same_thread() const { |
+ return ran_all_tasks_on_same_thread_; |
+ } |
+ |
+ private: |
+ PlatformThreadHandle last_thread_handle_; |
+ bool ran_all_tasks_on_same_thread_; |
+ SchedulerLock lock_; |
+}; |
+} // namespace |
+ |
+class TaskSchedulerThreadPoolTest : public testing::Test { |
+ protected: |
+ TaskSchedulerThreadPoolTest() |
+ : thread_pool_(ThreadPool::CreateThreadPool( |
+ ThreadPriority::NORMAL, |
+ 4, |
+ Bind(&TaskSchedulerThreadPoolTest::ReinsertSequenceCallback, |
+ Unretained(this)), |
+ &shutdown_manager_)), |
+ cv_(lock_.RawLockForConditionVariable()), |
+ last_posted_task_index_(0), |
+ last_run_task_index_(0), |
+ ran_task_that_should_not_run_(false) {} |
+ |
+ Closure GetTaskThatShouldRunClosure( |
+ SingleThreadChecker* single_thread_checker) { |
+ ++last_posted_task_index_; |
+ return Bind(&TaskSchedulerThreadPoolTest::RunTaskThatShouldRun, |
+ Unretained(this), last_posted_task_index_, |
+ single_thread_checker); |
+ } |
+ |
+ Closure GetTaskThatShouldNotRunClosure() { |
+ return Bind(&TaskSchedulerThreadPoolTest::RunTaskThatShouldNotRun, |
+ Unretained(this)); |
+ } |
+ |
+ void WaitUntilLastPostedTaskHasRun() { |
+ AutoSchedulerLock auto_lock(lock_); |
+ while (last_posted_task_index_ != last_run_task_index_) |
+ cv_.Wait(); |
+ } |
+ |
+ void Shutdown() { |
+ shutdown_manager_.Shutdown(); |
+ thread_pool_->JoinAllThreadsForTesting(); |
+ } |
+ |
+ bool ran_task_that_should_not_run() const { |
+ return ran_task_that_should_not_run_; |
+ } |
+ |
+ scoped_ptr<ThreadPool> thread_pool_; |
+ ShutdownManager shutdown_manager_; |
+ |
+ private: |
+ void ReinsertSequenceCallback(scoped_refptr<Sequence> sequence, |
+ const WorkerThread* worker_thread) { |
+ thread_pool_->ReinsertSequence(sequence, sequence->GetSortKey(), |
+ worker_thread); |
+ } |
+ |
+ void RunTaskThatShouldRun(size_t index, |
+ SingleThreadChecker* single_thread_checker) { |
+ AutoSchedulerLock auto_lock(lock_); |
+ |
+ if (single_thread_checker) |
+ single_thread_checker->RunTask(); |
+ |
+ // Wait until the task with index (|index| - 1) has run. If tasks are |
+ // executed in the wrong order, this can block forever and make the test |
+ // fail. |
+ // |
+ // Note: It isn't correct to assume that this condition will always be |
+ // immediatly true if tasks are popped from the priority queue in the right |
+ // order. Indeed, a thread A could start running task #2 before a thread B |
+ // starts running task #1 despite the fact that thread B has popped task #1 |
+ // before thread A has popped task #2. |
+ while (last_run_task_index_ != index - 1) |
+ cv_.Wait(); |
+ |
+ ++last_run_task_index_; |
+ cv_.Broadcast(); |
+ } |
+ |
+ void RunTaskThatShouldNotRun() { ran_task_that_should_not_run_ = true; } |
+ |
+ // Lock protecting |cv_|. |
+ SchedulerLock lock_; |
+ |
+ // Condition variable signaled each time a task completes its execution. |
+ ConditionVariable cv_; |
+ |
+ // Index of the last posted task. |
+ size_t last_posted_task_index_; |
+ |
+ // Index of the last run task. |
+ size_t last_run_task_index_; |
+ |
+ // True if a task that shouldn't run has run. |
+ bool ran_task_that_should_not_run_; |
+}; |
+ |
+TEST_F(TaskSchedulerThreadPoolTest, PostSingleParallelTask) { |
+ ASSERT_TRUE(thread_pool_.get()); |
+ |
+ thread_pool_->CreateTaskRunnerWithTraits(TaskTraits(), |
+ ExecutionMode::PARALLEL) |
+ ->PostTask(FROM_HERE, GetTaskThatShouldRunClosure(nullptr)); |
+ |
+ WaitUntilLastPostedTaskHasRun(); |
+ Shutdown(); |
+} |
+ |
+TEST_F(TaskSchedulerThreadPoolTest, PostSingleSequencedTask) { |
+ ASSERT_TRUE(thread_pool_.get()); |
+ |
+ thread_pool_->CreateTaskRunnerWithTraits(TaskTraits(), |
+ ExecutionMode::SEQUENCED) |
+ ->PostTask(FROM_HERE, GetTaskThatShouldRunClosure(nullptr)); |
+ |
+ WaitUntilLastPostedTaskHasRun(); |
+ Shutdown(); |
+} |
+ |
+TEST_F(TaskSchedulerThreadPoolTest, PostSingleSingleThreadedTask) { |
+ ASSERT_TRUE(thread_pool_.get()); |
+ |
+ thread_pool_->CreateTaskRunnerWithTraits(TaskTraits(), |
+ ExecutionMode::SINGLE_THREADED) |
+ ->PostTask(FROM_HERE, GetTaskThatShouldRunClosure(nullptr)); |
+ |
+ WaitUntilLastPostedTaskHasRun(); |
+ Shutdown(); |
+} |
+ |
+TEST_F(TaskSchedulerThreadPoolTest, PostMultipleTasksNoWaitBetweenPosts) { |
+ ASSERT_TRUE(thread_pool_.get()); |
+ |
+ for (size_t i = 0; i < 100; ++i) { |
+ thread_pool_->CreateTaskRunnerWithTraits(TaskTraits(), |
+ ExecutionMode::PARALLEL) |
+ ->PostTask(FROM_HERE, GetTaskThatShouldRunClosure(nullptr)); |
+ } |
+ |
+ WaitUntilLastPostedTaskHasRun(); |
+ Shutdown(); |
+} |
+ |
+TEST_F(TaskSchedulerThreadPoolTest, PostMultipleTasksWaitBetweenPosts) { |
+ ASSERT_TRUE(thread_pool_.get()); |
+ |
+ for (size_t i = 0; i < 100; ++i) { |
+ thread_pool_->CreateTaskRunnerWithTraits(TaskTraits(), |
+ ExecutionMode::PARALLEL) |
+ ->PostTask(FROM_HERE, GetTaskThatShouldRunClosure(nullptr)); |
+ WaitUntilLastPostedTaskHasRun(); |
+ } |
+ |
+ Shutdown(); |
+} |
+ |
+TEST_F(TaskSchedulerThreadPoolTest, PostMultipleTasksInSameSequence) { |
+ ASSERT_TRUE(thread_pool_.get()); |
+ |
+ auto task_runner = thread_pool_->CreateTaskRunnerWithTraits( |
+ TaskTraits(), ExecutionMode::SEQUENCED); |
+ |
+ for (size_t i = 0; i < 100; ++i) |
+ task_runner->PostTask(FROM_HERE, GetTaskThatShouldRunClosure(nullptr)); |
+ |
+ WaitUntilLastPostedTaskHasRun(); |
+ Shutdown(); |
+} |
+ |
+TEST_F(TaskSchedulerThreadPoolTest, PostMultipleTasksInTwoSequences) { |
+ ASSERT_TRUE(thread_pool_.get()); |
+ |
+ auto task_runner_a = thread_pool_->CreateTaskRunnerWithTraits( |
+ TaskTraits(), ExecutionMode::SEQUENCED); |
+ auto task_runner_b = thread_pool_->CreateTaskRunnerWithTraits( |
+ TaskTraits(), ExecutionMode::SEQUENCED); |
+ |
+ for (size_t i = 0; i < 100; ++i) { |
+ task_runner_a->PostTask(FROM_HERE, GetTaskThatShouldRunClosure(nullptr)); |
+ task_runner_b->PostTask(FROM_HERE, GetTaskThatShouldRunClosure(nullptr)); |
+ } |
+ |
+ WaitUntilLastPostedTaskHasRun(); |
+ Shutdown(); |
+} |
+ |
+TEST_F(TaskSchedulerThreadPoolTest, PostMultipleSingleThreadedTasks) { |
+ ASSERT_TRUE(thread_pool_.get()); |
+ |
+ auto task_runner = thread_pool_->CreateTaskRunnerWithTraits( |
+ TaskTraits(), ExecutionMode::SINGLE_THREADED); |
+ SingleThreadChecker single_thread_checker; |
+ |
+ for (size_t i = 0; i < 100; ++i) { |
+ task_runner->PostTask(FROM_HERE, |
+ GetTaskThatShouldRunClosure(&single_thread_checker)); |
+ } |
+ |
+ WaitUntilLastPostedTaskHasRun(); |
+ Shutdown(); |
+ EXPECT_TRUE(single_thread_checker.ran_all_tasks_on_same_thread()); |
+} |
+ |
+TEST_F(TaskSchedulerThreadPoolTest, PostParallelDelayedTasks) { |
+ ASSERT_TRUE(thread_pool_.get()); |
+ |
+ auto task_runner = thread_pool_->CreateTaskRunnerWithTraits( |
+ TaskTraits(), ExecutionMode::PARALLEL); |
+ |
+ for (size_t i = 0; i < 10; ++i) { |
+ task_runner->PostDelayedTask(FROM_HERE, |
+ GetTaskThatShouldRunClosure(nullptr), |
+ TimeDelta::FromMilliseconds(i * 50)); |
+ } |
+ |
+ WaitUntilLastPostedTaskHasRun(); |
+ Shutdown(); |
+} |
+ |
+TEST_F(TaskSchedulerThreadPoolTest, PostSequencedDelayedTasks) { |
+ ASSERT_TRUE(thread_pool_.get()); |
+ |
+ auto task_runner = thread_pool_->CreateTaskRunnerWithTraits( |
+ TaskTraits(), ExecutionMode::SEQUENCED); |
+ |
+ for (size_t i = 0; i < 10; ++i) { |
+ task_runner->PostDelayedTask(FROM_HERE, |
+ GetTaskThatShouldRunClosure(nullptr), |
+ TimeDelta::FromMilliseconds(i * 50)); |
+ } |
+ |
+ WaitUntilLastPostedTaskHasRun(); |
+ Shutdown(); |
+} |
+ |
+TEST_F(TaskSchedulerThreadPoolTest, ShutdownBehavior) { |
+ ASSERT_TRUE(thread_pool_.get()); |
+ |
+ shutdown_manager_.SetIsShuttingDownForTesting(); |
+ |
+ // Post tasks with different shutdown behaviors. |
+ thread_pool_->CreateTaskRunnerWithTraits( |
+ TaskTraits().WithShutdownBehavior( |
+ TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN), |
+ ExecutionMode::PARALLEL) |
+ ->PostTask(FROM_HERE, GetTaskThatShouldNotRunClosure()); |
+ thread_pool_->CreateTaskRunnerWithTraits( |
+ TaskTraits().WithShutdownBehavior( |
+ TaskShutdownBehavior::SKIP_ON_SHUTDOWN), |
+ ExecutionMode::PARALLEL) |
+ ->PostTask(FROM_HERE, GetTaskThatShouldNotRunClosure()); |
+ thread_pool_->CreateTaskRunnerWithTraits( |
+ TaskTraits().WithShutdownBehavior( |
+ TaskShutdownBehavior::BLOCK_SHUTDOWN), |
+ ExecutionMode::PARALLEL) |
+ ->PostTask(FROM_HERE, GetTaskThatShouldRunClosure(nullptr)); |
+ |
+ WaitUntilLastPostedTaskHasRun(); |
+ Shutdown(); |
+ EXPECT_FALSE(ran_task_that_should_not_run()); |
+} |
+ |
+} // namespace task_scheduler |
+} // namespace base |