blob: f9e667914e91beddaf1c6091bc944a19cf287a0e [file] [log] [blame]
// Copyright 2015 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 "third_party/blink/renderer/platform/scheduler/child/idle_helper.h"
#include <utility>
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/test/simple_test_tick_clock.h"
#include "components/viz/test/ordered_simple_task_runner.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/scheduler/base/real_time_domain.h"
#include "third_party/blink/renderer/platform/scheduler/base/task_queue.h"
#include "third_party/blink/renderer/platform/scheduler/base/task_queue_manager.h"
#include "third_party/blink/renderer/platform/scheduler/common/scheduler_helper.h"
#include "third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_helper.h"
#include "third_party/blink/renderer/platform/scheduler/test/task_queue_manager_for_test.h"
using testing::_;
using testing::AnyNumber;
using testing::AtLeast;
using testing::Exactly;
using testing::Invoke;
using testing::Return;
namespace blink {
namespace scheduler {
// To avoid symbol collisions in jumbo builds.
namespace idle_helper_unittest {
void AppendToVectorTestTask(std::vector<std::string>* vector,
std::string value) {
vector->push_back(value);
}
void AppendToVectorIdleTestTask(std::vector<std::string>* vector,
std::string value,
base::TimeTicks deadline) {
AppendToVectorTestTask(vector, value);
}
void NullTask() {}
void NullIdleTask(base::TimeTicks deadline) {}
void AppendToVectorReentrantTask(base::SingleThreadTaskRunner* task_runner,
std::vector<int>* vector,
int* reentrant_count,
int max_reentrant_count) {
vector->push_back((*reentrant_count)++);
if (*reentrant_count < max_reentrant_count) {
task_runner->PostTask(FROM_HERE,
base::BindOnce(AppendToVectorReentrantTask,
base::Unretained(task_runner), vector,
reentrant_count, max_reentrant_count));
}
}
void IdleTestTask(int* run_count,
base::TimeTicks* deadline_out,
base::TimeTicks deadline) {
(*run_count)++;
*deadline_out = deadline;
}
int g_max_idle_task_reposts = 2;
void RepostingIdleTestTask(SingleThreadIdleTaskRunner* idle_task_runner,
int* run_count,
base::TimeTicks* deadline_out,
base::TimeTicks deadline) {
if ((*run_count + 1) < g_max_idle_task_reposts) {
idle_task_runner->PostIdleTask(
FROM_HERE, base::BindOnce(&RepostingIdleTestTask,
base::Unretained(idle_task_runner), run_count,
deadline_out));
}
*deadline_out = deadline;
(*run_count)++;
}
void RepostingUpdateClockIdleTestTask(
SingleThreadIdleTaskRunner* idle_task_runner,
int* run_count,
base::SimpleTestTickClock* clock,
base::TimeDelta advance_time,
std::vector<base::TimeTicks>* deadlines,
base::TimeTicks deadline) {
if ((*run_count + 1) < g_max_idle_task_reposts) {
idle_task_runner->PostIdleTask(
FROM_HERE, base::BindOnce(&RepostingUpdateClockIdleTestTask,
base::Unretained(idle_task_runner), run_count,
clock, advance_time, deadlines));
}
deadlines->push_back(deadline);
(*run_count)++;
clock->Advance(advance_time);
}
void RepeatingTask(base::SingleThreadTaskRunner* task_runner,
int num_repeats,
base::TimeDelta delay) {
if (num_repeats > 1) {
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&RepeatingTask, base::Unretained(task_runner),
num_repeats - 1, delay),
delay);
}
}
void UpdateClockIdleTestTask(base::SimpleTestTickClock* clock,
int* run_count,
base::TimeTicks set_time,
base::TimeTicks deadline) {
clock->Advance(set_time - clock->NowTicks());
(*run_count)++;
}
void UpdateClockToDeadlineIdleTestTask(base::SimpleTestTickClock* clock,
int* run_count,
base::TimeTicks deadline) {
UpdateClockIdleTestTask(clock, run_count, deadline, deadline);
}
void EndIdlePeriodIdleTask(IdleHelper* idle_helper, base::TimeTicks deadline) {
idle_helper->EndIdlePeriod();
}
void ShutdownIdleTask(IdleHelper* helper,
bool* shutdown_task_run,
base::TimeTicks deadline) {
*shutdown_task_run = true;
helper->Shutdown();
}
// RAII helper class to enable auto advancing of time inside mock task runner.
// Automatically disables auto-advancement when destroyed.
class ScopedAutoAdvanceNowEnabler {
public:
ScopedAutoAdvanceNowEnabler(
scoped_refptr<cc::OrderedSimpleTaskRunner> task_runner)
: task_runner_(task_runner) {
if (task_runner_)
task_runner_->SetAutoAdvanceNowToPendingTasks(true);
}
~ScopedAutoAdvanceNowEnabler() {
if (task_runner_)
task_runner_->SetAutoAdvanceNowToPendingTasks(false);
}
private:
scoped_refptr<cc::OrderedSimpleTaskRunner> task_runner_;
DISALLOW_COPY_AND_ASSIGN(ScopedAutoAdvanceNowEnabler);
};
class IdleHelperForTest : public IdleHelper, public IdleHelper::Delegate {
public:
explicit IdleHelperForTest(
SchedulerHelper* scheduler_helper,
base::TimeDelta required_quiescence_duration_before_long_idle_period,
scoped_refptr<TaskQueue> idle_task_runner)
: IdleHelper(scheduler_helper,
this,
"TestSchedulerIdlePeriod",
required_quiescence_duration_before_long_idle_period,
idle_task_runner) {}
~IdleHelperForTest() override = default;
// IdleHelper::Delegate implementation:
MOCK_METHOD2(CanEnterLongIdlePeriod,
bool(base::TimeTicks now,
base::TimeDelta* next_long_idle_period_delay_out));
MOCK_METHOD0(IsNotQuiescent, void());
MOCK_METHOD0(OnIdlePeriodStarted, void());
MOCK_METHOD0(OnIdlePeriodEnded, void());
MOCK_METHOD1(OnPendingTasksChanged, void(bool has_tasks));
};
class BaseIdleHelperTest : public testing::Test {
public:
BaseIdleHelperTest(
base::MessageLoop* message_loop,
base::TimeDelta required_quiescence_duration_before_long_idle_period)
: mock_task_runner_(
message_loop ? nullptr
: new cc::OrderedSimpleTaskRunner(&clock_, false)),
message_loop_(message_loop) {
std::unique_ptr<TaskQueueManager> task_queue_manager =
TaskQueueManagerForTest::Create(
message_loop,
message_loop ? message_loop->task_runner() : mock_task_runner_,
&clock_);
task_queue_manager_ = task_queue_manager.get();
scheduler_helper_ = std::make_unique<WorkerSchedulerHelper>(
std::move(task_queue_manager), nullptr);
idle_helper_ = std::make_unique<IdleHelperForTest>(
scheduler_helper_.get(),
required_quiescence_duration_before_long_idle_period,
scheduler_helper_->NewTaskQueue(TaskQueue::Spec("idle_test")));
default_task_runner_ = scheduler_helper_->DefaultWorkerTaskQueue();
idle_task_runner_ = idle_helper_->IdleTaskRunner();
clock_.Advance(base::TimeDelta::FromMicroseconds(5000));
}
~BaseIdleHelperTest() override = default;
void SetUp() override {
EXPECT_CALL(*idle_helper_, OnIdlePeriodStarted()).Times(AnyNumber());
EXPECT_CALL(*idle_helper_, OnIdlePeriodEnded()).Times(AnyNumber());
EXPECT_CALL(*idle_helper_, CanEnterLongIdlePeriod(_, _))
.Times(AnyNumber())
.WillRepeatedly(Return(true));
EXPECT_CALL(*idle_helper_, OnPendingTasksChanged(_)).Times(AnyNumber());
}
void TearDown() override {
EXPECT_CALL(*idle_helper_, OnIdlePeriodEnded()).Times(AnyNumber());
idle_helper_->Shutdown();
DCHECK(!mock_task_runner_.get() || !message_loop_.get());
if (mock_task_runner_.get()) {
// Check that all tests stop posting tasks.
mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true);
while (mock_task_runner_->RunUntilIdle()) {
}
} else {
base::RunLoop().RunUntilIdle();
}
}
TaskQueueManager* task_queue_manager() const { return task_queue_manager_; }
void RunUntilIdle() {
// Only one of mock_task_runner_ or message_loop_ should be set.
DCHECK(!mock_task_runner_.get() || !message_loop_.get());
if (mock_task_runner_.get()) {
mock_task_runner_->RunUntilIdle();
} else {
base::RunLoop().RunUntilIdle();
}
}
template <typename E>
static void CallForEachEnumValue(E first,
E last,
const char* (*function)(E)) {
for (E val = first; val < last;
val = static_cast<E>(static_cast<int>(val) + 1)) {
(*function)(val);
}
}
static void CheckAllTaskQueueIdToString() {
CallForEachEnumValue<IdleHelper::IdlePeriodState>(
IdleHelper::IdlePeriodState::kFirstIdlePeriodState,
IdleHelper::IdlePeriodState::kIdlePeriodStateCount,
&IdleHelper::IdlePeriodStateToString);
}
bool IsInIdlePeriod() const {
return idle_helper_->IsInIdlePeriod(
idle_helper_->SchedulerIdlePeriodState());
}
protected:
static base::TimeDelta maximum_idle_period_duration() {
return base::TimeDelta::FromMilliseconds(
IdleHelper::kMaximumIdlePeriodMillis);
}
static base::TimeDelta retry_enable_long_idle_period_delay() {
return base::TimeDelta::FromMilliseconds(
IdleHelper::kRetryEnableLongIdlePeriodDelayMillis);
}
static base::TimeDelta minimum_idle_period_duration() {
return base::TimeDelta::FromMilliseconds(
IdleHelper::kMinimumIdlePeriodDurationMillis);
}
base::TimeTicks CurrentIdleTaskDeadline() {
return idle_helper_->CurrentIdleTaskDeadline();
}
void CheckIdlePeriodStateIs(const char* expected) {
EXPECT_STREQ(expected, IdleHelper::IdlePeriodStateToString(
idle_helper_->SchedulerIdlePeriodState()));
}
const scoped_refptr<TaskQueue>& idle_queue() const {
return idle_helper_->idle_queue_;
}
base::SimpleTestTickClock clock_;
// Only one of mock_task_runner_ or message_loop_ will be set.
scoped_refptr<cc::OrderedSimpleTaskRunner> mock_task_runner_;
std::unique_ptr<base::MessageLoop> message_loop_;
std::unique_ptr<WorkerSchedulerHelper> scheduler_helper_;
TaskQueueManager* task_queue_manager_; // Owned by scheduler_helper_.
std::unique_ptr<IdleHelperForTest> idle_helper_;
scoped_refptr<base::SingleThreadTaskRunner> default_task_runner_;
scoped_refptr<SingleThreadIdleTaskRunner> idle_task_runner_;
DISALLOW_COPY_AND_ASSIGN(BaseIdleHelperTest);
};
class IdleHelperTest : public BaseIdleHelperTest {
public:
IdleHelperTest() : BaseIdleHelperTest(nullptr, base::TimeDelta()) {}
~IdleHelperTest() override = default;
private:
DISALLOW_COPY_AND_ASSIGN(IdleHelperTest);
};
TEST_F(IdleHelperTest, TestPostIdleTask) {
int run_count = 0;
base::TimeTicks expected_deadline =
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(2300);
base::TimeTicks deadline_in_task;
clock_.Advance(base::TimeDelta::FromMilliseconds(100));
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
RunUntilIdle();
EXPECT_EQ(0, run_count);
idle_helper_->StartIdlePeriod(IdleHelper::IdlePeriodState::kInShortIdlePeriod,
clock_.NowTicks(), expected_deadline);
RunUntilIdle();
EXPECT_EQ(1, run_count);
EXPECT_EQ(expected_deadline, deadline_in_task);
}
TEST_F(IdleHelperTest, TestPostIdleTask_EndIdlePeriod) {
int run_count = 0;
base::TimeTicks deadline_in_task;
clock_.Advance(base::TimeDelta::FromMilliseconds(100));
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
RunUntilIdle();
EXPECT_EQ(0, run_count);
idle_helper_->StartIdlePeriod(
IdleHelper::IdlePeriodState::kInShortIdlePeriod, clock_.NowTicks(),
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(10));
idle_helper_->EndIdlePeriod();
RunUntilIdle();
EXPECT_EQ(0, run_count);
}
TEST_F(IdleHelperTest, TestRepostingIdleTask) {
base::TimeTicks actual_deadline;
int run_count = 0;
g_max_idle_task_reposts = 2;
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&RepostingIdleTestTask,
base::RetainedRef(idle_task_runner_),
&run_count, &actual_deadline));
idle_helper_->StartIdlePeriod(
IdleHelper::IdlePeriodState::kInShortIdlePeriod, clock_.NowTicks(),
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(10));
RunUntilIdle();
EXPECT_EQ(1, run_count);
// Reposted tasks shouldn't run until next idle period.
RunUntilIdle();
EXPECT_EQ(1, run_count);
idle_helper_->StartIdlePeriod(
IdleHelper::IdlePeriodState::kInShortIdlePeriod, clock_.NowTicks(),
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(10));
RunUntilIdle();
EXPECT_EQ(2, run_count);
}
TEST_F(IdleHelperTest, TestIdleTaskExceedsDeadline) {
int run_count = 0;
// Post two UpdateClockToDeadlineIdleTestTask tasks.
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::BindOnce(&UpdateClockToDeadlineIdleTestTask, &clock_, &run_count));
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::BindOnce(&UpdateClockToDeadlineIdleTestTask, &clock_, &run_count));
idle_helper_->StartIdlePeriod(
IdleHelper::IdlePeriodState::kInShortIdlePeriod, clock_.NowTicks(),
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(10));
RunUntilIdle();
// Only the first idle task should execute since it's used up the deadline.
EXPECT_EQ(1, run_count);
idle_helper_->EndIdlePeriod();
idle_helper_->StartIdlePeriod(
IdleHelper::IdlePeriodState::kInShortIdlePeriod, clock_.NowTicks(),
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(10));
RunUntilIdle();
// Second task should be run on the next idle period.
EXPECT_EQ(2, run_count);
}
class IdleHelperTestWithIdlePeriodObserver : public BaseIdleHelperTest {
public:
IdleHelperTestWithIdlePeriodObserver()
: BaseIdleHelperTest(nullptr, base::TimeDelta()) {}
~IdleHelperTestWithIdlePeriodObserver() override = default;
void SetUp() override {
// Don't set expectations on IdleHelper::Delegate.
}
void ExpectIdlePeriodStartsButNeverEnds() {
EXPECT_CALL(*idle_helper_, OnIdlePeriodStarted()).Times(1);
EXPECT_CALL(*idle_helper_, OnIdlePeriodEnded()).Times(0);
}
void ExpectIdlePeriodStartsAndEnds(const testing::Cardinality& cardinality) {
EXPECT_CALL(*idle_helper_, OnIdlePeriodStarted()).Times(cardinality);
EXPECT_CALL(*idle_helper_, OnIdlePeriodEnded()).Times(cardinality);
}
private:
DISALLOW_COPY_AND_ASSIGN(IdleHelperTestWithIdlePeriodObserver);
};
TEST_F(IdleHelperTestWithIdlePeriodObserver, TestEnterButNotExitIdlePeriod) {
ExpectIdlePeriodStartsButNeverEnds();
idle_helper_->StartIdlePeriod(
IdleHelper::IdlePeriodState::kInShortIdlePeriod, clock_.NowTicks(),
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(10));
}
TEST_F(IdleHelperTestWithIdlePeriodObserver, TestEnterAndExitIdlePeriod) {
BaseIdleHelperTest* fixture = this;
ON_CALL(*idle_helper_, OnIdlePeriodStarted())
.WillByDefault(
Invoke([fixture]() { EXPECT_TRUE(fixture->IsInIdlePeriod()); }));
ON_CALL(*idle_helper_, OnIdlePeriodEnded())
.WillByDefault(
Invoke([fixture]() { EXPECT_FALSE(fixture->IsInIdlePeriod()); }));
ExpectIdlePeriodStartsAndEnds(Exactly(1));
idle_helper_->StartIdlePeriod(
IdleHelper::IdlePeriodState::kInShortIdlePeriod, clock_.NowTicks(),
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(10));
idle_helper_->EndIdlePeriod();
}
class IdleHelperWithMessageLoopTest : public BaseIdleHelperTest {
public:
IdleHelperWithMessageLoopTest()
: BaseIdleHelperTest(new base::MessageLoop(), base::TimeDelta()) {}
~IdleHelperWithMessageLoopTest() override = default;
void PostFromNestedRunloop(
std::vector<std::pair<SingleThreadIdleTaskRunner::IdleTask, bool>>*
tasks) {
base::MessageLoop::ScopedNestableTaskAllower allow(message_loop_.get());
for (std::pair<SingleThreadIdleTaskRunner::IdleTask, bool>& pair : *tasks) {
if (pair.second) {
idle_task_runner_->PostIdleTask(FROM_HERE, std::move(pair.first));
} else {
idle_task_runner_->PostNonNestableIdleTask(FROM_HERE,
std::move(pair.first));
}
}
idle_helper_->StartIdlePeriod(
IdleHelper::IdlePeriodState::kInShortIdlePeriod, clock_.NowTicks(),
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(10));
base::RunLoop().RunUntilIdle();
}
void SetUp() override {
EXPECT_CALL(*idle_helper_, OnIdlePeriodStarted()).Times(AnyNumber());
EXPECT_CALL(*idle_helper_, OnIdlePeriodEnded()).Times(AnyNumber());
}
private:
DISALLOW_COPY_AND_ASSIGN(IdleHelperWithMessageLoopTest);
};
TEST_F(IdleHelperWithMessageLoopTest,
NonNestableIdleTaskDoesntExecuteInNestedLoop) {
std::vector<std::string> order;
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::BindOnce(&AppendToVectorIdleTestTask, &order, std::string("1")));
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::BindOnce(&AppendToVectorIdleTestTask, &order, std::string("2")));
std::vector<std::pair<SingleThreadIdleTaskRunner::IdleTask, bool>>
tasks_to_post_from_nested_loop;
tasks_to_post_from_nested_loop.push_back(std::make_pair(
base::BindOnce(&AppendToVectorIdleTestTask, &order, std::string("3")),
false));
tasks_to_post_from_nested_loop.push_back(std::make_pair(
base::BindOnce(&AppendToVectorIdleTestTask, &order, std::string("4")),
true));
tasks_to_post_from_nested_loop.push_back(std::make_pair(
base::BindOnce(&AppendToVectorIdleTestTask, &order, std::string("5")),
true));
default_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&IdleHelperWithMessageLoopTest::PostFromNestedRunloop,
base::Unretained(this),
base::Unretained(&tasks_to_post_from_nested_loop)));
idle_helper_->StartIdlePeriod(
IdleHelper::IdlePeriodState::kInShortIdlePeriod, clock_.NowTicks(),
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(10));
RunUntilIdle();
// Note we expect task 3 to run last because it's non-nestable.
EXPECT_THAT(order, testing::ElementsAre(std::string("1"), std::string("2"),
std::string("4"), std::string("5"),
std::string("3")));
}
TEST_F(IdleHelperTestWithIdlePeriodObserver, TestLongIdlePeriod) {
base::TimeTicks expected_deadline =
clock_.NowTicks() + maximum_idle_period_duration();
base::TimeTicks deadline_in_task;
int run_count = 0;
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
EXPECT_CALL(*idle_helper_, CanEnterLongIdlePeriod(_, _))
.Times(1)
.WillRepeatedly(Return(true));
ExpectIdlePeriodStartsButNeverEnds();
RunUntilIdle();
EXPECT_EQ(0, run_count); // Shouldn't run yet as no idle period.
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
EXPECT_EQ(1, run_count); // Should have run in a long idle time.
EXPECT_EQ(expected_deadline, deadline_in_task);
}
TEST_F(IdleHelperTest, TestLongIdlePeriodWithPendingDelayedTask) {
base::TimeDelta pending_task_delay = base::TimeDelta::FromMilliseconds(30);
base::TimeTicks expected_deadline = clock_.NowTicks() + pending_task_delay;
base::TimeTicks deadline_in_task;
int run_count = 0;
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
default_task_runner_->PostDelayedTask(FROM_HERE, base::BindOnce(&NullTask),
pending_task_delay);
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
EXPECT_EQ(1, run_count); // Should have run in a long idle time.
EXPECT_EQ(expected_deadline, deadline_in_task);
}
TEST_F(IdleHelperTest, TestLongIdlePeriodWithLatePendingDelayedTask) {
base::TimeDelta pending_task_delay = base::TimeDelta::FromMilliseconds(10);
base::TimeTicks deadline_in_task;
int run_count = 0;
default_task_runner_->PostDelayedTask(FROM_HERE, base::BindOnce(&NullTask),
pending_task_delay);
// Advance clock until after delayed task was meant to be run.
clock_.Advance(base::TimeDelta::FromMilliseconds(20));
// Post an idle task and then EnableLongIdlePeriod. Since there is a late
// pending delayed task this shouldn't actually start an idle period.
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
EXPECT_EQ(0, run_count);
// After the delayed task has been run we should trigger an idle period.
clock_.Advance(maximum_idle_period_duration());
RunUntilIdle();
EXPECT_EQ(1, run_count);
}
TEST_F(IdleHelperTestWithIdlePeriodObserver, TestLongIdlePeriodRepeating) {
mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true);
std::vector<base::TimeTicks> actual_deadlines;
int run_count = 0;
EXPECT_CALL(*idle_helper_, CanEnterLongIdlePeriod(_, _))
.Times(4)
.WillRepeatedly(Return(true));
ExpectIdlePeriodStartsAndEnds(AtLeast(2));
g_max_idle_task_reposts = 3;
base::TimeTicks clock_before(clock_.NowTicks());
base::TimeDelta idle_task_runtime(base::TimeDelta::FromMilliseconds(10));
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::BindOnce(&RepostingUpdateClockIdleTestTask,
base::RetainedRef(idle_task_runner_), &run_count, &clock_,
idle_task_runtime, &actual_deadlines));
// Check each idle task runs in their own idle period.
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
EXPECT_EQ(3, run_count);
EXPECT_THAT(
actual_deadlines,
testing::ElementsAre(clock_before + maximum_idle_period_duration(),
clock_before + 2 * maximum_idle_period_duration(),
clock_before + 3 * maximum_idle_period_duration()));
g_max_idle_task_reposts = 5;
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::BindOnce(&RepostingUpdateClockIdleTestTask,
base::RetainedRef(idle_task_runner_), &run_count, &clock_,
idle_task_runtime, &actual_deadlines));
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&EndIdlePeriodIdleTask,
base::Unretained(idle_helper_.get())));
// Ensure that reposting tasks stop after EndIdlePeriod is called.
RunUntilIdle();
EXPECT_EQ(4, run_count);
}
TEST_F(IdleHelperTestWithIdlePeriodObserver,
TestLongIdlePeriodWhenNotCanEnterLongIdlePeriod) {
base::TimeDelta delay = base::TimeDelta::FromMilliseconds(1000);
base::TimeDelta half_delay = base::TimeDelta::FromMilliseconds(500);
base::TimeTicks delay_over = clock_.NowTicks() + delay;
base::TimeTicks deadline_in_task;
int run_count = 0;
ON_CALL(*idle_helper_, CanEnterLongIdlePeriod(_, _))
.WillByDefault(
Invoke([delay, delay_over](
base::TimeTicks now,
base::TimeDelta* next_long_idle_period_delay_out) {
if (now >= delay_over)
return true;
*next_long_idle_period_delay_out = delay;
return false;
}));
EXPECT_CALL(*idle_helper_, CanEnterLongIdlePeriod(_, _)).Times(2);
EXPECT_CALL(*idle_helper_, OnIdlePeriodStarted()).Times(AnyNumber());
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
// Make sure Idle tasks don't run until the delay has occurred.
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
EXPECT_EQ(0, run_count);
clock_.Advance(half_delay);
RunUntilIdle();
EXPECT_EQ(0, run_count);
// Delay is finished, idle task should run.
clock_.Advance(half_delay);
RunUntilIdle();
EXPECT_EQ(1, run_count);
}
TEST_F(IdleHelperTest,
TestLongIdlePeriodDoesNotImmediatelyRestartIfMaxDeadline) {
ScopedAutoAdvanceNowEnabler advance_now(mock_task_runner_);
std::vector<base::TimeTicks> actual_deadlines;
int run_count = 0;
base::TimeTicks clock_before(clock_.NowTicks());
base::TimeDelta idle_task_runtime(base::TimeDelta::FromMilliseconds(10));
// The second idle period should happen immediately after the first the
// they have max deadlines.
g_max_idle_task_reposts = 2;
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::BindOnce(&RepostingUpdateClockIdleTestTask,
base::RetainedRef(idle_task_runner_), &run_count, &clock_,
idle_task_runtime, &actual_deadlines));
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
EXPECT_EQ(2, run_count);
EXPECT_THAT(
actual_deadlines,
testing::ElementsAre(clock_before + maximum_idle_period_duration(),
clock_before + 2 * maximum_idle_period_duration()));
}
TEST_F(IdleHelperTest, TestLongIdlePeriodRestartWaitsIfNotMaxDeadline) {
base::TimeTicks actual_deadline;
int run_count = 0;
base::TimeDelta pending_task_delay(base::TimeDelta::FromMilliseconds(20));
base::TimeDelta idle_task_duration(base::TimeDelta::FromMilliseconds(10));
base::TimeTicks expected_deadline(clock_.NowTicks() + pending_task_delay +
maximum_idle_period_duration() +
retry_enable_long_idle_period_delay());
// Post delayed task to ensure idle period doesn't have a max deadline.
default_task_runner_->PostDelayedTask(FROM_HERE, base::BindOnce(&NullTask),
pending_task_delay);
g_max_idle_task_reposts = 2;
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&RepostingIdleTestTask,
base::RetainedRef(idle_task_runner_),
&run_count, &actual_deadline));
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
EXPECT_EQ(1, run_count);
clock_.Advance(idle_task_duration);
// Next idle period shouldn't happen until the pending task has been run.
RunUntilIdle();
EXPECT_EQ(1, run_count);
// Once the pending task is run the new idle period should start.
clock_.Advance(pending_task_delay - idle_task_duration);
// Since the idle period tried to start before the pending task ran we have to
// wait for the idle helper to retry starting the long idle period.
clock_.Advance(retry_enable_long_idle_period_delay());
RunUntilIdle();
EXPECT_EQ(2, run_count);
EXPECT_EQ(expected_deadline, actual_deadline);
}
TEST_F(IdleHelperTest, TestLongIdlePeriodPaused) {
mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true);
std::vector<base::TimeTicks> actual_deadlines;
int run_count = 0;
// If there are no idle tasks posted we should start in the paused state.
idle_helper_->EnableLongIdlePeriod();
CheckIdlePeriodStateIs("in_long_idle_period_paused");
// There shouldn't be any delayed tasks posted by the idle helper when paused.
base::TimeTicks next_pending_delayed_task;
EXPECT_FALSE(scheduler_helper_->real_time_domain()->NextScheduledRunTime(
&next_pending_delayed_task));
// Posting a task should transition us to the an active state.
g_max_idle_task_reposts = 2;
base::TimeTicks clock_before(clock_.NowTicks());
base::TimeDelta idle_task_runtime(base::TimeDelta::FromMilliseconds(10));
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::BindOnce(&RepostingUpdateClockIdleTestTask,
base::RetainedRef(idle_task_runner_), &run_count, &clock_,
idle_task_runtime, &actual_deadlines));
RunUntilIdle();
EXPECT_EQ(2, run_count);
EXPECT_THAT(
actual_deadlines,
testing::ElementsAre(clock_before + maximum_idle_period_duration(),
clock_before + 2 * maximum_idle_period_duration()));
// Once all task have been run we should go back to the paused state.
CheckIdlePeriodStateIs("in_long_idle_period_paused");
EXPECT_FALSE(scheduler_helper_->real_time_domain()->NextScheduledRunTime(
&next_pending_delayed_task));
idle_helper_->EndIdlePeriod();
CheckIdlePeriodStateIs("not_in_idle_period");
}
TEST_F(IdleHelperTest, TestLongIdlePeriodWhenShutdown) {
base::TimeTicks deadline_in_task;
int run_count = 0;
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
idle_helper_->Shutdown();
// We shouldn't be able to enter a long idle period when shutdown
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
CheckIdlePeriodStateIs("not_in_idle_period");
EXPECT_EQ(0, run_count);
}
void TestCanExceedIdleDeadlineIfRequiredTask(IdleHelperForTest* idle_helper,
bool* can_exceed_idle_deadline_out,
int* run_count,
base::TimeTicks deadline) {
*can_exceed_idle_deadline_out =
idle_helper->CanExceedIdleDeadlineIfRequired();
(*run_count)++;
}
TEST_F(IdleHelperTest, CanExceedIdleDeadlineIfRequired) {
int run_count = 0;
bool can_exceed_idle_deadline = false;
// Should return false if not in an idle period.
EXPECT_FALSE(idle_helper_->CanExceedIdleDeadlineIfRequired());
// Should return false for short idle periods.
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&TestCanExceedIdleDeadlineIfRequiredTask,
idle_helper_.get(), &can_exceed_idle_deadline,
&run_count));
idle_helper_->StartIdlePeriod(
IdleHelper::IdlePeriodState::kInShortIdlePeriod, clock_.NowTicks(),
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(10));
RunUntilIdle();
EXPECT_EQ(1, run_count);
EXPECT_FALSE(can_exceed_idle_deadline);
// Should return false for a long idle period which is shortened due to a
// pending delayed task.
default_task_runner_->PostDelayedTask(FROM_HERE, base::BindOnce(&NullTask),
base::TimeDelta::FromMilliseconds(10));
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&TestCanExceedIdleDeadlineIfRequiredTask,
idle_helper_.get(), &can_exceed_idle_deadline,
&run_count));
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
EXPECT_EQ(2, run_count);
EXPECT_FALSE(can_exceed_idle_deadline);
// Next long idle period will be for the maximum time, so
// CanExceedIdleDeadlineIfRequired should return true.
clock_.Advance(maximum_idle_period_duration());
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&TestCanExceedIdleDeadlineIfRequiredTask,
idle_helper_.get(), &can_exceed_idle_deadline,
&run_count));
RunUntilIdle();
EXPECT_EQ(3, run_count);
EXPECT_TRUE(can_exceed_idle_deadline);
}
class IdleHelperWithQuiescencePeriodTest : public BaseIdleHelperTest {
public:
enum {
kQuiescenceDelayMs = 100,
kLongIdlePeriodMs = 50,
};
IdleHelperWithQuiescencePeriodTest()
: BaseIdleHelperTest(
nullptr,
base::TimeDelta::FromMilliseconds(kQuiescenceDelayMs)) {}
~IdleHelperWithQuiescencePeriodTest() override = default;
void SetUp() override {
EXPECT_CALL(*idle_helper_, OnIdlePeriodStarted()).Times(AnyNumber());
EXPECT_CALL(*idle_helper_, OnIdlePeriodEnded()).Times(AnyNumber());
EXPECT_CALL(*idle_helper_, CanEnterLongIdlePeriod(_, _))
.Times(AnyNumber())
.WillRepeatedly(Return(true));
EXPECT_CALL(*idle_helper_, IsNotQuiescent()).Times(AnyNumber());
}
void MakeNonQuiescent() {
// Run an arbitrary task so we're deemed to be not quiescent.
default_task_runner_->PostTask(FROM_HERE, base::BindOnce(NullTask));
RunUntilIdle();
}
private:
DISALLOW_COPY_AND_ASSIGN(IdleHelperWithQuiescencePeriodTest);
};
class IdleHelperWithQuiescencePeriodTestWithIdlePeriodObserver
: public IdleHelperWithQuiescencePeriodTest {
public:
IdleHelperWithQuiescencePeriodTestWithIdlePeriodObserver()
: IdleHelperWithQuiescencePeriodTest() {}
~IdleHelperWithQuiescencePeriodTestWithIdlePeriodObserver() override =
default;
void SetUp() override {
// Don't set expectations on IdleHelper::Delegate.
}
private:
DISALLOW_COPY_AND_ASSIGN(
IdleHelperWithQuiescencePeriodTestWithIdlePeriodObserver);
};
TEST_F(IdleHelperWithQuiescencePeriodTest,
LongIdlePeriodStartsImmediatelyIfQuiescent) {
base::TimeTicks actual_deadline;
int run_count = 0;
g_max_idle_task_reposts = 1;
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&RepostingIdleTestTask,
base::RetainedRef(idle_task_runner_),
&run_count, &actual_deadline));
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
EXPECT_EQ(1, run_count);
}
TEST_F(IdleHelperWithQuiescencePeriodTestWithIdlePeriodObserver,
LongIdlePeriodDoesNotStartsImmediatelyIfBusy) {
MakeNonQuiescent();
EXPECT_CALL(*idle_helper_, OnIdlePeriodStarted()).Times(0);
EXPECT_CALL(*idle_helper_, OnIdlePeriodEnded()).Times(0);
EXPECT_CALL(*idle_helper_, CanEnterLongIdlePeriod(_, _)).Times(0);
EXPECT_CALL(*idle_helper_, IsNotQuiescent()).Times(AtLeast(1));
base::TimeTicks actual_deadline;
int run_count = 0;
g_max_idle_task_reposts = 1;
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&RepostingIdleTestTask,
base::RetainedRef(idle_task_runner_),
&run_count, &actual_deadline));
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
EXPECT_EQ(0, run_count);
scheduler_helper_->Shutdown();
}
TEST_F(IdleHelperWithQuiescencePeriodTest,
LongIdlePeriodStartsAfterQuiescence) {
MakeNonQuiescent();
mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true);
// Run a repeating task so we're deemed to be busy for the next 400ms.
default_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&RepeatingTask,
base::Unretained(default_task_runner_.get()),
10, base::TimeDelta::FromMilliseconds(40)));
int run_count = 0;
// In this scenario EnableLongIdlePeriod deems us not to be quiescent 5x in
// a row.
base::TimeTicks expected_deadline =
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(
5 * kQuiescenceDelayMs + kLongIdlePeriodMs);
base::TimeTicks deadline_in_task;
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
EXPECT_EQ(1, run_count);
EXPECT_EQ(expected_deadline, deadline_in_task);
}
TEST_F(IdleHelperWithQuiescencePeriodTest,
QuescienceCheckedForAfterLongIdlePeriodEnds) {
mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true);
idle_task_runner_->PostIdleTask(FROM_HERE, base::BindOnce(&NullIdleTask));
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
// Post a normal task to make the scheduler non-quiescent.
default_task_runner_->PostTask(FROM_HERE, base::BindOnce(&NullTask));
RunUntilIdle();
// Post an idle task. The idle task won't run initially because the system is
// not judged to be quiescent, but should be run after the quiescence delay.
int run_count = 0;
base::TimeTicks deadline_in_task;
base::TimeTicks expected_deadline =
clock_.NowTicks() +
base::TimeDelta::FromMilliseconds(kQuiescenceDelayMs + kLongIdlePeriodMs);
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
EXPECT_EQ(1, run_count);
EXPECT_EQ(expected_deadline, deadline_in_task);
}
TEST_F(IdleHelperTest, NoShortIdlePeriodWhenDeadlineTooClose) {
int run_count = 0;
base::TimeTicks deadline_in_task;
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
base::TimeDelta half_a_ms(base::TimeDelta::FromMicroseconds(50));
base::TimeTicks less_than_min_deadline(
clock_.NowTicks() + minimum_idle_period_duration() - half_a_ms);
base::TimeTicks more_than_min_deadline(
clock_.NowTicks() + minimum_idle_period_duration() + half_a_ms);
idle_helper_->StartIdlePeriod(IdleHelper::IdlePeriodState::kInShortIdlePeriod,
clock_.NowTicks(), less_than_min_deadline);
RunUntilIdle();
EXPECT_EQ(0, run_count);
idle_helper_->StartIdlePeriod(IdleHelper::IdlePeriodState::kInShortIdlePeriod,
clock_.NowTicks(), more_than_min_deadline);
RunUntilIdle();
EXPECT_EQ(1, run_count);
}
TEST_F(IdleHelperTest, NoLongIdlePeriodWhenDeadlineTooClose) {
int run_count = 0;
base::TimeTicks deadline_in_task;
base::TimeDelta half_a_ms(base::TimeDelta::FromMicroseconds(50));
base::TimeDelta less_than_min_deadline_duration(
minimum_idle_period_duration() - half_a_ms);
base::TimeDelta more_than_min_deadline_duration(
minimum_idle_period_duration() + half_a_ms);
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
default_task_runner_->PostDelayedTask(FROM_HERE, base::BindOnce(&NullTask),
less_than_min_deadline_duration);
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
EXPECT_EQ(0, run_count);
idle_helper_->EndIdlePeriod();
clock_.Advance(maximum_idle_period_duration());
RunUntilIdle();
EXPECT_EQ(0, run_count);
default_task_runner_->PostDelayedTask(FROM_HERE, base::BindOnce(&NullTask),
more_than_min_deadline_duration);
idle_helper_->EnableLongIdlePeriod();
RunUntilIdle();
EXPECT_EQ(1, run_count);
}
TEST_F(IdleHelperWithQuiescencePeriodTest,
PendingEnableLongIdlePeriodNotRunAfterShutdown) {
MakeNonQuiescent();
bool shutdown_task_run = false;
int run_count = 0;
base::TimeTicks deadline_in_task;
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::BindOnce(&ShutdownIdleTask, base::Unretained(idle_helper_.get()),
&shutdown_task_run));
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
// Delayed call to IdleHelper::EnableLongIdlePeriod enables idle tasks.
idle_helper_->EnableLongIdlePeriod();
clock_.Advance(maximum_idle_period_duration() * 2.0);
mock_task_runner_->RunPendingTasks();
EXPECT_TRUE(shutdown_task_run);
EXPECT_EQ(0, run_count);
// Shutdown immediately after idle period started should prevent the idle
// task from running.
idle_helper_->Shutdown();
mock_task_runner_->RunUntilIdle();
EXPECT_EQ(0, run_count);
}
TEST_F(IdleHelperTest, TestPostDelayedIdleTask) {
int run_count = 0;
base::TimeTicks expected_deadline =
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(2300);
base::TimeTicks deadline_in_task;
// Posting a delayed idle task should not post anything on the underlying
// task queue until the delay is up.
idle_task_runner_->PostDelayedIdleTask(
FROM_HERE, base::TimeDelta::FromMilliseconds(200),
base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
EXPECT_EQ(0u, idle_queue()->GetNumberOfPendingTasks());
clock_.Advance(base::TimeDelta::FromMilliseconds(100));
// It shouldn't run until the delay is over even though we went idle.
idle_helper_->StartIdlePeriod(IdleHelper::IdlePeriodState::kInShortIdlePeriod,
clock_.NowTicks(), expected_deadline);
EXPECT_EQ(0u, idle_queue()->GetNumberOfPendingTasks());
RunUntilIdle();
EXPECT_EQ(0, run_count);
clock_.Advance(base::TimeDelta::FromMilliseconds(100));
idle_helper_->StartIdlePeriod(IdleHelper::IdlePeriodState::kInShortIdlePeriod,
clock_.NowTicks(), expected_deadline);
EXPECT_EQ(1u, idle_queue()->GetNumberOfPendingTasks());
RunUntilIdle();
EXPECT_EQ(1, run_count);
EXPECT_EQ(expected_deadline, deadline_in_task);
}
// Tests that the OnPendingTasksChanged callback is called once when the idle
// queue becomes non-empty and again when it becomes empty.
TEST_F(IdleHelperTest, OnPendingTasksChanged) {
int run_count = 0;
base::TimeTicks expected_deadline =
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(2300);
base::TimeTicks deadline_in_task;
{
testing::InSequence dummy;
// This will be called once. I.e when the one and only task is posted.
EXPECT_CALL(*idle_helper_, OnPendingTasksChanged(true)).Times(1);
// This will be called once. I.e when the one and only task completes.
EXPECT_CALL(*idle_helper_, OnPendingTasksChanged(false)).Times(1);
}
clock_.Advance(base::TimeDelta::FromMilliseconds(100));
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
RunUntilIdle();
EXPECT_EQ(0, run_count);
idle_helper_->StartIdlePeriod(IdleHelper::IdlePeriodState::kInShortIdlePeriod,
clock_.NowTicks(), expected_deadline);
RunUntilIdle();
EXPECT_EQ(1, run_count);
EXPECT_EQ(expected_deadline, deadline_in_task);
}
// Tests that the OnPendingTasksChanged callback is still only called once
// with false despite there being two idle tasks posted.
TEST_F(IdleHelperTest, OnPendingTasksChanged_TwoTasksAtTheSameTime) {
int run_count = 0;
base::TimeTicks expected_deadline =
clock_.NowTicks() + base::TimeDelta::FromMilliseconds(2300);
base::TimeTicks deadline_in_task;
{
testing::InSequence dummy;
// This will be called 3 times. I.e when T1 and T2 are posted and when T1
// completes.
EXPECT_CALL(*idle_helper_, OnPendingTasksChanged(true)).Times(3);
// This will be called once. I.e when T2 completes.
EXPECT_CALL(*idle_helper_, OnPendingTasksChanged(false)).Times(1);
}
clock_.Advance(base::TimeDelta::FromMilliseconds(100));
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
idle_task_runner_->PostIdleTask(
FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
RunUntilIdle();
EXPECT_EQ(0, run_count);
idle_helper_->StartIdlePeriod(IdleHelper::IdlePeriodState::kInShortIdlePeriod,
clock_.NowTicks(), expected_deadline);
RunUntilIdle();
EXPECT_EQ(2, run_count);
EXPECT_EQ(expected_deadline, deadline_in_task);
}
} // namespace idle_helper_unittest
} // namespace scheduler
} // namespace blink