// Copyright 2013 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 "content/browser/startup_task_runner.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" #include "base/location.h" #include "base/run_loop.h" #include "base/task_runner.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace content { namespace { using base::Closure; using testing::_; using testing::Assign; using testing::Invoke; using testing::WithArg; int observer_calls = 0; int task_count = 0; int observer_result; base::Closure task; // I couldn't get gMock's SaveArg to compile, hence had to save the argument // this way bool SaveTaskArg(const Closure& arg) { task = arg; return true; } void Observer(int result) { observer_calls++; observer_result = result; } class StartupTaskRunnerTest : public testing::Test { public: virtual void SetUp() { last_task_ = 0; observer_calls = 0; task_count = 0; } int Task1() { last_task_ = 1; task_count++; return 0; } int Task2() { last_task_ = 2; task_count++; return 0; } int FailingTask() { // Task returning failure last_task_ = 3; task_count++; return 1; } int GetLastTask() { return last_task_; } private: int last_task_; }; // We can't use the real message loop, even if we want to, since doing so on // Android requires a complex Java infrastructure. The test would have to built // as a content_shell test; but content_shell startup invokes the class we are // trying to test. // // The mocks are not directly in TaskRunnerProxy because reference counted // objects seem to confuse the mocking framework class MockTaskRunner { public: MOCK_METHOD3( PostDelayedTask, bool(const tracked_objects::Location&, const Closure&, base::TimeDelta)); MOCK_METHOD3( PostNonNestableDelayedTask, bool(const tracked_objects::Location&, const Closure&, base::TimeDelta)); }; class TaskRunnerProxy : public base::SingleThreadTaskRunner { public: TaskRunnerProxy(MockTaskRunner* mock) : mock_(mock) {} virtual bool RunsTasksOnCurrentThread() const OVERRIDE { return true; } virtual bool PostDelayedTask(const tracked_objects::Location& location, const Closure& closure, base::TimeDelta delta) OVERRIDE { return mock_->PostDelayedTask(location, closure, delta); } virtual bool PostNonNestableDelayedTask( const tracked_objects::Location& location, const Closure& closure, base::TimeDelta delta) OVERRIDE { return mock_->PostNonNestableDelayedTask(location, closure, delta); } private: MockTaskRunner* mock_; virtual ~TaskRunnerProxy() {} }; TEST_F(StartupTaskRunnerTest, SynchronousExecution) { MockTaskRunner mock_runner; scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner); EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0); EXPECT_CALL(mock_runner, PostNonNestableDelayedTask(_, _, _)).Times(0); StartupTaskRunner runner(base::Bind(&Observer), proxy); StartupTask task1 = base::Bind(&StartupTaskRunnerTest::Task1, base::Unretained(this)); runner.AddTask(task1); EXPECT_EQ(GetLastTask(), 0); StartupTask task2 = base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this)); runner.AddTask(task2); // Nothing should run until we tell them to. EXPECT_EQ(GetLastTask(), 0); runner.RunAllTasksNow(); // On an immediate StartupTaskRunner the tasks should now all have run. EXPECT_EQ(GetLastTask(), 2); EXPECT_EQ(task_count, 2); EXPECT_EQ(observer_calls, 1); EXPECT_EQ(observer_result, 0); // Running the tasks asynchronously shouldn't do anything // In particular Post... should not be called runner.StartRunningTasksAsync(); // No more tasks should be run and the observer should not have been called // again EXPECT_EQ(task_count, 2); EXPECT_EQ(observer_calls, 1); } TEST_F(StartupTaskRunnerTest, NullObserver) { MockTaskRunner mock_runner; scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner); EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0); EXPECT_CALL(mock_runner, PostNonNestableDelayedTask(_, _, _)).Times(0); StartupTaskRunner runner(base::Callback<void(int)>(), proxy); StartupTask task1 = base::Bind(&StartupTaskRunnerTest::Task1, base::Unretained(this)); runner.AddTask(task1); EXPECT_EQ(GetLastTask(), 0); StartupTask task2 = base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this)); runner.AddTask(task2); // Nothing should run until we tell them to. EXPECT_EQ(GetLastTask(), 0); runner.RunAllTasksNow(); // On an immediate StartupTaskRunner the tasks should now all have run. EXPECT_EQ(GetLastTask(), 2); EXPECT_EQ(task_count, 2); // Running the tasks asynchronously shouldn't do anything // In particular Post... should not be called runner.StartRunningTasksAsync(); // No more tasks should have been run EXPECT_EQ(task_count, 2); EXPECT_EQ(observer_calls, 0); } TEST_F(StartupTaskRunnerTest, SynchronousExecutionFailedTask) { MockTaskRunner mock_runner; scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner); EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0); EXPECT_CALL(mock_runner, PostNonNestableDelayedTask(_, _, _)).Times(0); StartupTaskRunner runner(base::Bind(&Observer), proxy); StartupTask task3 = base::Bind(&StartupTaskRunnerTest::FailingTask, base::Unretained(this)); runner.AddTask(task3); EXPECT_EQ(GetLastTask(), 0); StartupTask task2 = base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this)); runner.AddTask(task2); // Nothing should run until we tell them to. EXPECT_EQ(GetLastTask(), 0); runner.RunAllTasksNow(); // Only the first task should have run, since it failed EXPECT_EQ(GetLastTask(), 3); EXPECT_EQ(task_count, 1); EXPECT_EQ(observer_calls, 1); EXPECT_EQ(observer_result, 1); // After a failed task all remaining tasks should be cancelled // In particular Post... should not be called by running asynchronously runner.StartRunningTasksAsync(); // The observer should only be called the first time the queue completes and // no more tasks should have run EXPECT_EQ(observer_calls, 1); EXPECT_EQ(task_count, 1); } TEST_F(StartupTaskRunnerTest, AsynchronousExecution) { MockTaskRunner mock_runner; scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner); EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0); EXPECT_CALL( mock_runner, PostNonNestableDelayedTask(_, _, base::TimeDelta::FromMilliseconds(0))) .Times(testing::Between(2, 3)) .WillRepeatedly(WithArg<1>(Invoke(SaveTaskArg))); StartupTaskRunner runner(base::Bind(&Observer), proxy); StartupTask task1 = base::Bind(&StartupTaskRunnerTest::Task1, base::Unretained(this)); runner.AddTask(task1); StartupTask task2 = base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this)); runner.AddTask(task2); // Nothing should run until we tell them to. EXPECT_EQ(GetLastTask(), 0); runner.StartRunningTasksAsync(); // No tasks should have run yet, since we the message loop hasn't run. EXPECT_EQ(GetLastTask(), 0); // Fake the actual message loop. Each time a task is run a new task should // be added to the queue, hence updating "task". The loop should actually run // at most 3 times (once for each task plus possibly once for the observer), // the "4" is a backstop. for (int i = 0; i < 4 && observer_calls == 0; i++) { task.Run(); EXPECT_EQ(i + 1, GetLastTask()); } EXPECT_EQ(task_count, 2); EXPECT_EQ(observer_calls, 1); EXPECT_EQ(observer_result, 0); // Check that running synchronously now doesn't do anything runner.RunAllTasksNow(); EXPECT_EQ(task_count, 2); EXPECT_EQ(observer_calls, 1); } TEST_F(StartupTaskRunnerTest, AsynchronousExecutionFailedTask) { MockTaskRunner mock_runner; scoped_refptr<TaskRunnerProxy> proxy = new TaskRunnerProxy(&mock_runner); EXPECT_CALL(mock_runner, PostDelayedTask(_, _, _)).Times(0); EXPECT_CALL( mock_runner, PostNonNestableDelayedTask(_, _, base::TimeDelta::FromMilliseconds(0))) .Times(testing::Between(1, 2)) .WillRepeatedly(WithArg<1>(Invoke(SaveTaskArg))); StartupTaskRunner runner(base::Bind(&Observer), proxy); StartupTask task3 = base::Bind(&StartupTaskRunnerTest::FailingTask, base::Unretained(this)); runner.AddTask(task3); StartupTask task2 = base::Bind(&StartupTaskRunnerTest::Task2, base::Unretained(this)); runner.AddTask(task2); // Nothing should run until we tell them to. EXPECT_EQ(GetLastTask(), 0); runner.StartRunningTasksAsync(); // No tasks should have run yet, since we the message loop hasn't run. EXPECT_EQ(GetLastTask(), 0); // Fake the actual message loop. Each time a task is run a new task should // be added to the queue, hence updating "task". The loop should actually run // at most twice (once for the failed task plus possibly once for the // observer), the "4" is a backstop. for (int i = 0; i < 4 && observer_calls == 0; i++) { task.Run(); } EXPECT_EQ(GetLastTask(), 3); EXPECT_EQ(task_count, 1); EXPECT_EQ(observer_calls, 1); EXPECT_EQ(observer_result, 1); // Check that running synchronously now doesn't do anything runner.RunAllTasksNow(); EXPECT_EQ(observer_calls, 1); EXPECT_EQ(task_count, 1); } } // namespace } // namespace content