| // 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/run_loop.h" | 
 | #include "base/single_thread_task_runner.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: | 
 |   ~CancelableTaskTrackerTest() override { 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.task_runner().get(), | 
 |                                        FROM_HERE, | 
 |                                        MakeExpectedRunClosure(FROM_HERE))); | 
 |  | 
 |   ignore_result(task_tracker_.PostTaskAndReply( | 
 |       worker_thread.task_runner().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->NumPendingTasks()); | 
 |  | 
 |   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.task_runner().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.task_runner()->PostTask( | 
 |       FROM_HERE, Bind(&ExpectIsCanceled, is_canceled, false)); | 
 |   other_thread.Stop(); | 
 |  | 
 |   task_tracker_.TryCancel(task_id); | 
 |  | 
 |   ASSERT_TRUE(other_thread.Start()); | 
 |   other_thread.task_runner()->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"; | 
 |   } | 
 | }; | 
 |  | 
 | // 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 DCHECK_IS_ON() | 
 |   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.task_runner()->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.task_runner()->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.task_runner()->PostTask( | 
 |       FROM_HERE, | 
 |       Bind(&MaybeRunDeadlyTaskTrackerMemberFunction, Unretained(&task_tracker_), | 
 |            Bind(&CancelableTaskTracker::TryCancelAll))); | 
 |  | 
 |   test_task_runner->RunUntilIdle(); | 
 | } | 
 |  | 
 | }  // namespace base |