// 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 "base/task/sequence_manager/work_queue.h"

#include <stddef.h>
#include <memory>

#include "base/bind.h"
#include "base/task/sequence_manager/real_time_domain.h"
#include "base/task/sequence_manager/task_queue_impl.h"
#include "base/task/sequence_manager/work_queue_sets.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace base {
namespace sequence_manager {
namespace internal {

namespace {

void NopTask() {}

struct Cancelable {
  Cancelable() : weak_ptr_factory(this) {}

  void NopTask() {}

  WeakPtrFactory<Cancelable> weak_ptr_factory;
};

}  // namespace

class WorkQueueTest : public testing::Test {
 public:
  void SetUp() override {
    time_domain_.reset(new RealTimeDomain());
    task_queue_ = std::make_unique<TaskQueueImpl>(nullptr, time_domain_.get(),
                                                  TaskQueue::Spec("test"));

    work_queue_.reset(new WorkQueue(task_queue_.get(), "test",
                                    WorkQueue::QueueType::kImmediate));
    work_queue_sets_.reset(new WorkQueueSets(1, "test"));
    work_queue_sets_->AddQueue(work_queue_.get(), 0);
  }

  void TearDown() override { work_queue_sets_->RemoveQueue(work_queue_.get()); }

 protected:
  TaskQueueImpl::Task FakeCancelableTaskWithEnqueueOrder(
      int enqueue_order,
      WeakPtr<Cancelable> weak_ptr) {
    TaskQueueImpl::Task fake_task(
        TaskQueue::PostedTask(BindOnce(&Cancelable::NopTask, weak_ptr),
                              FROM_HERE),
        TimeTicks(), EnqueueOrder(),
        EnqueueOrder::FromIntForTesting(enqueue_order));
    return fake_task;
  }

  TaskQueueImpl::Task FakeTaskWithEnqueueOrder(int enqueue_order) {
    TaskQueueImpl::Task fake_task(
        TaskQueue::PostedTask(BindOnce(&NopTask), FROM_HERE), TimeTicks(),
        EnqueueOrder(), EnqueueOrder::FromIntForTesting(enqueue_order));
    return fake_task;
  }

  TaskQueueImpl::Task FakeNonNestableTaskWithEnqueueOrder(int enqueue_order) {
    TaskQueueImpl::Task fake_task(
        TaskQueue::PostedTask(BindOnce(&NopTask), FROM_HERE), TimeTicks(),
        EnqueueOrder(), EnqueueOrder::FromIntForTesting(enqueue_order));
    fake_task.nestable = Nestable::kNonNestable;
    return fake_task;
  }

  std::unique_ptr<RealTimeDomain> time_domain_;
  std::unique_ptr<TaskQueueImpl> task_queue_;
  std::unique_ptr<WorkQueue> work_queue_;
  std::unique_ptr<WorkQueueSets> work_queue_sets_;
  std::unique_ptr<TaskQueueImpl::TaskDeque> incoming_queue_;
};

TEST_F(WorkQueueTest, Empty) {
  EXPECT_TRUE(work_queue_->Empty());
  work_queue_->Push(FakeTaskWithEnqueueOrder(1));
  EXPECT_FALSE(work_queue_->Empty());
}

TEST_F(WorkQueueTest, Empty_IgnoresFences) {
  work_queue_->Push(FakeTaskWithEnqueueOrder(1));
  work_queue_->InsertFence(EnqueueOrder::blocking_fence());
  EXPECT_FALSE(work_queue_->Empty());
}

TEST_F(WorkQueueTest, GetFrontTaskEnqueueOrderQueueEmpty) {
  EnqueueOrder enqueue_order;
  EXPECT_FALSE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
}

TEST_F(WorkQueueTest, GetFrontTaskEnqueueOrder) {
  work_queue_->Push(FakeTaskWithEnqueueOrder(2));
  work_queue_->Push(FakeTaskWithEnqueueOrder(3));
  work_queue_->Push(FakeTaskWithEnqueueOrder(4));

  EnqueueOrder enqueue_order;
  EXPECT_TRUE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
  EXPECT_EQ(2ull, enqueue_order);
}

TEST_F(WorkQueueTest, GetFrontTaskQueueEmpty) {
  EXPECT_EQ(nullptr, work_queue_->GetFrontTask());
}

TEST_F(WorkQueueTest, GetFrontTask) {
  work_queue_->Push(FakeTaskWithEnqueueOrder(2));
  work_queue_->Push(FakeTaskWithEnqueueOrder(3));
  work_queue_->Push(FakeTaskWithEnqueueOrder(4));

  ASSERT_NE(nullptr, work_queue_->GetFrontTask());
  EXPECT_EQ(2ull, work_queue_->GetFrontTask()->enqueue_order());
}

TEST_F(WorkQueueTest, GetBackTask_Empty) {
  EXPECT_EQ(nullptr, work_queue_->GetBackTask());
}

TEST_F(WorkQueueTest, GetBackTask) {
  work_queue_->Push(FakeTaskWithEnqueueOrder(2));
  work_queue_->Push(FakeTaskWithEnqueueOrder(3));
  work_queue_->Push(FakeTaskWithEnqueueOrder(4));

  ASSERT_NE(nullptr, work_queue_->GetBackTask());
  EXPECT_EQ(4ull, work_queue_->GetBackTask()->enqueue_order());
}

TEST_F(WorkQueueTest, Push) {
  WorkQueue* work_queue;
  EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));

  work_queue_->Push(FakeTaskWithEnqueueOrder(2));
  EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
  EXPECT_EQ(work_queue_.get(), work_queue);
}

TEST_F(WorkQueueTest, PushAfterFenceHit) {
  work_queue_->InsertFence(EnqueueOrder::blocking_fence());
  WorkQueue* work_queue;
  EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));

  work_queue_->Push(FakeTaskWithEnqueueOrder(2));
  EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
}

TEST_F(WorkQueueTest, PushNonNestableTaskToFront) {
  WorkQueue* work_queue;
  EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));

  work_queue_->PushNonNestableTaskToFront(
      FakeNonNestableTaskWithEnqueueOrder(3));
  EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
  EXPECT_EQ(work_queue_.get(), work_queue);

  work_queue_->PushNonNestableTaskToFront(
      FakeNonNestableTaskWithEnqueueOrder(2));

  EXPECT_EQ(2ull, work_queue_->GetFrontTask()->enqueue_order());
  EXPECT_EQ(3ull, work_queue_->GetBackTask()->enqueue_order());
}

TEST_F(WorkQueueTest, PushNonNestableTaskToFrontAfterFenceHit) {
  work_queue_->InsertFence(EnqueueOrder::blocking_fence());
  WorkQueue* work_queue;
  EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));

  work_queue_->PushNonNestableTaskToFront(
      FakeNonNestableTaskWithEnqueueOrder(2));
  EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
}

TEST_F(WorkQueueTest, PushNonNestableTaskToFrontBeforeFenceHit) {
  work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(3));
  WorkQueue* work_queue;
  EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));

  work_queue_->PushNonNestableTaskToFront(
      FakeNonNestableTaskWithEnqueueOrder(2));
  EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
}

TEST_F(WorkQueueTest, ReloadEmptyImmediateQueue) {
  task_queue_->PushImmediateIncomingTaskForTest(FakeTaskWithEnqueueOrder(2));
  task_queue_->PushImmediateIncomingTaskForTest(FakeTaskWithEnqueueOrder(3));
  task_queue_->PushImmediateIncomingTaskForTest(FakeTaskWithEnqueueOrder(4));

  WorkQueue* work_queue;
  EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
  EXPECT_TRUE(work_queue_->Empty());
  work_queue_->ReloadEmptyImmediateQueue();

  EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
  EXPECT_FALSE(work_queue_->Empty());

  ASSERT_NE(nullptr, work_queue_->GetFrontTask());
  EXPECT_EQ(2ull, work_queue_->GetFrontTask()->enqueue_order());

  ASSERT_NE(nullptr, work_queue_->GetBackTask());
  EXPECT_EQ(4ull, work_queue_->GetBackTask()->enqueue_order());
}

TEST_F(WorkQueueTest, ReloadEmptyImmediateQueueAfterFenceHit) {
  work_queue_->InsertFence(EnqueueOrder::blocking_fence());
  task_queue_->PushImmediateIncomingTaskForTest(FakeTaskWithEnqueueOrder(2));
  task_queue_->PushImmediateIncomingTaskForTest(FakeTaskWithEnqueueOrder(3));
  task_queue_->PushImmediateIncomingTaskForTest(FakeTaskWithEnqueueOrder(4));

  WorkQueue* work_queue;
  EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
  EXPECT_TRUE(work_queue_->Empty());
  work_queue_->ReloadEmptyImmediateQueue();

  EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
  EXPECT_FALSE(work_queue_->Empty());

  ASSERT_NE(nullptr, work_queue_->GetFrontTask());
  EXPECT_EQ(2ull, work_queue_->GetFrontTask()->enqueue_order());

  ASSERT_NE(nullptr, work_queue_->GetBackTask());
  EXPECT_EQ(4ull, work_queue_->GetBackTask()->enqueue_order());
}

TEST_F(WorkQueueTest, TakeTaskFromWorkQueue) {
  work_queue_->Push(FakeTaskWithEnqueueOrder(2));
  work_queue_->Push(FakeTaskWithEnqueueOrder(3));
  work_queue_->Push(FakeTaskWithEnqueueOrder(4));

  WorkQueue* work_queue;
  EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
  EXPECT_FALSE(work_queue_->Empty());

  EXPECT_EQ(2ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
  EXPECT_EQ(3ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
  EXPECT_EQ(4ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());

  EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
  EXPECT_TRUE(work_queue_->Empty());
}

TEST_F(WorkQueueTest, TakeTaskFromWorkQueue_HitFence) {
  work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(3));
  work_queue_->Push(FakeTaskWithEnqueueOrder(2));
  work_queue_->Push(FakeTaskWithEnqueueOrder(4));
  EXPECT_FALSE(work_queue_->BlockedByFence());

  WorkQueue* work_queue;
  EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
  EXPECT_FALSE(work_queue_->Empty());
  EXPECT_FALSE(work_queue_->BlockedByFence());

  EXPECT_EQ(2ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
  EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
  EXPECT_FALSE(work_queue_->Empty());
  EXPECT_TRUE(work_queue_->BlockedByFence());
}

TEST_F(WorkQueueTest, InsertFenceBeforeEnqueueing) {
  EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::blocking_fence()));
  EXPECT_TRUE(work_queue_->BlockedByFence());

  work_queue_->Push(FakeTaskWithEnqueueOrder(2));
  work_queue_->Push(FakeTaskWithEnqueueOrder(3));
  work_queue_->Push(FakeTaskWithEnqueueOrder(4));

  EnqueueOrder enqueue_order;
  EXPECT_FALSE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
}

TEST_F(WorkQueueTest, InsertFenceAfterEnqueueingNonBlocking) {
  work_queue_->Push(FakeTaskWithEnqueueOrder(2));
  work_queue_->Push(FakeTaskWithEnqueueOrder(3));
  work_queue_->Push(FakeTaskWithEnqueueOrder(4));

  EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(5)));
  EXPECT_FALSE(work_queue_->BlockedByFence());

  EnqueueOrder enqueue_order;
  EXPECT_TRUE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
  EXPECT_EQ(2ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
}

TEST_F(WorkQueueTest, InsertFenceAfterEnqueueing) {
  work_queue_->Push(FakeTaskWithEnqueueOrder(2));
  work_queue_->Push(FakeTaskWithEnqueueOrder(3));
  work_queue_->Push(FakeTaskWithEnqueueOrder(4));

  // NB in reality a fence will always be greater than any currently enqueued
  // tasks.
  EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::blocking_fence()));
  EXPECT_TRUE(work_queue_->BlockedByFence());

  EnqueueOrder enqueue_order;
  EXPECT_FALSE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
}

TEST_F(WorkQueueTest, InsertNewFence) {
  work_queue_->Push(FakeTaskWithEnqueueOrder(2));
  work_queue_->Push(FakeTaskWithEnqueueOrder(4));
  work_queue_->Push(FakeTaskWithEnqueueOrder(5));

  EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(3)));
  EXPECT_FALSE(work_queue_->BlockedByFence());

  // Note until TakeTaskFromWorkQueue() is called we don't hit the fence.
  EnqueueOrder enqueue_order;
  EXPECT_TRUE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
  EXPECT_EQ(2ull, enqueue_order);

  EXPECT_EQ(2ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
  EXPECT_FALSE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
  EXPECT_TRUE(work_queue_->BlockedByFence());

  // Inserting the new fence should temporarily unblock the queue until the new
  // one is hit.
  EXPECT_TRUE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(6)));
  EXPECT_FALSE(work_queue_->BlockedByFence());

  EXPECT_TRUE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
  EXPECT_EQ(4ull, enqueue_order);
  EXPECT_EQ(4ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
  EXPECT_TRUE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
  EXPECT_FALSE(work_queue_->BlockedByFence());
}

TEST_F(WorkQueueTest, PushWithNonEmptyQueueDoesNotHitFence) {
  work_queue_->Push(FakeTaskWithEnqueueOrder(1));
  EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(2)));
  work_queue_->Push(FakeTaskWithEnqueueOrder(3));
  EXPECT_FALSE(work_queue_->BlockedByFence());
}

TEST_F(WorkQueueTest, RemoveFence) {
  work_queue_->Push(FakeTaskWithEnqueueOrder(2));
  work_queue_->Push(FakeTaskWithEnqueueOrder(4));
  work_queue_->Push(FakeTaskWithEnqueueOrder(5));
  work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(3));

  WorkQueue* work_queue;
  EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
  EXPECT_FALSE(work_queue_->Empty());

  EXPECT_EQ(2ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
  EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
  EXPECT_FALSE(work_queue_->Empty());
  EXPECT_TRUE(work_queue_->BlockedByFence());

  EXPECT_TRUE(work_queue_->RemoveFence());
  EXPECT_EQ(4ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
  EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
  EXPECT_FALSE(work_queue_->BlockedByFence());
}

TEST_F(WorkQueueTest, RemoveFenceButNoFence) {
  EXPECT_FALSE(work_queue_->RemoveFence());
}

TEST_F(WorkQueueTest, RemoveFenceNothingUnblocked) {
  EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::blocking_fence()));
  EXPECT_TRUE(work_queue_->BlockedByFence());

  EXPECT_FALSE(work_queue_->RemoveFence());
  EXPECT_FALSE(work_queue_->BlockedByFence());
}

TEST_F(WorkQueueTest, BlockedByFence) {
  EXPECT_FALSE(work_queue_->BlockedByFence());
  EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::blocking_fence()));
  EXPECT_TRUE(work_queue_->BlockedByFence());
}

TEST_F(WorkQueueTest, BlockedByFencePopBecomesEmpty) {
  work_queue_->Push(FakeTaskWithEnqueueOrder(1));
  EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(2)));
  EXPECT_FALSE(work_queue_->BlockedByFence());

  EXPECT_EQ(1ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
  EXPECT_TRUE(work_queue_->BlockedByFence());
}

TEST_F(WorkQueueTest, BlockedByFencePop) {
  work_queue_->Push(FakeTaskWithEnqueueOrder(1));
  EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(2)));
  EXPECT_FALSE(work_queue_->BlockedByFence());

  work_queue_->Push(FakeTaskWithEnqueueOrder(3));
  EXPECT_FALSE(work_queue_->BlockedByFence());

  EXPECT_EQ(1ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
  EXPECT_TRUE(work_queue_->BlockedByFence());
}

TEST_F(WorkQueueTest, InitiallyEmptyBlockedByFenceNewFenceUnblocks) {
  EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::blocking_fence()));
  EXPECT_TRUE(work_queue_->BlockedByFence());

  work_queue_->Push(FakeTaskWithEnqueueOrder(2));
  EXPECT_TRUE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(3)));
  EXPECT_FALSE(work_queue_->BlockedByFence());
}

TEST_F(WorkQueueTest, BlockedByFenceNewFenceUnblocks) {
  work_queue_->Push(FakeTaskWithEnqueueOrder(1));
  EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(2)));
  EXPECT_FALSE(work_queue_->BlockedByFence());

  work_queue_->Push(FakeTaskWithEnqueueOrder(3));
  EXPECT_FALSE(work_queue_->BlockedByFence());

  EXPECT_EQ(1ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
  EXPECT_TRUE(work_queue_->BlockedByFence());

  EXPECT_TRUE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(4)));
  EXPECT_FALSE(work_queue_->BlockedByFence());
}

TEST_F(WorkQueueTest, InsertFenceAfterEnqueuing) {
  work_queue_->Push(FakeTaskWithEnqueueOrder(2));
  work_queue_->Push(FakeTaskWithEnqueueOrder(3));
  work_queue_->Push(FakeTaskWithEnqueueOrder(4));
  EXPECT_FALSE(work_queue_->BlockedByFence());

  EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::blocking_fence()));
  EXPECT_TRUE(work_queue_->BlockedByFence());

  EnqueueOrder enqueue_order;
  EXPECT_FALSE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
}

TEST_F(WorkQueueTest, RemoveAllCanceledTasksFromFront) {
  {
    Cancelable cancelable;
    work_queue_->Push(FakeCancelableTaskWithEnqueueOrder(
        2, cancelable.weak_ptr_factory.GetWeakPtr()));
    work_queue_->Push(FakeCancelableTaskWithEnqueueOrder(
        3, cancelable.weak_ptr_factory.GetWeakPtr()));
    work_queue_->Push(FakeCancelableTaskWithEnqueueOrder(
        4, cancelable.weak_ptr_factory.GetWeakPtr()));
    work_queue_->Push(FakeTaskWithEnqueueOrder(5));
  }
  EXPECT_TRUE(work_queue_->RemoveAllCanceledTasksFromFront());

  EnqueueOrder enqueue_order;
  EXPECT_TRUE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
  EXPECT_EQ(5ull, enqueue_order);
}

TEST_F(WorkQueueTest, RemoveAllCanceledTasksFromFrontTasksNotCanceled) {
  {
    Cancelable cancelable;
    work_queue_->Push(FakeCancelableTaskWithEnqueueOrder(
        2, cancelable.weak_ptr_factory.GetWeakPtr()));
    work_queue_->Push(FakeCancelableTaskWithEnqueueOrder(
        3, cancelable.weak_ptr_factory.GetWeakPtr()));
    work_queue_->Push(FakeCancelableTaskWithEnqueueOrder(
        4, cancelable.weak_ptr_factory.GetWeakPtr()));
    work_queue_->Push(FakeTaskWithEnqueueOrder(5));
    EXPECT_FALSE(work_queue_->RemoveAllCanceledTasksFromFront());

    EnqueueOrder enqueue_order;
    EXPECT_TRUE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
    EXPECT_EQ(2ull, enqueue_order);
  }
}

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