// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/task/sequence_manager/task_queue.h"

#include "base/message_loop/message_pump.h"
#include "base/message_loop/message_pump_type.h"
#include "base/task/sequence_manager/sequence_manager.h"
#include "base/task/sequence_manager/test/sequence_manager_for_test.h"
#include "base/task/task_features.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_task_runner_handle.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {
namespace sequence_manager {
namespace internal {
// To avoid symbol collisions in jumbo builds.
namespace task_queue_unittest {
namespace {

TEST(TaskQueueTest, TaskQueueVoters) {
  auto sequence_manager = CreateSequenceManagerOnCurrentThreadWithPump(
      MessagePump::Create(MessagePumpType::DEFAULT));

  auto queue = sequence_manager->CreateTaskQueue(TaskQueue::Spec("test"));

  // The task queue should be initially enabled.
  EXPECT_TRUE(queue->IsQueueEnabled());

  std::unique_ptr<TaskQueue::QueueEnabledVoter> voter1 =
      queue->CreateQueueEnabledVoter();
  std::unique_ptr<TaskQueue::QueueEnabledVoter> voter2 =
      queue->CreateQueueEnabledVoter();
  std::unique_ptr<TaskQueue::QueueEnabledVoter> voter3 =
      queue->CreateQueueEnabledVoter();
  std::unique_ptr<TaskQueue::QueueEnabledVoter> voter4 =
      queue->CreateQueueEnabledVoter();

  // Voters should initially vote for the queue to be enabled.
  EXPECT_TRUE(queue->IsQueueEnabled());

  // If any voter wants to disable, the queue is disabled.
  voter1->SetVoteToEnable(false);
  EXPECT_FALSE(queue->IsQueueEnabled());

  // If the voter is deleted then the queue should be re-enabled.
  voter1.reset();
  EXPECT_TRUE(queue->IsQueueEnabled());

  // If any of the remaining voters wants to disable, the queue should be
  // disabled.
  voter2->SetVoteToEnable(false);
  EXPECT_FALSE(queue->IsQueueEnabled());

  // If another queue votes to disable, nothing happens because it's already
  // disabled.
  voter3->SetVoteToEnable(false);
  EXPECT_FALSE(queue->IsQueueEnabled());

  // There are two votes to disable, so one of them voting to enable does
  // nothing.
  voter2->SetVoteToEnable(true);
  EXPECT_FALSE(queue->IsQueueEnabled());

  // IF all queues vote to enable then the queue is enabled.
  voter3->SetVoteToEnable(true);
  EXPECT_TRUE(queue->IsQueueEnabled());
}

TEST(TaskQueueTest, ShutdownQueueBeforeEnabledVoterDeleted) {
  auto sequence_manager = CreateSequenceManagerOnCurrentThreadWithPump(
      MessagePump::Create(MessagePumpType::DEFAULT));
  auto queue = sequence_manager->CreateTaskQueue(TaskQueue::Spec("test"));

  std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
      queue->CreateQueueEnabledVoter();

  voter->SetVoteToEnable(true);  // NOP
  queue->ShutdownTaskQueue();

  // This should complete without DCHECKing.
  voter.reset();
}

TEST(TaskQueueTest, ShutdownQueueBeforeDisabledVoterDeleted) {
  auto sequence_manager = CreateSequenceManagerOnCurrentThreadWithPump(
      MessagePump::Create(MessagePumpType::DEFAULT));
  auto queue = sequence_manager->CreateTaskQueue(TaskQueue::Spec("test"));

  std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
      queue->CreateQueueEnabledVoter();

  voter->SetVoteToEnable(false);
  queue->ShutdownTaskQueue();

  // This should complete without DCHECKing.
  voter.reset();
}

class ScopedNoWakeUpsForCanceledTasks {
 public:
  explicit ScopedNoWakeUpsForCanceledTasks(bool feature_enabled) {
    scoped_feature_list_.InitWithFeatureState(kRemoveCanceledTasksInTaskQueue,
                                              feature_enabled);
    TaskQueueImpl::ApplyRemoveCanceledTasksInTaskQueue();
  }

  ~ScopedNoWakeUpsForCanceledTasks() {
    TaskQueueImpl::ResetRemoveCanceledTasksInTaskQueueForTesting();
  }

 private:
  test::ScopedFeatureList scoped_feature_list_;
};

TEST(TaskQueueTest, CanceledTaskRemovedIfFeatureEnabled) {
  for (bool feature_enabled : {false, true}) {
    ScopedNoWakeUpsForCanceledTasks scoped_no_wake_ups_for_canceled_tasks(
        feature_enabled);

    auto sequence_manager = CreateSequenceManagerOnCurrentThreadWithPump(
        MessagePump::Create(MessagePumpType::DEFAULT));
    auto queue = sequence_manager->CreateTaskQueue(TaskQueue::Spec("test"));

    // Get the default task runner.
    auto task_runner = queue->task_runner();
    EXPECT_EQ(queue->GetNumberOfPendingTasks(), 0u);

    bool task_ran = false;
    DelayedTaskHandle delayed_task_handle =
        task_runner->PostCancelableDelayedTask(
            subtle::PostDelayedTaskPassKeyForTesting(), FROM_HERE,
            BindLambdaForTesting([&task_ran]() { task_ran = true; }),
            Seconds(20));
    EXPECT_EQ(queue->GetNumberOfPendingTasks(), 1u);

    // The task is only removed from the queue if the feature is enabled.
    delayed_task_handle.CancelTask();
    EXPECT_EQ(queue->GetNumberOfPendingTasks(), feature_enabled ? 0u : 1u);

    // In any case, the task never actually ran.
    EXPECT_FALSE(task_ran);
  }
}

// Tests that a task posted through `PostCancelableDelayedTask()`, while the
// RemoveCanceledDelayedTasksInTaskQueue feature is enabled, is not considered
// canceled once it has reached the |delayed_work_queue| and is therefore
// not removed.
//
// This is a regression test for a bug in `Task::IsCanceled()`
// (see https://crbug.com/1288882). Note that this function is only called on
// tasks inside the |delayed_work_queue|, and not for tasks in the
// |delayed_incoming_queue|. This is because a task posted through
// `PostCancelableDelayedTask()` is always valid while it is in the
// |delayed_incoming_queue|, since canceling it would remove it from the queue.
TEST(TaskQueueTest, ValidCancelableTaskIsNotCanceled) {
  ScopedNoWakeUpsForCanceledTasks scoped_no_wake_ups_for_canceled_tasks(true);

  auto sequence_manager = CreateSequenceManagerOnCurrentThreadWithPump(
      MessagePump::Create(MessagePumpType::DEFAULT));
  auto queue = sequence_manager->CreateTaskQueue(TaskQueue::Spec("test"));

  // Get the default task runner.
  auto task_runner = queue->task_runner();
  EXPECT_EQ(queue->GetNumberOfPendingTasks(), 0u);

  // RunLoop requires the ThreadTaskRunnerHandle to be set.
  ThreadTaskRunnerHandle thread_task_runner_handle(task_runner);
  RunLoop run_loop;

  // To reach the |delayed_work_queue|, the task must be posted with a non-
  // zero delay, which is then moved to the |delayed_work_queue| when it is
  // ripe. To achieve this, run the RunLoop for exactly the same delay of the
  // cancelable task. Since real time waiting happens, chose a very small delay.
  constexpr TimeDelta kTestDelay = Microseconds(1);
  task_runner->PostDelayedTask(FROM_HERE, run_loop.QuitClosure(), kTestDelay);

  DelayedTaskHandle delayed_task_handle =
      task_runner->PostCancelableDelayedTask(
          subtle::PostDelayedTaskPassKeyForTesting(), FROM_HERE, DoNothing(),
          kTestDelay);
  run_loop.Run();

  // Now only the cancelable delayed task remains and it is ripe.
  EXPECT_EQ(queue->GetNumberOfPendingTasks(), 1u);

  // ReclaimMemory doesn't remove the task because it is valid (not canceled).
  sequence_manager->ReclaimMemory();
  EXPECT_EQ(queue->GetNumberOfPendingTasks(), 1u);

  // Clean-up.
  delayed_task_handle.CancelTask();
}

}  // namespace
}  // namespace task_queue_unittest
}  // namespace internal
}  // namespace sequence_manager
}  // namespace base
