// Copyright 2015 The Chromium Authors
// 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/common/idle_helper.h"

#include <memory>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/run_loop.h"
#include "base/task/common/lazy_now.h"
#include "base/task/sequence_manager/sequence_manager.h"
#include "base/task/sequence_manager/task_queue.h"
#include "base/task/sequence_manager/time_domain.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/time/time.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/scheduler/common/scheduler_helper.h"
#include "third_party/blink/renderer/platform/scheduler/common/single_thread_idle_task_runner.h"
#include "third_party/blink/renderer/platform/scheduler/public/non_main_thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
#include "third_party/blink/renderer/platform/scheduler/test/task_environment.h"
#include "third_party/blink/renderer/platform/scheduler/worker/non_main_thread_scheduler_helper.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_copier_base.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_copier_std.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"

using testing::_;
using testing::AnyNumber;
using testing::AtLeast;
using testing::Exactly;
using testing::Return;

namespace blink {
namespace scheduler {
// To avoid symbol collisions in jumbo builds.
namespace idle_helper_unittest {

using base::sequence_manager::SequenceManager;
using base::sequence_manager::TaskQueue;

void AppendToVectorTestTask(Vector<String>* vector, String value) {
  vector->push_back(value);
}

void AppendToVectorIdleTestTask(Vector<String>* vector,
                                String value,
                                base::TimeTicks deadline) {
  AppendToVectorTestTask(vector, value);
}

void NullTask() {}

void NullIdleTask(base::TimeTicks deadline) {}

void AppendToVectorReentrantTask(base::SingleThreadTaskRunner* task_runner,
                                 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)++;
  if (deadline_out) {
    *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::test::TaskEnvironment* task_environment,
    base::TimeDelta advance_time,
    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,
                                  task_environment, advance_time, deadlines));
  }
  deadlines->push_back(deadline);
  (*run_count)++;
  task_environment->AdvanceClock(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(
    blink::test::TaskEnvironmentWithNonMainThreadSchedulerHelper*
        task_environment,
    int* run_count,
    base::TimeTicks set_time,
    base::TimeTicks deadline) {
  task_environment->AdvanceClock(set_time - base::TimeTicks::Now());
  (*run_count)++;
}

void UpdateClockToDeadlineIdleTestTask(
    blink::test::TaskEnvironmentWithNonMainThreadSchedulerHelper*
        task_environment,
    int* run_count,
    base::TimeTicks deadline) {
  UpdateClockIdleTestTask(task_environment, 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();
}

class IdleHelperForTest : public IdleHelper, public IdleHelper::Delegate {
 public:
  explicit IdleHelperForTest(
      SchedulerHelper* scheduler_helper,
      base::TimeDelta required_quiescence_duration_before_long_idle_period,
      TaskQueue* idle_task_queue)
      : IdleHelper(scheduler_helper,
                   this,
                   "TestSchedulerIdlePeriod",
                   required_quiescence_duration_before_long_idle_period,
                   idle_task_queue) {}

  ~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_METHOD1(OnPendingTasksChanged, void(bool has_tasks));
};

class BaseIdleHelperTest : public testing::Test {
 public:
  explicit BaseIdleHelperTest(
      base::TimeDelta required_quiescence_duration_before_long_idle_period)
      : task_environment_(
            base::test::TaskEnvironment::TimeSource::MOCK_TIME,
            base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY) {
    idle_helper_queue_ =
        task_environment_.GetNonMainThreadSchedulerHelper()->NewTaskQueue(
            TaskQueue::Spec(base::sequence_manager::QueueName::IDLE_TQ));
    idle_helper_ = std::make_unique<IdleHelperForTest>(
        task_environment_.GetNonMainThreadSchedulerHelper(),
        required_quiescence_duration_before_long_idle_period,
        idle_helper_queue_->GetTaskQueue());
    default_task_queue_ = task_environment_.GetNonMainThreadSchedulerHelper()
                              ->DefaultNonMainThreadTaskQueue();
    default_task_runner_ =
        default_task_queue_->GetTaskRunnerWithDefaultTaskType();
    idle_task_runner_ = idle_helper_->IdleTaskRunner();
    task_environment_.FastForwardBy(base::Microseconds(5000));
  }

  BaseIdleHelperTest(const BaseIdleHelperTest&) = delete;
  BaseIdleHelperTest& operator=(const BaseIdleHelperTest&) = delete;
  ~BaseIdleHelperTest() override = default;

  void SetUp() override {
    EXPECT_CALL(*idle_helper_, CanEnterLongIdlePeriod(_, _))
        .Times(AnyNumber())
        .WillRepeatedly(Return(true));
    EXPECT_CALL(*idle_helper_, OnPendingTasksChanged(_)).Times(AnyNumber());
  }

  void TearDown() override {
    idle_helper_->Shutdown();
    idle_helper_queue_->ShutdownTaskQueue();
    task_environment_.FastForwardUntilNoTasksRemain();
  }

 protected:
  static base::TimeDelta minimum_idle_period_duration() {
    return IdleHelper::kMinimumIdlePeriodDuration;
  }

  static base::TimeDelta maximum_idle_period_duration() {
    return IdleHelper::kMaximumIdlePeriodDuration;
  }

  static base::TimeDelta retry_enable_long_idle_period_delay() {
    return IdleHelper::kRetryEnableLongIdlePeriodDelay;
  }

  void CheckIdlePeriodStateIs(const char* expected) {
    EXPECT_STREQ(expected, idle_helper_->IdlePeriodStateForTracing());
  }

  const TaskQueue* idle_queue() const { return idle_helper_->idle_queue_; }

 protected:
  blink::test::TaskEnvironmentWithNonMainThreadSchedulerHelper
      task_environment_;
  scoped_refptr<NonMainThreadTaskQueue> idle_helper_queue_;
  std::unique_ptr<IdleHelperForTest> idle_helper_;
  scoped_refptr<NonMainThreadTaskQueue> default_task_queue_;
  scoped_refptr<base::SingleThreadTaskRunner> default_task_runner_;
  scoped_refptr<SingleThreadIdleTaskRunner> idle_task_runner_;
};

class IdleHelperTest : public BaseIdleHelperTest {
 public:
  IdleHelperTest() : BaseIdleHelperTest(base::TimeDelta()) {}
  IdleHelperTest(const IdleHelperTest&) = delete;
  IdleHelperTest& operator=(const IdleHelperTest&) = delete;

  ~IdleHelperTest() override = default;
};

TEST_F(IdleHelperTest, TestPostIdleTask) {
  int run_count = 0;
  base::TimeTicks expected_deadline =
      base::TimeTicks::Now() + base::Milliseconds(2300);
  base::TimeTicks deadline_in_task;

  task_environment_.FastForwardBy(base::Milliseconds(100));
  idle_task_runner_->PostIdleTask(
      FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));

  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, run_count);

  idle_helper_->StartShortIdlePeriod(base::TimeTicks::Now(), expected_deadline);
  task_environment_.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;

  task_environment_.FastForwardBy(base::Milliseconds(100));
  idle_task_runner_->PostIdleTask(
      FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));

  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, run_count);

  idle_helper_->StartShortIdlePeriod(
      base::TimeTicks::Now(), base::TimeTicks::Now() + base::Milliseconds(10));
  idle_helper_->EndIdlePeriod();
  task_environment_.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::Unretained(idle_task_runner_.get()),
                                &run_count, &actual_deadline));
  idle_helper_->StartShortIdlePeriod(
      base::TimeTicks::Now(), base::TimeTicks::Now() + base::Milliseconds(10));
  task_environment_.RunUntilIdle();
  EXPECT_EQ(1, run_count);

  // Reposted tasks shouldn't run until next idle period.
  task_environment_.RunUntilIdle();
  EXPECT_EQ(1, run_count);

  idle_helper_->StartShortIdlePeriod(
      base::TimeTicks::Now(), base::TimeTicks::Now() + base::Milliseconds(10));
  task_environment_.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,
                     base::Unretained(&task_environment_), &run_count));
  idle_task_runner_->PostIdleTask(
      FROM_HERE,
      base::BindOnce(&UpdateClockToDeadlineIdleTestTask,
                     base::Unretained(&task_environment_), &run_count));

  idle_helper_->StartShortIdlePeriod(
      base::TimeTicks::Now(), base::TimeTicks::Now() + base::Milliseconds(10));
  task_environment_.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_->StartShortIdlePeriod(
      base::TimeTicks::Now(), base::TimeTicks::Now() + base::Milliseconds(10));
  task_environment_.RunUntilIdle();
  // Second task should be run on the next idle period.
  EXPECT_EQ(2, run_count);
}

TEST_F(IdleHelperTest, TestEnterAndExitIdlePeriod) {
  idle_helper_->StartShortIdlePeriod(
      base::TimeTicks::Now(), base::TimeTicks::Now() + base::Milliseconds(10));
  EXPECT_TRUE(idle_helper_->IsInIdlePeriod());
  idle_helper_->EndIdlePeriod();
  EXPECT_FALSE(idle_helper_->IsInIdlePeriod());
}

TEST_F(IdleHelperTest, TestEnterLongIdlePeriod) {
  base::TimeTicks expected_deadline =
      base::TimeTicks::Now() + 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));

  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, run_count);  // Shouldn't run yet as no idle period.

  idle_helper_->EnableLongIdlePeriod();
  task_environment_.RunUntilIdle();
  EXPECT_EQ(1, run_count);  // Should have run in a long idle time.
  EXPECT_EQ(expected_deadline, deadline_in_task);
  EXPECT_TRUE(idle_helper_->IsInIdlePeriod());
}

TEST_F(IdleHelperTest, TestLongIdlePeriodWithPendingDelayedTask) {
  base::TimeDelta pending_task_delay = base::Milliseconds(30);
  base::TimeTicks expected_deadline =
      base::TimeTicks::Now() + 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();
  task_environment_.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::Milliseconds(10);
  bool normal_task_ran = false;
  bool idle_task_ran = false;

  default_task_runner_->PostDelayedTask(
      FROM_HERE,
      base::BindOnce([](bool* ran) { *ran = true; }, &normal_task_ran),
      pending_task_delay);

  // Advance clock until after delayed task was meant to be run.
  task_environment_.AdvanceClock(base::Milliseconds(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(
                     [](bool* ran, bool* normal_ran, base::TimeTicks deadline) {
                       *ran = true;
                       EXPECT_TRUE(*normal_ran);
                     },
                     &idle_task_ran, &normal_task_ran));

  idle_helper_->EnableLongIdlePeriod();
  task_environment_.RunUntilIdle();

  // The normal task should have run because it was due. However, the idle
  // period didn't start because the normal task was pending when
  // EnableLongIdlePeriod() ran. So the idle task should not have run yet.
  EXPECT_TRUE(normal_task_ran);
  EXPECT_FALSE(idle_task_ran);

  // Now advance time to trigger the retry to start the long idle period.
  task_environment_.AdvanceClock(retry_enable_long_idle_period_delay());
  task_environment_.RunUntilIdle();

  // Now the idle task should have run.
  EXPECT_TRUE(idle_task_ran);
}

TEST_F(IdleHelperTest, TestLongIdlePeriodRepeating) {
  Vector<base::TimeTicks> actual_deadlines;
  int run_count = 0;

  EXPECT_CALL(*idle_helper_, CanEnterLongIdlePeriod(_, _))
      .Times(4)
      .WillRepeatedly(Return(true));

  g_max_idle_task_reposts = 3;
  base::TimeTicks clock_before(base::TimeTicks::Now());
  base::TimeDelta idle_task_runtime(base::Milliseconds(10));
  idle_task_runner_->PostIdleTask(
      FROM_HERE,
      base::BindOnce(&RepostingUpdateClockIdleTestTask,
                     base::Unretained(idle_task_runner_.get()), &run_count,
                     base::Unretained(&task_environment_), idle_task_runtime,
                     &actual_deadlines));

  // Check each idle task runs in their own idle period.
  idle_helper_->EnableLongIdlePeriod();
  task_environment_.FastForwardUntilNoTasksRemain();
  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::Unretained(idle_task_runner_.get()), &run_count,
                     base::Unretained(&task_environment_), 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.
  task_environment_.FastForwardUntilNoTasksRemain();
  EXPECT_EQ(4, run_count);
}

TEST_F(IdleHelperTest, TestLongIdlePeriodWhenNotCanEnterLongIdlePeriod) {
  base::TimeDelta delay = base::Milliseconds(1000);
  base::TimeDelta half_delay = base::Milliseconds(500);
  base::TimeTicks delay_over = base::TimeTicks::Now() + delay;
  base::TimeTicks deadline_in_task;
  int run_count = 0;

  ON_CALL(*idle_helper_, CanEnterLongIdlePeriod(_, _))
      .WillByDefault([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);

  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();
  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, run_count);

  task_environment_.FastForwardBy(half_delay);
  EXPECT_EQ(0, run_count);

  // Delay is finished, idle task should run.
  task_environment_.FastForwardBy(half_delay);
  EXPECT_EQ(1, run_count);
}

TEST_F(IdleHelperTest,
       TestLongIdlePeriodDoesNotImmediatelyRestartIfMaxDeadline) {
  Vector<base::TimeTicks> actual_deadlines;
  int run_count = 0;

  base::TimeTicks clock_before(base::TimeTicks::Now());
  base::TimeDelta idle_task_runtime(base::Milliseconds(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::Unretained(idle_task_runner_.get()), &run_count,
                     base::Unretained(&task_environment_), idle_task_runtime,
                     &actual_deadlines));

  idle_helper_->EnableLongIdlePeriod();
  task_environment_.FastForwardUntilNoTasksRemain();
  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::Milliseconds(20));
  base::TimeDelta idle_task_duration(base::Milliseconds(10));
  base::TimeTicks expected_deadline(
      base::TimeTicks::Now() + 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::Unretained(idle_task_runner_.get()),
                                &run_count, &actual_deadline));
  idle_helper_->EnableLongIdlePeriod();
  task_environment_.RunUntilIdle();
  EXPECT_EQ(1, run_count);
  task_environment_.AdvanceClock(idle_task_duration);

  // Next idle period shouldn't happen until the pending task has been run.
  task_environment_.RunUntilIdle();
  EXPECT_EQ(1, run_count);

  // Once the pending task is run the new idle period should start.
  task_environment_.AdvanceClock(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.
  task_environment_.AdvanceClock(retry_enable_long_idle_period_delay());
  task_environment_.RunUntilIdle();

  EXPECT_EQ(2, run_count);
  EXPECT_EQ(expected_deadline, actual_deadline);
}

TEST_F(IdleHelperTest, TestLongIdlePeriodPaused) {
  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::LazyNow lazy_now_1(task_environment_.GetMockTickClock());
  EXPECT_FALSE(
      task_environment_.GetNonMainThreadSchedulerHelper()->GetNextWakeUp());

  // Posting a task should transition us to the an active state.
  g_max_idle_task_reposts = 2;
  base::TimeTicks clock_before(base::TimeTicks::Now());
  base::TimeDelta idle_task_runtime(base::Milliseconds(10));
  idle_task_runner_->PostIdleTask(
      FROM_HERE,
      base::BindOnce(&RepostingUpdateClockIdleTestTask,
                     base::Unretained(idle_task_runner_.get()), &run_count,
                     base::Unretained(&task_environment_), idle_task_runtime,
                     &actual_deadlines));
  task_environment_.FastForwardUntilNoTasksRemain();
  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");
  base::LazyNow lazy_now_2(task_environment_.GetMockTickClock());
  EXPECT_FALSE(
      task_environment_.GetNonMainThreadSchedulerHelper()->GetNextWakeUp());

  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();
  task_environment_.RunUntilIdle();
  CheckIdlePeriodStateIs("not_in_idle_period");
  EXPECT_EQ(0, run_count);
}

class IdleHelperWithQuiescencePeriodTest : public BaseIdleHelperTest {
 public:
  IdleHelperWithQuiescencePeriodTest(
      const IdleHelperWithQuiescencePeriodTest&) = delete;
  IdleHelperWithQuiescencePeriodTest& operator=(
      const IdleHelperWithQuiescencePeriodTest&) = delete;
  enum {
    kQuiescenceDelayMs = 100,
    kLongIdlePeriodMs = 50,
  };

  IdleHelperWithQuiescencePeriodTest()
      : BaseIdleHelperTest(
            base::Milliseconds(std::to_underlying(kQuiescenceDelayMs))) {}

  ~IdleHelperWithQuiescencePeriodTest() override = default;

  void SetUp() override {
    EXPECT_CALL(*idle_helper_, CanEnterLongIdlePeriod(_, _))
        .Times(AnyNumber())
        .WillRepeatedly(Return(true));
    EXPECT_CALL(*idle_helper_, IsNotQuiescent()).Times(AnyNumber());
    EXPECT_CALL(*idle_helper_, OnPendingTasksChanged(_)).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));
    task_environment_.RunUntilIdle();
  }

 private:
};

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();
  task_environment_.RunUntilIdle();

  EXPECT_EQ(1, run_count);
}

TEST_F(IdleHelperWithQuiescencePeriodTest,
       LongIdlePeriodDoesNotStartsImmediatelyIfBusy) {
  MakeNonQuiescent();
  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();
  task_environment_.RunUntilIdle();

  EXPECT_EQ(0, run_count);
}

TEST_F(IdleHelperWithQuiescencePeriodTest,
       LongIdlePeriodStartsAfterQuiescence) {
  MakeNonQuiescent();

  // 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::Milliseconds(40)));

  int run_count = 0;
  // In this scenario EnableLongIdlePeriod deems us not to be quiescent 5x in
  // a row.
  base::TimeTicks expected_deadline =
      base::TimeTicks::Now() +
      base::Milliseconds(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();
  task_environment_.FastForwardUntilNoTasksRemain();
  EXPECT_EQ(1, run_count);
  EXPECT_EQ(expected_deadline, deadline_in_task);
}

TEST_F(IdleHelperWithQuiescencePeriodTest,
       QuescienceCheckedForAfterLongIdlePeriodEnds) {
  idle_task_runner_->PostIdleTask(FROM_HERE, base::BindOnce(&NullIdleTask));
  idle_helper_->EnableLongIdlePeriod();
  task_environment_.RunUntilIdle();

  // Post a normal task to make the scheduler non-quiescent.
  default_task_runner_->PostTask(FROM_HERE, base::BindOnce(&NullTask));
  task_environment_.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 =
      base::TimeTicks::Now() +
      base::Milliseconds(kQuiescenceDelayMs + kLongIdlePeriodMs);
  idle_task_runner_->PostIdleTask(
      FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
  idle_helper_->EnableLongIdlePeriod();
  task_environment_.FastForwardUntilNoTasksRemain();

  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::Microseconds(50));
  base::TimeTicks less_than_min_deadline(
      base::TimeTicks::Now() + minimum_idle_period_duration() - half_a_ms);
  base::TimeTicks more_than_min_deadline(
      base::TimeTicks::Now() + minimum_idle_period_duration() + half_a_ms);

  idle_helper_->StartShortIdlePeriod(base::TimeTicks::Now(),
                                     less_than_min_deadline);
  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, run_count);

  idle_helper_->StartShortIdlePeriod(base::TimeTicks::Now(),
                                     more_than_min_deadline);
  task_environment_.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::Microseconds(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();
  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, run_count);

  idle_helper_->EndIdlePeriod();
  task_environment_.AdvanceClock(maximum_idle_period_duration());
  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, run_count);

  default_task_runner_->PostDelayedTask(FROM_HERE, base::BindOnce(&NullTask),
                                        more_than_min_deadline_duration);
  idle_helper_->EnableLongIdlePeriod();
  task_environment_.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();
  task_environment_.AdvanceClock(maximum_idle_period_duration() * 2.0);
  task_environment_.RunUntilIdle();
  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();
  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, run_count);
}

TEST_F(IdleHelperTest, TestPostDelayedIdleTask) {
  int run_count = 0;
  base::TimeTicks expected_deadline =
      base::TimeTicks::Now() + base::Milliseconds(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::Milliseconds(200),
      base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));
  EXPECT_EQ(0u, idle_queue()->GetNumberOfPendingTasks());

  task_environment_.AdvanceClock(base::Milliseconds(100));

  // It shouldn't run until the delay is over even though we went idle.
  idle_helper_->StartShortIdlePeriod(base::TimeTicks::Now(), expected_deadline);
  EXPECT_EQ(0u, idle_queue()->GetNumberOfPendingTasks());
  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, run_count);

  task_environment_.AdvanceClock(base::Milliseconds(100));
  idle_helper_->StartShortIdlePeriod(base::TimeTicks::Now(), expected_deadline);
  EXPECT_EQ(1u, idle_queue()->GetNumberOfPendingTasks());
  task_environment_.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 =
      base::TimeTicks::Now() + base::Milliseconds(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);
  }

  task_environment_.AdvanceClock(base::Milliseconds(100));
  idle_task_runner_->PostIdleTask(
      FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, &deadline_in_task));

  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, run_count);

  idle_helper_->StartShortIdlePeriod(base::TimeTicks::Now(), expected_deadline);
  task_environment_.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 =
      base::TimeTicks::Now() + base::Milliseconds(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);
  }

  task_environment_.AdvanceClock(base::Milliseconds(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));

  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, run_count);

  idle_helper_->StartShortIdlePeriod(base::TimeTicks::Now(), expected_deadline);
  task_environment_.RunUntilIdle();
  EXPECT_EQ(2, run_count);
  EXPECT_EQ(expected_deadline, deadline_in_task);
}

class MultiThreadedIdleHelperTest : public IdleHelperTest {
 public:
#if DCHECK_IS_ON()
  ~MultiThreadedIdleHelperTest() override { SetIsBeforeThreadCreatedForTest(); }
#endif

  void PostIdleTaskFromNewThread(int* run_count) {
    PostDelayedIdleTaskFromNewThread(base::TimeDelta(), run_count);
  }

  void PostDelayedIdleTaskFromNewThread(base::TimeDelta delay, int* run_count) {
    std::unique_ptr<NonMainThread> thread = NonMainThread::CreateThread(
        ThreadCreationParams(ThreadType::kTestThread)
            .SetThreadNameForTest("TestBackgroundThread"));
    PostCrossThreadTask(*thread->GetTaskRunner(), FROM_HERE,
                        CrossThreadBindOnce(&PostIdleTaskFromBackgroundThread,
                                            idle_task_runner_, delay,
                                            CrossThreadUnretained(run_count)));
    thread.reset();
  }

 protected:
  static void PostIdleTaskFromBackgroundThread(
      scoped_refptr<SingleThreadIdleTaskRunner> idle_task_runner,
      base::TimeDelta delay,
      int* run_count) {
    auto callback = ConvertToBaseOnceCallback(CrossThreadBindOnce(
        &IdleTestTask, CrossThreadUnretained(run_count), nullptr));
    if (delay.is_zero()) {
      idle_task_runner->PostIdleTask(FROM_HERE, std::move(callback));
    } else {
      idle_task_runner->PostDelayedIdleTask(FROM_HERE, delay,
                                            std::move(callback));
    }
  }
};

TEST_F(MultiThreadedIdleHelperTest, IdleTasksFromNonMainThreads) {
  int run_count = 0;

  task_environment_.AdvanceClock(base::Milliseconds(100));

  PostIdleTaskFromNewThread(&run_count);
  PostIdleTaskFromNewThread(&run_count);
  PostIdleTaskFromNewThread(&run_count);

  EXPECT_EQ(3u, idle_queue()->GetNumberOfPendingTasks());
  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, run_count);

  idle_helper_->StartShortIdlePeriod(
      base::TimeTicks::Now(), base::TimeTicks::Now() + base::Milliseconds(10));
  task_environment_.RunUntilIdle();
  EXPECT_EQ(3, run_count);
}

TEST_F(MultiThreadedIdleHelperTest, DelayedIdleTasksFromNonMainThreads) {
  int run_count = 0;

  task_environment_.AdvanceClock(base::Milliseconds(100));

  PostDelayedIdleTaskFromNewThread(base::Milliseconds(200), &run_count);
  PostDelayedIdleTaskFromNewThread(base::Milliseconds(250), &run_count);
  PostDelayedIdleTaskFromNewThread(base::Milliseconds(300), &run_count);

  // Delayed idle tasks are not queued until a new idle period starts.
  EXPECT_EQ(0u, idle_queue()->GetNumberOfPendingTasks());
  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, run_count);

  task_environment_.AdvanceClock(base::Milliseconds(300));
  idle_helper_->StartShortIdlePeriod(
      base::TimeTicks::Now(), base::TimeTicks::Now() + base::Milliseconds(10));
  EXPECT_EQ(3u, idle_queue()->GetNumberOfPendingTasks());
  task_environment_.RunUntilIdle();

  EXPECT_EQ(3, run_count);
}

TEST_F(MultiThreadedIdleHelperTest,
       DelayedAndNonDelayedIdleTasksFromMultipleThreads) {
  int run_count = 0;

  PostIdleTaskFromNewThread(&run_count);

  idle_task_runner_->PostIdleTask(
      FROM_HERE, base::BindOnce(&IdleTestTask, &run_count, nullptr));

  PostDelayedIdleTaskFromNewThread(base::Milliseconds(200), &run_count);

  idle_task_runner_->PostDelayedIdleTask(
      FROM_HERE, base::Milliseconds(250),
      base::BindOnce(&IdleTestTask, &run_count, nullptr));

  task_environment_.AdvanceClock(base::Milliseconds(250));

  EXPECT_EQ(2u, idle_queue()->GetNumberOfPendingTasks());
  task_environment_.RunUntilIdle();
  EXPECT_EQ(0, run_count);

  idle_helper_->StartShortIdlePeriod(
      base::TimeTicks::Now(), base::TimeTicks::Now() + base::Milliseconds(10));
  EXPECT_EQ(4u, idle_queue()->GetNumberOfPendingTasks());
  task_environment_.RunUntilIdle();

  EXPECT_EQ(4, run_count);
}

}  // namespace idle_helper_unittest
}  // namespace scheduler
}  // namespace blink
