summaryrefslogtreecommitdiffstats
path: root/chromium/base/task/cancelable_task_tracker_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/base/task/cancelable_task_tracker_unittest.cc')
-rw-r--r--chromium/base/task/cancelable_task_tracker_unittest.cc435
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