// 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 "components/scheduler/child/worker_scheduler_impl.h" #include "base/callback.h" #include "base/macros.h" #include "base/strings/stringprintf.h" #include "base/test/simple_test_tick_clock.h" #include "cc/test/ordered_simple_task_runner.h" #include "components/scheduler/base/test_time_source.h" #include "components/scheduler/child/scheduler_tqm_delegate_for_test.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using testing::ElementsAreArray; namespace scheduler { namespace { void NopTask() { } int TimeTicksToIntMs(const base::TimeTicks& time) { return static_cast((time - base::TimeTicks()).InMilliseconds()); } void RecordTimelineTask(std::vector* timeline, base::SimpleTestTickClock* clock) { timeline->push_back(base::StringPrintf("run RecordTimelineTask @ %d", TimeTicksToIntMs(clock->NowTicks()))); } void AppendToVectorTestTask(std::vector* vector, std::string value) { vector->push_back(value); } void AppendToVectorIdleTestTask(std::vector* vector, std::string value, base::TimeTicks deadline) { AppendToVectorTestTask(vector, value); } void TimelineIdleTestTask(std::vector* timeline, base::TimeTicks deadline) { timeline->push_back(base::StringPrintf("run TimelineIdleTestTask deadline %d", TimeTicksToIntMs(deadline))); } }; // namespace class WorkerSchedulerImplForTest : public WorkerSchedulerImpl { public: WorkerSchedulerImplForTest( scoped_refptr main_task_runner, base::SimpleTestTickClock* clock_) : WorkerSchedulerImpl(main_task_runner), clock_(clock_), timeline_(nullptr) {} void RecordTimelineEvents(std::vector* timeline) { timeline_ = timeline; } private: bool CanEnterLongIdlePeriod( base::TimeTicks now, base::TimeDelta* next_long_idle_period_delay_out) override { if (timeline_) { timeline_->push_back(base::StringPrintf("CanEnterLongIdlePeriod @ %d", TimeTicksToIntMs(now))); } return WorkerSchedulerImpl::CanEnterLongIdlePeriod( now, next_long_idle_period_delay_out); } void IsNotQuiescent() override { if (timeline_) { timeline_->push_back(base::StringPrintf( "IsNotQuiescent @ %d", TimeTicksToIntMs(clock_->NowTicks()))); } WorkerSchedulerImpl::IsNotQuiescent(); } base::SimpleTestTickClock* clock_; // NOT OWNED std::vector* timeline_; // NOT OWNED }; class WorkerSchedulerImplTest : public testing::Test { public: WorkerSchedulerImplTest() : clock_(new base::SimpleTestTickClock()), mock_task_runner_(new cc::OrderedSimpleTaskRunner(clock_.get(), true)), main_task_runner_(SchedulerTqmDelegateForTest::Create( mock_task_runner_, make_scoped_ptr(new TestTimeSource(clock_.get())))), scheduler_( new WorkerSchedulerImplForTest(main_task_runner_, clock_.get())), timeline_(nullptr) { clock_->Advance(base::TimeDelta::FromMicroseconds(5000)); } ~WorkerSchedulerImplTest() override {} void TearDown() override { // Check that all tests stop posting tasks. while (mock_task_runner_->RunUntilIdle()) { } } void Init() { scheduler_->Init(); default_task_runner_ = scheduler_->DefaultTaskRunner(); idle_task_runner_ = scheduler_->IdleTaskRunner(); timeline_ = nullptr; } void RecordTimelineEvents(std::vector* timeline) { timeline_ = timeline; scheduler_->RecordTimelineEvents(timeline); } void RunUntilIdle() { if (timeline_) { timeline_->push_back(base::StringPrintf( "RunUntilIdle begin @ %d", TimeTicksToIntMs(clock_->NowTicks()))); } mock_task_runner_->RunUntilIdle(); if (timeline_) { timeline_->push_back(base::StringPrintf( "RunUntilIdle end @ %d", TimeTicksToIntMs(clock_->NowTicks()))); } } // Helper for posting several tasks of specific types. |task_descriptor| is a // string with space delimited task identifiers. The first letter of each // task identifier specifies the task type: // - 'D': Default task // - 'I': Idle task void PostTestTasks(std::vector* run_order, const std::string& task_descriptor) { std::istringstream stream(task_descriptor); while (!stream.eof()) { std::string task; stream >> task; switch (task[0]) { case 'D': default_task_runner_->PostTask( FROM_HERE, base::Bind(&AppendToVectorTestTask, run_order, task)); break; case 'I': idle_task_runner_->PostIdleTask( FROM_HERE, base::Bind(&AppendToVectorIdleTestTask, run_order, task)); break; default: NOTREACHED(); } } } static base::TimeDelta maximum_idle_period_duration() { return base::TimeDelta::FromMilliseconds( IdleHelper::kMaximumIdlePeriodMillis); } protected: scoped_ptr clock_; // Only one of mock_task_runner_ or message_loop_ will be set. scoped_refptr mock_task_runner_; scoped_refptr main_task_runner_; scoped_ptr scheduler_; scoped_refptr default_task_runner_; scoped_refptr idle_task_runner_; std::vector* timeline_; // NOT OWNED DISALLOW_COPY_AND_ASSIGN(WorkerSchedulerImplTest); }; TEST_F(WorkerSchedulerImplTest, TestPostDefaultTask) { Init(); std::vector run_order; PostTestTasks(&run_order, "D1 D2 D3 D4"); RunUntilIdle(); EXPECT_THAT(run_order, testing::ElementsAre(std::string("D1"), std::string("D2"), std::string("D3"), std::string("D4"))); } TEST_F(WorkerSchedulerImplTest, TestPostIdleTask) { Init(); std::vector run_order; PostTestTasks(&run_order, "I1"); RunUntilIdle(); EXPECT_THAT(run_order, testing::ElementsAre(std::string("I1"))); } TEST_F(WorkerSchedulerImplTest, TestPostDefaultAndIdleTasks) { Init(); std::vector run_order; PostTestTasks(&run_order, "I1 D2 D3 D4"); RunUntilIdle(); EXPECT_THAT(run_order, testing::ElementsAre(std::string("D2"), std::string("D3"), std::string("D4"), std::string("I1"))); } TEST_F(WorkerSchedulerImplTest, TestPostDefaultDelayedAndIdleTasks) { Init(); std::vector run_order; PostTestTasks(&run_order, "I1 D2 D3 D4"); default_task_runner_->PostDelayedTask( FROM_HERE, base::Bind(&AppendToVectorTestTask, &run_order, "DELAYED"), base::TimeDelta::FromMilliseconds(1000)); RunUntilIdle(); EXPECT_THAT(run_order, testing::ElementsAre(std::string("D2"), std::string("D3"), std::string("D4"), std::string("I1"), std::string("DELAYED"))); } TEST_F(WorkerSchedulerImplTest, TestIdleTaskWhenIsNotQuiescent) { std::vector timeline; RecordTimelineEvents(&timeline); Init(); timeline.push_back("Post default task"); // Post a delayed task timed to occur mid way during the long idle period. default_task_runner_->PostTask( FROM_HERE, base::Bind(&RecordTimelineTask, base::Unretained(&timeline), base::Unretained(clock_.get()))); RunUntilIdle(); timeline.push_back("Post idle task"); idle_task_runner_->PostIdleTask(FROM_HERE, base::Bind(&TimelineIdleTestTask, &timeline)); RunUntilIdle(); std::string expected_timeline[] = {"CanEnterLongIdlePeriod @ 5", "Post default task", "run RecordTimelineTask @ 5", "Post idle task", "IsNotQuiescent @ 5", "CanEnterLongIdlePeriod @ 305", "run TimelineIdleTestTask deadline 355"}; EXPECT_THAT(timeline, ElementsAreArray(expected_timeline)); } TEST_F(WorkerSchedulerImplTest, TestIdleDeadlineWithPendingDelayedTask) { std::vector timeline; RecordTimelineEvents(&timeline); Init(); timeline.push_back("Post delayed and idle tasks"); // Post a delayed task timed to occur mid way during the long idle period. default_task_runner_->PostDelayedTask( FROM_HERE, base::Bind(&RecordTimelineTask, base::Unretained(&timeline), base::Unretained(clock_.get())), base::TimeDelta::FromMilliseconds(20)); idle_task_runner_->PostIdleTask(FROM_HERE, base::Bind(&TimelineIdleTestTask, &timeline)); RunUntilIdle(); std::string expected_timeline[] = { "CanEnterLongIdlePeriod @ 5", "Post delayed and idle tasks", "CanEnterLongIdlePeriod @ 5", "run TimelineIdleTestTask deadline 25", // Note the short 20ms deadline. "run RecordTimelineTask @ 25"}; EXPECT_THAT(timeline, ElementsAreArray(expected_timeline)); } TEST_F(WorkerSchedulerImplTest, TestIdleDeadlineWithPendingDelayedTaskFarInTheFuture) { std::vector timeline; RecordTimelineEvents(&timeline); Init(); timeline.push_back("Post delayed and idle tasks"); // Post a delayed task timed to occur well after the long idle period. default_task_runner_->PostDelayedTask( FROM_HERE, base::Bind(&RecordTimelineTask, base::Unretained(&timeline), base::Unretained(clock_.get())), base::TimeDelta::FromMilliseconds(500)); idle_task_runner_->PostIdleTask(FROM_HERE, base::Bind(&TimelineIdleTestTask, &timeline)); RunUntilIdle(); std::string expected_timeline[] = { "CanEnterLongIdlePeriod @ 5", "Post delayed and idle tasks", "CanEnterLongIdlePeriod @ 5", "run TimelineIdleTestTask deadline 55", // Note the full 50ms deadline. "run RecordTimelineTask @ 505"}; EXPECT_THAT(timeline, ElementsAreArray(expected_timeline)); } TEST_F(WorkerSchedulerImplTest, TestPostIdleTaskAfterRunningUntilIdle) { Init(); default_task_runner_->PostDelayedTask( FROM_HERE, base::Bind(&NopTask), base::TimeDelta::FromMilliseconds(1000)); RunUntilIdle(); std::vector run_order; PostTestTasks(&run_order, "I1 I2 D3"); RunUntilIdle(); EXPECT_THAT(run_order, testing::ElementsAre(std::string("D3"), std::string("I1"), std::string("I2"))); } TEST_F(WorkerSchedulerImplTest, TestLongIdlePeriodTimeline) { Init(); std::vector timeline; RecordTimelineEvents(&timeline); // The scheduler should not run the initiate_next_long_idle_period task if // there are no idle tasks and no other task woke up the scheduler, thus // the idle period deadline shouldn't update at the end of the current long // idle period. base::TimeTicks idle_period_deadline = scheduler_->CurrentIdleTaskDeadlineForTesting(); clock_->Advance(maximum_idle_period_duration()); RunUntilIdle(); base::TimeTicks new_idle_period_deadline = scheduler_->CurrentIdleTaskDeadlineForTesting(); EXPECT_EQ(idle_period_deadline, new_idle_period_deadline); // Posting a after-wakeup idle task also shouldn't wake the scheduler or // initiate the next long idle period. timeline.push_back("PostIdleTaskAfterWakeup"); idle_task_runner_->PostIdleTaskAfterWakeup( FROM_HERE, base::Bind(&TimelineIdleTestTask, &timeline)); RunUntilIdle(); new_idle_period_deadline = scheduler_->CurrentIdleTaskDeadlineForTesting(); // Running a normal task should initiate a new long idle period after waiting // 300ms for quiescence. timeline.push_back("Post RecordTimelineTask"); default_task_runner_->PostTask( FROM_HERE, base::Bind(&RecordTimelineTask, base::Unretained(&timeline), base::Unretained(clock_.get()))); RunUntilIdle(); std::string expected_timeline[] = { "RunUntilIdle begin @ 55", "RunUntilIdle end @ 55", "PostIdleTaskAfterWakeup", "RunUntilIdle begin @ 55", // NOTE idle task doesn't run till later. "RunUntilIdle end @ 55", "Post RecordTimelineTask", "RunUntilIdle begin @ 55", "run RecordTimelineTask @ 55", "IsNotQuiescent @ 55", // NOTE we have to wait for quiescence. "CanEnterLongIdlePeriod @ 355", "run TimelineIdleTestTask deadline 405", "RunUntilIdle end @ 355"}; EXPECT_THAT(timeline, ElementsAreArray(expected_timeline)); } } // namespace scheduler