blob: 40aae04cb51bbdfd5a8cdd608604cb1f45c9eb39 [file] [log] [blame]
// Copyright 2018 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/sequence_manager/thread_controller_with_message_pump_impl.h"
#include "base/bind_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "base/single_thread_task_runner.h"
#include "base/test/bind_test_util.h"
#include "base/test/mock_callback.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/threading/thread_task_runner_handle.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "base/debug/stack_trace.h"
#include <queue>
using testing::_;
using testing::Invoke;
using testing::ElementsAre;
namespace base {
namespace sequence_manager {
namespace {
class ThreadControllerForTest
: public internal::ThreadControllerWithMessagePumpImpl {
public:
ThreadControllerForTest(std::unique_ptr<MessagePump> pump,
const TickClock* clock)
: ThreadControllerWithMessagePumpImpl(std::move(pump), clock) {}
using ThreadControllerWithMessagePumpImpl::DoDelayedWork;
using ThreadControllerWithMessagePumpImpl::DoIdleWork;
using ThreadControllerWithMessagePumpImpl::DoWork;
using ThreadControllerWithMessagePumpImpl::EnsureWorkScheduled;
using ThreadControllerWithMessagePumpImpl::Quit;
};
class MockMessagePump : public MessagePump {
public:
MockMessagePump() {}
~MockMessagePump() override {}
MOCK_METHOD1(Run, void(MessagePump::Delegate*));
MOCK_METHOD0(Quit, void());
MOCK_METHOD0(ScheduleWork, void());
MOCK_METHOD1(ScheduleDelayedWork, void(const TimeTicks&));
MOCK_METHOD1(SetTimerSlack, void(TimerSlack));
};
// TODO(crbug.com/901373): Deduplicate FakeTaskRunners.
class FakeTaskRunner : public SingleThreadTaskRunner {
public:
bool PostDelayedTask(const Location& from_here,
OnceClosure task,
TimeDelta delay) override {
return true;
}
bool PostNonNestableDelayedTask(const Location& from_here,
OnceClosure task,
TimeDelta delay) override {
return true;
}
bool RunsTasksInCurrentSequence() const override { return true; }
protected:
~FakeTaskRunner() override = default;
};
class FakeSequencedTaskSource : public internal::SequencedTaskSource {
public:
explicit FakeSequencedTaskSource(TickClock* clock) : clock_(clock) {}
~FakeSequencedTaskSource() override = default;
Optional<PendingTask> TakeTask() override {
if (tasks_.empty())
return nullopt;
if (tasks_.front().delayed_run_time > clock_->NowTicks())
return nullopt;
PendingTask task = std::move(tasks_.front());
tasks_.pop();
return task;
}
void DidRunTask() override {}
TimeDelta DelayTillNextTask(LazyNow* lazy_now) const override {
if (tasks_.empty())
return TimeDelta::Max();
if (tasks_.front().delayed_run_time.is_null())
return TimeDelta();
if (lazy_now->Now() > tasks_.front().delayed_run_time)
return TimeDelta();
return tasks_.front().delayed_run_time - lazy_now->Now();
}
void AddTask(PendingTask task) {
DCHECK(tasks_.empty() || task.delayed_run_time.is_null() ||
tasks_.back().delayed_run_time < task.delayed_run_time);
tasks_.push(std::move(task));
}
bool HasPendingHighResolutionTasks() override { return false; }
bool OnSystemIdle() override { return false; }
private:
TickClock* clock_;
std::queue<PendingTask> tasks_;
};
TimeTicks Seconds(int seconds) {
return TimeTicks() + TimeDelta::FromSeconds(seconds);
}
TimeTicks Days(int seconds) {
return TimeTicks() + TimeDelta::FromDays(seconds);
}
} // namespace
class ThreadControllerWithMessagePumpTest : public testing::Test {
public:
ThreadControllerWithMessagePumpTest()
: message_pump_(new testing::StrictMock<MockMessagePump>()),
thread_controller_(std::unique_ptr<MessagePump>(message_pump_),
&clock_),
task_source_(&clock_) {
thread_controller_.SetWorkBatchSize(1);
thread_controller_.SetSequencedTaskSource(&task_source_);
}
protected:
MockMessagePump* message_pump_;
SimpleTestTickClock clock_;
ThreadControllerForTest thread_controller_;
FakeSequencedTaskSource task_source_;
};
TEST_F(ThreadControllerWithMessagePumpTest, ScheduleDelayedWork) {
TimeTicks next_run_time;
MockCallback<OnceClosure> task1;
task_source_.AddTask(PendingTask(FROM_HERE, task1.Get(), Seconds(10)));
MockCallback<OnceClosure> task2;
task_source_.AddTask(PendingTask(FROM_HERE, task2.Get(), TimeTicks()));
MockCallback<OnceClosure> task3;
task_source_.AddTask(PendingTask(FROM_HERE, task3.Get(), Seconds(20)));
// Call a no-op DoWork. Expect that it doesn't do any work, but
// schedules a delayed wake-up appropriately.
clock_.SetNowTicks(Seconds(5));
EXPECT_CALL(*message_pump_, ScheduleDelayedWork(Seconds(10)));
EXPECT_FALSE(thread_controller_.DoWork());
testing::Mock::VerifyAndClearExpectations(message_pump_);
// Call DoDelayedWork after the expiration of the delay.
// Expect that a task will run and the next delay will equal to
// TimeTicks() as we have immediate work to do.
clock_.SetNowTicks(Seconds(11));
EXPECT_CALL(task1, Run()).Times(1);
EXPECT_TRUE(thread_controller_.DoDelayedWork(&next_run_time));
EXPECT_EQ(next_run_time, TimeTicks());
testing::Mock::VerifyAndClearExpectations(message_pump_);
testing::Mock::VerifyAndClearExpectations(&task1);
// Call DoWork immediately after the previous call. Expect a new task
// to be run.
EXPECT_CALL(task2, Run()).Times(1);
EXPECT_CALL(*message_pump_, ScheduleDelayedWork(Seconds(20)));
EXPECT_TRUE(thread_controller_.DoWork());
testing::Mock::VerifyAndClearExpectations(message_pump_);
testing::Mock::VerifyAndClearExpectations(&task2);
// Call DoDelayedWork for the last task and expect to be told
// about the lack of further delayed work (next run time being TimeTicks()).
clock_.SetNowTicks(Seconds(21));
EXPECT_CALL(task3, Run()).Times(1);
EXPECT_TRUE(thread_controller_.DoDelayedWork(&next_run_time));
EXPECT_EQ(next_run_time, TimeTicks());
testing::Mock::VerifyAndClearExpectations(message_pump_);
testing::Mock::VerifyAndClearExpectations(&task3);
}
TEST_F(ThreadControllerWithMessagePumpTest, SetNextDelayedDoWork) {
EXPECT_CALL(*message_pump_, ScheduleDelayedWork(Seconds(123)));
LazyNow lazy_now(&clock_);
thread_controller_.SetNextDelayedDoWork(&lazy_now, Seconds(123));
}
TEST_F(ThreadControllerWithMessagePumpTest, SetNextDelayedDoWork_CapAtOneDay) {
EXPECT_CALL(*message_pump_, ScheduleDelayedWork(Days(1)));
LazyNow lazy_now(&clock_);
thread_controller_.SetNextDelayedDoWork(&lazy_now, Days(2));
}
TEST_F(ThreadControllerWithMessagePumpTest, DelayedWork_CapAtOneDay) {
MockCallback<OnceClosure> task1;
task_source_.AddTask(PendingTask(FROM_HERE, task1.Get(), Days(10)));
EXPECT_CALL(*message_pump_, ScheduleDelayedWork(Days(1)));
EXPECT_FALSE(thread_controller_.DoWork());
}
TEST_F(ThreadControllerWithMessagePumpTest, NestedExecution) {
// This test posts three immediate tasks. The first creates a nested RunLoop
// and the test expects that the second and third tasks are run outside of
// the nested loop.
std::vector<std::string> log;
ThreadTaskRunnerHandle handle(MakeRefCounted<FakeTaskRunner>());
EXPECT_CALL(*message_pump_, Run(_))
.WillOnce(Invoke([&log, this](MessagePump::Delegate* delegate) {
log.push_back("entering top-level runloop");
EXPECT_EQ(delegate, &thread_controller_);
EXPECT_TRUE(delegate->DoWork());
EXPECT_TRUE(delegate->DoWork());
EXPECT_TRUE(delegate->DoWork());
EXPECT_FALSE(delegate->DoWork());
log.push_back("exiting top-level runloop");
}))
.WillOnce(Invoke([&log, this](MessagePump::Delegate* delegate) {
log.push_back("entering nested runloop");
EXPECT_EQ(delegate, &thread_controller_);
EXPECT_FALSE(thread_controller_.IsTaskExecutionAllowed());
EXPECT_FALSE(delegate->DoWork());
log.push_back("exiting nested runloop");
}));
task_source_.AddTask(
PendingTask(FROM_HERE,
base::BindOnce(
[](std::vector<std::string>* log,
ThreadControllerForTest* controller) {
EXPECT_FALSE(controller->IsTaskExecutionAllowed());
log->push_back("task1");
RunLoop().Run();
},
&log, &thread_controller_),
TimeTicks()));
task_source_.AddTask(
PendingTask(FROM_HERE,
base::BindOnce(
[](std::vector<std::string>* log,
ThreadControllerForTest* controller) {
EXPECT_FALSE(controller->IsTaskExecutionAllowed());
log->push_back("task2");
},
&log, &thread_controller_),
TimeTicks()));
task_source_.AddTask(
PendingTask(FROM_HERE,
base::BindOnce(
[](std::vector<std::string>* log,
ThreadControllerForTest* controller) {
EXPECT_FALSE(controller->IsTaskExecutionAllowed());
log->push_back("task3");
},
&log, &thread_controller_),
TimeTicks()));
EXPECT_TRUE(thread_controller_.IsTaskExecutionAllowed());
RunLoop().Run();
EXPECT_THAT(log,
ElementsAre("entering top-level runloop", "task1",
"entering nested runloop", "exiting nested runloop",
"task2", "task3", "exiting top-level runloop"));
testing::Mock::VerifyAndClearExpectations(message_pump_);
}
TEST_F(ThreadControllerWithMessagePumpTest,
NestedExecutionWithApplicationTasks) {
// THis test is similar to the previous one, but execution is explicitly
// allowed (by specifying appropriate RunLoop type), and tasks are run inside
// nested runloop.
std::vector<std::string> log;
ThreadTaskRunnerHandle handle(MakeRefCounted<FakeTaskRunner>());
EXPECT_CALL(*message_pump_, Run(_))
.WillOnce(Invoke([&log, this](MessagePump::Delegate* delegate) {
log.push_back("entering top-level runloop");
EXPECT_EQ(delegate, &thread_controller_);
EXPECT_TRUE(delegate->DoWork());
EXPECT_FALSE(delegate->DoWork());
log.push_back("exiting top-level runloop");
}))
.WillOnce(Invoke([&log, this](MessagePump::Delegate* delegate) {
log.push_back("entering nested runloop");
EXPECT_EQ(delegate, &thread_controller_);
EXPECT_TRUE(thread_controller_.IsTaskExecutionAllowed());
EXPECT_TRUE(delegate->DoWork());
EXPECT_TRUE(delegate->DoWork());
EXPECT_FALSE(delegate->DoWork());
log.push_back("exiting nested runloop");
}));
// An extra schedule work will be called when entering a nested runloop.
EXPECT_CALL(*message_pump_, ScheduleWork());
task_source_.AddTask(
PendingTask(FROM_HERE,
base::BindOnce(
[](std::vector<std::string>* log,
ThreadControllerForTest* controller) {
EXPECT_FALSE(controller->IsTaskExecutionAllowed());
log->push_back("task1");
RunLoop(RunLoop::Type::kNestableTasksAllowed).Run();
},
&log, &thread_controller_),
TimeTicks()));
task_source_.AddTask(
PendingTask(FROM_HERE,
base::BindOnce(
[](std::vector<std::string>* log,
ThreadControllerForTest* controller) {
EXPECT_FALSE(controller->IsTaskExecutionAllowed());
log->push_back("task2");
},
&log, &thread_controller_),
TimeTicks()));
task_source_.AddTask(
PendingTask(FROM_HERE,
base::BindOnce(
[](std::vector<std::string>* log,
ThreadControllerForTest* controller) {
EXPECT_FALSE(controller->IsTaskExecutionAllowed());
log->push_back("task3");
},
&log, &thread_controller_),
TimeTicks()));
EXPECT_TRUE(thread_controller_.IsTaskExecutionAllowed());
RunLoop().Run();
EXPECT_THAT(
log, ElementsAre("entering top-level runloop", "task1",
"entering nested runloop", "task2", "task3",
"exiting nested runloop", "exiting top-level runloop"));
testing::Mock::VerifyAndClearExpectations(message_pump_);
}
TEST_F(ThreadControllerWithMessagePumpTest, SetDefaultTaskRunner) {
scoped_refptr<SingleThreadTaskRunner> task_runner1 =
MakeRefCounted<FakeTaskRunner>();
thread_controller_.SetDefaultTaskRunner(task_runner1);
EXPECT_EQ(task_runner1, ThreadTaskRunnerHandle::Get());
// Check that we are correctly supporting overriding.
scoped_refptr<SingleThreadTaskRunner> task_runner2 =
MakeRefCounted<FakeTaskRunner>();
thread_controller_.SetDefaultTaskRunner(task_runner2);
EXPECT_EQ(task_runner2, ThreadTaskRunnerHandle::Get());
}
TEST_F(ThreadControllerWithMessagePumpTest, EnsureWorkScheduled) {
task_source_.AddTask(PendingTask(FROM_HERE, DoNothing(), TimeTicks()));
// Ensure that the first ScheduleWork() call results in the pump being called.
EXPECT_CALL(*message_pump_, ScheduleWork());
thread_controller_.ScheduleWork();
testing::Mock::VerifyAndClearExpectations(message_pump_);
// Ensure that the subsequent ScheduleWork() does not call the pump.
thread_controller_.ScheduleWork();
testing::Mock::VerifyAndClearExpectations(message_pump_);
// Ensure that EnsureWorkScheduled() forces a call to a pump.
EXPECT_CALL(*message_pump_, ScheduleWork());
thread_controller_.EnsureWorkScheduled();
testing::Mock::VerifyAndClearExpectations(message_pump_);
}
TEST_F(ThreadControllerWithMessagePumpTest, WorkBatching) {
ThreadTaskRunnerHandle handle(MakeRefCounted<FakeTaskRunner>());
const int kBatchSize = 5;
thread_controller_.SetWorkBatchSize(kBatchSize);
int task_count = 0;
EXPECT_CALL(*message_pump_, Run(_))
.WillOnce(Invoke([&](MessagePump::Delegate* delegate) {
EXPECT_TRUE(delegate->DoWork());
EXPECT_EQ(5, task_count);
}));
for (int i = 0; i < kBatchSize; i++) {
task_source_.AddTask(PendingTask(
FROM_HERE, BindLambdaForTesting([&] { task_count++; }), TimeTicks()));
}
RunLoop run_loop;
run_loop.Run();
testing::Mock::VerifyAndClearExpectations(message_pump_);
}
TEST_F(ThreadControllerWithMessagePumpTest, QuitInterruptsBatch) {
// This check ensures that RunLoop::Quit() makes us drop back to a work batch
// size of 1.
ThreadTaskRunnerHandle handle(MakeRefCounted<FakeTaskRunner>());
const int kBatchSize = 5;
thread_controller_.SetWorkBatchSize(kBatchSize);
int task_count = 0;
EXPECT_CALL(*message_pump_, Run(_))
.WillOnce(Invoke([&](MessagePump::Delegate* delegate) {
TimeTicks next_time;
EXPECT_TRUE(delegate->DoWork());
EXPECT_EQ(1, task_count);
// Somewhat counter-intuitive, but if the pump keeps calling us after
// Quit(), the delegate should still run tasks as normally. This is
// needed to support nested OS-level runloops that still pump
// application tasks (e.g., showing a popup menu on Mac).
EXPECT_TRUE(delegate->DoDelayedWork(&next_time));
EXPECT_EQ(2, task_count);
EXPECT_TRUE(delegate->DoWork());
EXPECT_EQ(3, task_count);
}));
EXPECT_CALL(*message_pump_, Quit());
RunLoop run_loop;
for (int i = 0; i < kBatchSize; i++) {
task_source_.AddTask(PendingTask(FROM_HERE, BindLambdaForTesting([&] {
if (!task_count++)
run_loop.Quit();
}),
TimeTicks()));
}
run_loop.Run();
testing::Mock::VerifyAndClearExpectations(message_pump_);
}
TEST_F(ThreadControllerWithMessagePumpTest, EarlyQuit) {
// This test ensures that a opt-of-runloop Quit() (which is possible with some
// pump implementations) doesn't affect the next RunLoop::Run call.
ThreadTaskRunnerHandle handle(MakeRefCounted<FakeTaskRunner>());
std::vector<std::string> log;
// This quit should be a no-op for future calls.
EXPECT_CALL(*message_pump_, Quit());
thread_controller_.Quit();
testing::Mock::VerifyAndClearExpectations(message_pump_);
EXPECT_CALL(*message_pump_, Run(_))
.WillOnce(Invoke([this](MessagePump::Delegate* delegate) {
EXPECT_EQ(delegate, &thread_controller_);
EXPECT_TRUE(delegate->DoWork());
EXPECT_TRUE(delegate->DoWork());
EXPECT_FALSE(delegate->DoWork());
}));
RunLoop run_loop;
task_source_.AddTask(PendingTask(
FROM_HERE,
base::BindOnce(
[](std::vector<std::string>* log) { log->push_back("task1"); }, &log),
TimeTicks()));
task_source_.AddTask(PendingTask(
FROM_HERE,
base::BindOnce(
[](std::vector<std::string>* log) { log->push_back("task2"); }, &log),
TimeTicks()));
run_loop.RunUntilIdle();
EXPECT_THAT(log, ElementsAre("task1", "task2"));
testing::Mock::VerifyAndClearExpectations(message_pump_);
}
} // namespace sequence_manager
} // namespace base