diff options
Diffstat (limited to 'chromium/base/task/cancelable_task_tracker_unittest.cc')
-rw-r--r-- | chromium/base/task/cancelable_task_tracker_unittest.cc | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/chromium/base/task/cancelable_task_tracker_unittest.cc b/chromium/base/task/cancelable_task_tracker_unittest.cc new file mode 100644 index 00000000000..e122e8deef0 --- /dev/null +++ b/chromium/base/task/cancelable_task_tracker_unittest.cc @@ -0,0 +1,435 @@ +// Copyright 2014 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/cancelable_task_tracker.h" + +#include <cstddef> +#include <deque> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/test/test_simple_task_runner.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +class CancelableTaskTrackerTest : public testing::Test { + protected: + virtual ~CancelableTaskTrackerTest() { RunCurrentLoopUntilIdle(); } + + void RunCurrentLoopUntilIdle() { + RunLoop run_loop; + run_loop.RunUntilIdle(); + } + + CancelableTaskTracker task_tracker_; + + private: + // Needed by CancelableTaskTracker methods. + MessageLoop message_loop_; +}; + +void AddFailureAt(const tracked_objects::Location& location) { + ADD_FAILURE_AT(location.file_name(), location.line_number()); +} + +// Returns a closure that fails if run. +Closure MakeExpectedNotRunClosure(const tracked_objects::Location& location) { + return Bind(&AddFailureAt, location); +} + +// A helper class for MakeExpectedRunClosure() that fails if it is +// destroyed without Run() having been called. This class may be used +// from multiple threads as long as Run() is called at most once +// before destruction. +class RunChecker { + public: + explicit RunChecker(const tracked_objects::Location& location) + : location_(location), called_(false) {} + + ~RunChecker() { + if (!called_) { + ADD_FAILURE_AT(location_.file_name(), location_.line_number()); + } + } + + void Run() { called_ = true; } + + private: + tracked_objects::Location location_; + bool called_; +}; + +// Returns a closure that fails on destruction if it hasn't been run. +Closure MakeExpectedRunClosure(const tracked_objects::Location& location) { + return Bind(&RunChecker::Run, Owned(new RunChecker(location))); +} + +} // namespace + +// With the task tracker, post a task, a task with a reply, and get a +// new task id without canceling any of them. The tasks and the reply +// should run and the "is canceled" callback should return false. +TEST_F(CancelableTaskTrackerTest, NoCancel) { + Thread worker_thread("worker thread"); + ASSERT_TRUE(worker_thread.Start()); + + ignore_result(task_tracker_.PostTask(worker_thread.message_loop_proxy().get(), + FROM_HERE, + MakeExpectedRunClosure(FROM_HERE))); + + ignore_result( + task_tracker_.PostTaskAndReply(worker_thread.message_loop_proxy().get(), + FROM_HERE, + MakeExpectedRunClosure(FROM_HERE), + MakeExpectedRunClosure(FROM_HERE))); + + CancelableTaskTracker::IsCanceledCallback is_canceled; + ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled)); + + worker_thread.Stop(); + + RunCurrentLoopUntilIdle(); + + EXPECT_FALSE(is_canceled.Run()); +} + +// Post a task with the task tracker but cancel it before running the +// task runner. The task should not run. +TEST_F(CancelableTaskTrackerTest, CancelPostedTask) { + scoped_refptr<TestSimpleTaskRunner> test_task_runner( + new TestSimpleTaskRunner()); + + CancelableTaskTracker::TaskId task_id = task_tracker_.PostTask( + test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE)); + EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id); + + EXPECT_EQ(1U, test_task_runner->GetPendingTasks().size()); + + task_tracker_.TryCancel(task_id); + + test_task_runner->RunUntilIdle(); +} + +// Post a task with reply with the task tracker and cancel it before +// running the task runner. Neither the task nor the reply should +// run. +TEST_F(CancelableTaskTrackerTest, CancelPostedTaskAndReply) { + scoped_refptr<TestSimpleTaskRunner> test_task_runner( + new TestSimpleTaskRunner()); + + CancelableTaskTracker::TaskId task_id = + task_tracker_.PostTaskAndReply(test_task_runner.get(), + FROM_HERE, + MakeExpectedNotRunClosure(FROM_HERE), + MakeExpectedNotRunClosure(FROM_HERE)); + EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id); + + task_tracker_.TryCancel(task_id); + + test_task_runner->RunUntilIdle(); +} + +// Post a task with reply with the task tracker and cancel it after +// running the task runner but before running the current message +// loop. The task should run but the reply should not. +TEST_F(CancelableTaskTrackerTest, CancelReply) { + scoped_refptr<TestSimpleTaskRunner> test_task_runner( + new TestSimpleTaskRunner()); + + CancelableTaskTracker::TaskId task_id = + task_tracker_.PostTaskAndReply(test_task_runner.get(), + FROM_HERE, + MakeExpectedRunClosure(FROM_HERE), + MakeExpectedNotRunClosure(FROM_HERE)); + EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id); + + test_task_runner->RunUntilIdle(); + + task_tracker_.TryCancel(task_id); +} + +// Post a task with reply with the task tracker on a worker thread and +// cancel it before running the current message loop. The task should +// run but the reply should not. +TEST_F(CancelableTaskTrackerTest, CancelReplyDifferentThread) { + Thread worker_thread("worker thread"); + ASSERT_TRUE(worker_thread.Start()); + + CancelableTaskTracker::TaskId task_id = + task_tracker_.PostTaskAndReply(worker_thread.message_loop_proxy().get(), + FROM_HERE, + Bind(&DoNothing), + MakeExpectedNotRunClosure(FROM_HERE)); + EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id); + + task_tracker_.TryCancel(task_id); + + worker_thread.Stop(); +} + +void ExpectIsCanceled( + const CancelableTaskTracker::IsCanceledCallback& is_canceled, + bool expected_is_canceled) { + EXPECT_EQ(expected_is_canceled, is_canceled.Run()); +} + +// Create a new task ID and check its status on a separate thread +// before and after canceling. The is-canceled callback should be +// thread-safe (i.e., nothing should blow up). +TEST_F(CancelableTaskTrackerTest, NewTrackedTaskIdDifferentThread) { + CancelableTaskTracker::IsCanceledCallback is_canceled; + CancelableTaskTracker::TaskId task_id = + task_tracker_.NewTrackedTaskId(&is_canceled); + + EXPECT_FALSE(is_canceled.Run()); + + Thread other_thread("other thread"); + ASSERT_TRUE(other_thread.Start()); + other_thread.message_loop_proxy()->PostTask( + FROM_HERE, Bind(&ExpectIsCanceled, is_canceled, false)); + other_thread.Stop(); + + task_tracker_.TryCancel(task_id); + + ASSERT_TRUE(other_thread.Start()); + other_thread.message_loop_proxy()->PostTask( + FROM_HERE, Bind(&ExpectIsCanceled, is_canceled, true)); + other_thread.Stop(); +} + +// With the task tracker, post a task, a task with a reply, get a new +// task id, and then cancel all of them. None of the tasks nor the +// reply should run and the "is canceled" callback should return +// true. +TEST_F(CancelableTaskTrackerTest, CancelAll) { + scoped_refptr<TestSimpleTaskRunner> test_task_runner( + new TestSimpleTaskRunner()); + + ignore_result(task_tracker_.PostTask( + test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE))); + + ignore_result( + task_tracker_.PostTaskAndReply(test_task_runner.get(), + FROM_HERE, + MakeExpectedNotRunClosure(FROM_HERE), + MakeExpectedNotRunClosure(FROM_HERE))); + + CancelableTaskTracker::IsCanceledCallback is_canceled; + ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled)); + + task_tracker_.TryCancelAll(); + + test_task_runner->RunUntilIdle(); + + RunCurrentLoopUntilIdle(); + + EXPECT_TRUE(is_canceled.Run()); +} + +// With the task tracker, post a task, a task with a reply, get a new +// task id, and then cancel all of them. None of the tasks nor the +// reply should run and the "is canceled" callback should return +// true. +TEST_F(CancelableTaskTrackerTest, DestructionCancelsAll) { + scoped_refptr<TestSimpleTaskRunner> test_task_runner( + new TestSimpleTaskRunner()); + + CancelableTaskTracker::IsCanceledCallback is_canceled; + + { + // Create another task tracker with a smaller scope. + CancelableTaskTracker task_tracker; + + ignore_result(task_tracker.PostTask(test_task_runner.get(), + FROM_HERE, + MakeExpectedNotRunClosure(FROM_HERE))); + + ignore_result( + task_tracker.PostTaskAndReply(test_task_runner.get(), + FROM_HERE, + MakeExpectedNotRunClosure(FROM_HERE), + MakeExpectedNotRunClosure(FROM_HERE))); + + ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled)); + } + + test_task_runner->RunUntilIdle(); + + RunCurrentLoopUntilIdle(); + + EXPECT_FALSE(is_canceled.Run()); +} + +// Post a task and cancel it. HasTrackedTasks() should return true +// from when the task is posted until the (do-nothing) reply task is +// flushed. +TEST_F(CancelableTaskTrackerTest, HasTrackedTasksPost) { + scoped_refptr<TestSimpleTaskRunner> test_task_runner( + new TestSimpleTaskRunner()); + + EXPECT_FALSE(task_tracker_.HasTrackedTasks()); + + ignore_result(task_tracker_.PostTask( + test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE))); + + task_tracker_.TryCancelAll(); + + test_task_runner->RunUntilIdle(); + + EXPECT_TRUE(task_tracker_.HasTrackedTasks()); + + RunCurrentLoopUntilIdle(); + + EXPECT_FALSE(task_tracker_.HasTrackedTasks()); +} + +// Post a task with a reply and cancel it. HasTrackedTasks() should +// return true from when the task is posted until it is canceled. +TEST_F(CancelableTaskTrackerTest, HasTrackedTasksPostWithReply) { + scoped_refptr<TestSimpleTaskRunner> test_task_runner( + new TestSimpleTaskRunner()); + + EXPECT_FALSE(task_tracker_.HasTrackedTasks()); + + ignore_result( + task_tracker_.PostTaskAndReply(test_task_runner.get(), + FROM_HERE, + MakeExpectedNotRunClosure(FROM_HERE), + MakeExpectedNotRunClosure(FROM_HERE))); + + task_tracker_.TryCancelAll(); + + test_task_runner->RunUntilIdle(); + + EXPECT_TRUE(task_tracker_.HasTrackedTasks()); + + RunCurrentLoopUntilIdle(); + + EXPECT_FALSE(task_tracker_.HasTrackedTasks()); +} + +// Create a new tracked task ID. HasTrackedTasks() should return true +// until the IsCanceledCallback is destroyed. +TEST_F(CancelableTaskTrackerTest, HasTrackedTasksIsCancelled) { + EXPECT_FALSE(task_tracker_.HasTrackedTasks()); + + CancelableTaskTracker::IsCanceledCallback is_canceled; + ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled)); + + task_tracker_.TryCancelAll(); + + EXPECT_TRUE(task_tracker_.HasTrackedTasks()); + + is_canceled.Reset(); + + EXPECT_FALSE(task_tracker_.HasTrackedTasks()); +} + +// The death tests below make sure that calling task tracker member +// functions from a thread different from its owner thread DCHECKs in +// debug mode. + +class CancelableTaskTrackerDeathTest : public CancelableTaskTrackerTest { + protected: + CancelableTaskTrackerDeathTest() { + // The default style "fast" does not support multi-threaded tests. + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + } + + virtual ~CancelableTaskTrackerDeathTest() {} +}; + +// Duplicated from base/threading/thread_checker.h so that we can be +// good citizens there and undef the macro. +#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON) +#define ENABLE_THREAD_CHECKER 1 +#else +#define ENABLE_THREAD_CHECKER 0 +#endif + +// Runs |fn| with |task_tracker|, expecting it to crash in debug mode. +void MaybeRunDeadlyTaskTrackerMemberFunction( + CancelableTaskTracker* task_tracker, + const Callback<void(CancelableTaskTracker*)>& fn) { +// CancelableTask uses DCHECKs with its ThreadChecker (itself only +// enabled in debug mode). +#if ENABLE_THREAD_CHECKER + EXPECT_DEATH_IF_SUPPORTED(fn.Run(task_tracker), ""); +#endif +} + +void PostDoNothingTask(CancelableTaskTracker* task_tracker) { + ignore_result(task_tracker->PostTask( + scoped_refptr<TestSimpleTaskRunner>(new TestSimpleTaskRunner()).get(), + FROM_HERE, + Bind(&DoNothing))); +} + +TEST_F(CancelableTaskTrackerDeathTest, PostFromDifferentThread) { + Thread bad_thread("bad thread"); + ASSERT_TRUE(bad_thread.Start()); + + bad_thread.message_loop_proxy()->PostTask( + FROM_HERE, + Bind(&MaybeRunDeadlyTaskTrackerMemberFunction, + Unretained(&task_tracker_), + Bind(&PostDoNothingTask))); +} + +void TryCancel(CancelableTaskTracker::TaskId task_id, + CancelableTaskTracker* task_tracker) { + task_tracker->TryCancel(task_id); +} + +TEST_F(CancelableTaskTrackerDeathTest, CancelOnDifferentThread) { + scoped_refptr<TestSimpleTaskRunner> test_task_runner( + new TestSimpleTaskRunner()); + + Thread bad_thread("bad thread"); + ASSERT_TRUE(bad_thread.Start()); + + CancelableTaskTracker::TaskId task_id = task_tracker_.PostTask( + test_task_runner.get(), FROM_HERE, Bind(&DoNothing)); + EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id); + + bad_thread.message_loop_proxy()->PostTask( + FROM_HERE, + Bind(&MaybeRunDeadlyTaskTrackerMemberFunction, + Unretained(&task_tracker_), + Bind(&TryCancel, task_id))); + + test_task_runner->RunUntilIdle(); +} + +TEST_F(CancelableTaskTrackerDeathTest, CancelAllOnDifferentThread) { + scoped_refptr<TestSimpleTaskRunner> test_task_runner( + new TestSimpleTaskRunner()); + + Thread bad_thread("bad thread"); + ASSERT_TRUE(bad_thread.Start()); + + CancelableTaskTracker::TaskId task_id = task_tracker_.PostTask( + test_task_runner.get(), FROM_HERE, Bind(&DoNothing)); + EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id); + + bad_thread.message_loop_proxy()->PostTask( + FROM_HERE, + Bind(&MaybeRunDeadlyTaskTrackerMemberFunction, + Unretained(&task_tracker_), + Bind(&CancelableTaskTracker::TryCancelAll))); + + test_task_runner->RunUntilIdle(); +} + +} // namespace base |