// Copyright 2017 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/test/test_mock_time_task_runner.h"

#include "base/cancelable_callback.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "base/test/gtest_util.h"
#include "base/test/test_timeouts.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {

// Basic usage should work the same from default and bound
// TestMockTimeTaskRunners.
TEST(TestMockTimeTaskRunnerTest, Basic) {
  static constexpr TestMockTimeTaskRunner::Type kTestCases[] = {
      TestMockTimeTaskRunner::Type::kStandalone,
      TestMockTimeTaskRunner::Type::kBoundToThread};

  for (auto type : kTestCases) {
    SCOPED_TRACE(static_cast<int>(type));

    auto mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>(type);
    int counter = 0;

    mock_time_task_runner->PostTask(
        FROM_HERE,
        base::Bind([](int* counter) { *counter += 1; }, Unretained(&counter)));
    mock_time_task_runner->PostTask(
        FROM_HERE,
        base::Bind([](int* counter) { *counter += 32; }, Unretained(&counter)));
    mock_time_task_runner->PostDelayedTask(
        FROM_HERE,
        base::Bind([](int* counter) { *counter += 256; }, Unretained(&counter)),
        TimeDelta::FromSeconds(3));
    mock_time_task_runner->PostDelayedTask(
        FROM_HERE,
        base::Bind([](int* counter) { *counter += 64; }, Unretained(&counter)),
        TimeDelta::FromSeconds(1));
    mock_time_task_runner->PostDelayedTask(
        FROM_HERE,
        base::Bind([](int* counter) { *counter += 1024; },
                   Unretained(&counter)),
        TimeDelta::FromMinutes(20));
    mock_time_task_runner->PostDelayedTask(
        FROM_HERE,
        base::Bind([](int* counter) { *counter += 4096; },
                   Unretained(&counter)),
        TimeDelta::FromDays(20));

    int expected_value = 0;
    EXPECT_EQ(expected_value, counter);
    mock_time_task_runner->RunUntilIdle();
    expected_value += 1;
    expected_value += 32;
    EXPECT_EQ(expected_value, counter);

    mock_time_task_runner->RunUntilIdle();
    EXPECT_EQ(expected_value, counter);

    mock_time_task_runner->FastForwardBy(TimeDelta::FromSeconds(1));
    expected_value += 64;
    EXPECT_EQ(expected_value, counter);

    mock_time_task_runner->FastForwardBy(TimeDelta::FromSeconds(5));
    expected_value += 256;
    EXPECT_EQ(expected_value, counter);

    mock_time_task_runner->FastForwardUntilNoTasksRemain();
    expected_value += 1024;
    expected_value += 4096;
    EXPECT_EQ(expected_value, counter);
  }
}

// A default TestMockTimeTaskRunner shouldn't result in a thread association.
TEST(TestMockTimeTaskRunnerTest, DefaultUnbound) {
  auto unbound_mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>();
  EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
  EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
  EXPECT_DEATH_IF_SUPPORTED({ RunLoop().RunUntilIdle(); }, "");
}

TEST(TestMockTimeTaskRunnerTest, RunLoopDriveableWhenBound) {
  auto bound_mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>(
      TestMockTimeTaskRunner::Type::kBoundToThread);

  int counter = 0;
  ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE,
      base::Bind([](int* counter) { *counter += 1; }, Unretained(&counter)));
  ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE,
      base::Bind([](int* counter) { *counter += 32; }, Unretained(&counter)));
  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
      FROM_HERE,
      base::Bind([](int* counter) { *counter += 256; }, Unretained(&counter)),
      TimeDelta::FromSeconds(3));
  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
      FROM_HERE,
      base::Bind([](int* counter) { *counter += 64; }, Unretained(&counter)),
      TimeDelta::FromSeconds(1));
  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
      FROM_HERE,
      base::Bind([](int* counter) { *counter += 1024; }, Unretained(&counter)),
      TimeDelta::FromMinutes(20));
  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
      FROM_HERE,
      base::Bind([](int* counter) { *counter += 4096; }, Unretained(&counter)),
      TimeDelta::FromDays(20));

  int expected_value = 0;
  EXPECT_EQ(expected_value, counter);
  RunLoop().RunUntilIdle();
  expected_value += 1;
  expected_value += 32;
  EXPECT_EQ(expected_value, counter);

  RunLoop().RunUntilIdle();
  EXPECT_EQ(expected_value, counter);

  {
    RunLoop run_loop;
    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
        FROM_HERE, run_loop.QuitClosure(), TimeDelta::FromSeconds(1));
    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
        FROM_HERE,
        base::Bind([](int* counter) { *counter += 8192; },
                   Unretained(&counter)),
        TimeDelta::FromSeconds(1));

    // The QuitClosure() should be ordered between the 64 and the 8192
    // increments and should preempt the latter.
    run_loop.Run();
    expected_value += 64;
    EXPECT_EQ(expected_value, counter);

    // Running until idle should process the 8192 increment whose delay has
    // expired in the previous Run().
    RunLoop().RunUntilIdle();
    expected_value += 8192;
    EXPECT_EQ(expected_value, counter);
  }

  {
    RunLoop run_loop;
    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
        FROM_HERE, run_loop.QuitWhenIdleClosure(), TimeDelta::FromSeconds(5));
    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
        FROM_HERE,
        base::Bind([](int* counter) { *counter += 16384; },
                   Unretained(&counter)),
        TimeDelta::FromSeconds(5));

    // The QuitWhenIdleClosure() shouldn't preempt equally delayed tasks and as
    // such the 16384 increment should be processed before quitting.
    run_loop.Run();
    expected_value += 256;
    expected_value += 16384;
    EXPECT_EQ(expected_value, counter);
  }

  // Process the remaining tasks (note: do not mimic this elsewhere,
  // TestMockTimeTaskRunner::FastForwardUntilNoTasksRemain() is a better API to
  // do this, this is just done here for the purpose of extensively testing the
  // RunLoop approach).
  RunLoop run_loop;
  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
      FROM_HERE, run_loop.QuitWhenIdleClosure(), TimeDelta::FromDays(50));

  run_loop.Run();
  expected_value += 1024;
  expected_value += 4096;
  EXPECT_EQ(expected_value, counter);
}

TEST(TestMockTimeTaskRunnerTest, AvoidCaptureWhenBound) {
  // Make sure that capturing the active task runner --- which sometimes happens
  // unknowingly due to ThreadsafeObserverList deep within some singleton ---
  // does not keep the entire TestMockTimeTaskRunner alive, as in bound mode
  // that's a RunLoop::Delegate, and leaking that renders any further tests that
  // need RunLoop support unrunnable.
  //
  // (This used to happen when code run from ProcessAllTasksNoLaterThan grabbed
  //  the runner.).
  scoped_refptr<SingleThreadTaskRunner> captured;
  {
    auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>(
        TestMockTimeTaskRunner::Type::kBoundToThread);

    task_runner->PostTask(FROM_HERE, base::BindLambdaForTesting([&]() {
                            captured = ThreadTaskRunnerHandle::Get();
                          }));
    task_runner->RunUntilIdle();
  }

  {
    // This should not complain about RunLoop::Delegate already existing.
    auto task_runner2 = MakeRefCounted<TestMockTimeTaskRunner>(
        TestMockTimeTaskRunner::Type::kBoundToThread);
  }
}

// Regression test that receiving the quit-when-idle signal when already empty
// works as intended (i.e. that |TestMockTimeTaskRunner::tasks_lock_cv| is
// properly signaled).
TEST(TestMockTimeTaskRunnerTest, RunLoopQuitFromIdle) {
  auto bound_mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>(
      TestMockTimeTaskRunner::Type::kBoundToThread);

  Thread quitting_thread("quitting thread");
  quitting_thread.Start();

  RunLoop run_loop;
  quitting_thread.task_runner()->PostDelayedTask(
      FROM_HERE, run_loop.QuitWhenIdleClosure(), TestTimeouts::tiny_timeout());
  run_loop.Run();
}

TEST(TestMockTimeTaskRunnerTest, TakePendingTasks) {
  auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>();
  task_runner->PostTask(FROM_HERE, Bind([]() {}));
  EXPECT_TRUE(task_runner->HasPendingTask());
  EXPECT_EQ(1u, task_runner->TakePendingTasks().size());
  EXPECT_FALSE(task_runner->HasPendingTask());
}

TEST(TestMockTimeTaskRunnerTest, CancelPendingTask) {
  auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>();
  CancelableClosure task1(Bind([]() {}));
  task_runner->PostDelayedTask(FROM_HERE, task1.callback(),
                               TimeDelta::FromSeconds(1));
  EXPECT_TRUE(task_runner->HasPendingTask());
  EXPECT_EQ(1u, task_runner->GetPendingTaskCount());
  EXPECT_EQ(TimeDelta::FromSeconds(1), task_runner->NextPendingTaskDelay());
  task1.Cancel();
  EXPECT_FALSE(task_runner->HasPendingTask());

  CancelableClosure task2(Bind([]() {}));
  task_runner->PostDelayedTask(FROM_HERE, task2.callback(),
                               TimeDelta::FromSeconds(1));
  task2.Cancel();
  EXPECT_EQ(0u, task_runner->GetPendingTaskCount());

  CancelableClosure task3(Bind([]() {}));
  task_runner->PostDelayedTask(FROM_HERE, task3.callback(),
                               TimeDelta::FromSeconds(1));
  task3.Cancel();
  EXPECT_EQ(TimeDelta::Max(), task_runner->NextPendingTaskDelay());

  CancelableClosure task4(Bind([]() {}));
  task_runner->PostDelayedTask(FROM_HERE, task4.callback(),
                               TimeDelta::FromSeconds(1));
  task4.Cancel();
  EXPECT_TRUE(task_runner->TakePendingTasks().empty());
}

TEST(TestMockTimeTaskRunnerTest, NoFastForwardToCancelledTask) {
  auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>();
  TimeTicks start_time = task_runner->NowTicks();
  CancelableClosure task(Bind([]() {}));
  task_runner->PostDelayedTask(FROM_HERE, task.callback(),
                               TimeDelta::FromSeconds(1));
  EXPECT_EQ(TimeDelta::FromSeconds(1), task_runner->NextPendingTaskDelay());
  task.Cancel();
  task_runner->FastForwardUntilNoTasksRemain();
  EXPECT_EQ(start_time, task_runner->NowTicks());
}

TEST(TestMockTimeTaskRunnerTest, AdvanceMockTickClockDoesNotRunTasks) {
  auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>();
  TimeTicks start_time = task_runner->NowTicks();
  task_runner->PostTask(FROM_HERE, BindOnce([]() { ADD_FAILURE(); }));
  task_runner->PostDelayedTask(FROM_HERE, BindOnce([]() { ADD_FAILURE(); }),
                               TimeDelta::FromSeconds(1));

  task_runner->AdvanceMockTickClock(TimeDelta::FromSeconds(3));
  EXPECT_EQ(start_time + TimeDelta::FromSeconds(3), task_runner->NowTicks());
  EXPECT_EQ(2u, task_runner->GetPendingTaskCount());
}

}  // namespace base
