// Copyright 2013 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 "cc/raster/task_graph_work_queue.h"

#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace cc {
namespace {

class FakeTaskImpl : public Task {
 public:
  FakeTaskImpl() = default;
  FakeTaskImpl(const FakeTaskImpl&) = delete;

  FakeTaskImpl& operator=(const FakeTaskImpl&) = delete;

  // Overridden from Task:
  void RunOnWorkerThread() override {}

 private:
  ~FakeTaskImpl() override = default;
};

TEST(TaskGraphWorkQueueTest, TestChangingDependency) {
  TaskGraphWorkQueue work_queue;
  NamespaceToken token = work_queue.GenerateNamespaceToken();

  // Create a graph with one task
  TaskGraph graph1;
  scoped_refptr<FakeTaskImpl> task(new FakeTaskImpl());
  graph1.nodes.push_back(TaskGraph::Node(task.get(), 0u, 0u, 0u));

  // Schedule the graph
  work_queue.ScheduleTasks(token, &graph1);

  // Run the task.
  TaskGraphWorkQueue::PrioritizedTask prioritized_task =
      work_queue.GetNextTaskToRun(0u);
  work_queue.CompleteTask(std::move(prioritized_task));

  // Create a graph where task1 has a dependency
  TaskGraph graph2;
  scoped_refptr<FakeTaskImpl> dependency_task(new FakeTaskImpl());
  graph2.nodes.push_back(TaskGraph::Node(task.get(), 0u, 0u, 1u));
  graph2.nodes.push_back(TaskGraph::Node(dependency_task.get(), 0u, 0u, 0u));
  graph2.edges.push_back(TaskGraph::Edge(dependency_task.get(), task.get()));

  // Schedule the second graph.
  work_queue.ScheduleTasks(token, &graph2);

  // Run the |dependency_task|
  TaskGraphWorkQueue::PrioritizedTask prioritized_dependency_task =
      work_queue.GetNextTaskToRun(0u);
  EXPECT_EQ(prioritized_dependency_task.task.get(), dependency_task.get());
  work_queue.CompleteTask(std::move(prioritized_dependency_task));

  // We should have no tasks to run, as the dependent task already completed.
  EXPECT_FALSE(work_queue.HasReadyToRunTasks());
}

// Tasks with same priority but in different category.
TEST(TaskGraphWorkQueueTest, TestTaskWithDifferentCategory) {
  TaskGraphWorkQueue work_queue;
  NamespaceToken token = work_queue.GenerateNamespaceToken();

  // Create a graph where | task| has dependencies.
  TaskGraph graph;
  scoped_refptr<FakeTaskImpl> task(new FakeTaskImpl());
  scoped_refptr<FakeTaskImpl> dependency_task1(new FakeTaskImpl());
  scoped_refptr<FakeTaskImpl> dependency_task2(new FakeTaskImpl());
  scoped_refptr<FakeTaskImpl> dependency_task3(new FakeTaskImpl());

  graph.nodes.push_back(TaskGraph::Node(task.get(), 0u, 0u, 3u));
  graph.nodes.push_back(TaskGraph::Node(dependency_task1.get(), 0u, 0u, 0u));
  graph.nodes.push_back(TaskGraph::Node(dependency_task2.get(), 1u, 0u, 0u));
  graph.nodes.push_back(TaskGraph::Node(dependency_task3.get(), 2u, 0u, 0u));

  graph.edges.push_back(TaskGraph::Edge(dependency_task1.get(), task.get()));
  graph.edges.push_back(TaskGraph::Edge(dependency_task2.get(), task.get()));
  graph.edges.push_back(TaskGraph::Edge(dependency_task3.get(), task.get()));

  // Schedule the graph.
  work_queue.ScheduleTasks(token, &graph);

  // Run the |dependency_task1|from category 0.
  TaskGraphWorkQueue::PrioritizedTask prioritized_dependency_task =
      work_queue.GetNextTaskToRun(0u);
  EXPECT_EQ(prioritized_dependency_task.task.get(), dependency_task1.get());
  work_queue.CompleteTask(std::move(prioritized_dependency_task));
  EXPECT_TRUE(work_queue.HasReadyToRunTasks());
  EXPECT_FALSE(work_queue.HasReadyToRunTasksForCategory(0u));
  EXPECT_TRUE(work_queue.HasReadyToRunTasksForCategory(1u));
  EXPECT_TRUE(work_queue.HasReadyToRunTasksForCategory(2u));

  // Run the |dependency_task2|from category 1.
  prioritized_dependency_task = work_queue.GetNextTaskToRun(1u);
  EXPECT_EQ(prioritized_dependency_task.task.get(), dependency_task2.get());
  work_queue.CompleteTask(std::move(prioritized_dependency_task));
  EXPECT_TRUE(work_queue.HasReadyToRunTasks());
  EXPECT_FALSE(work_queue.HasReadyToRunTasksForCategory(0u));
  EXPECT_FALSE(work_queue.HasReadyToRunTasksForCategory(1u));
  EXPECT_TRUE(work_queue.HasReadyToRunTasksForCategory(2u));

  // Run the |dependency_task3|from category 2.
  prioritized_dependency_task = work_queue.GetNextTaskToRun(2u);
  EXPECT_EQ(prioritized_dependency_task.task.get(), dependency_task3.get());
  work_queue.CompleteTask(std::move(prioritized_dependency_task));
  EXPECT_TRUE(work_queue.HasReadyToRunTasks());
  // Once all dependencies from different category completed, | task| turns
  // ready to run.
  EXPECT_TRUE(work_queue.HasReadyToRunTasksForCategory(0u));
  EXPECT_FALSE(work_queue.HasReadyToRunTasksForCategory(1u));
  EXPECT_FALSE(work_queue.HasReadyToRunTasksForCategory(2u));

  prioritized_dependency_task = work_queue.GetNextTaskToRun(0u);
  EXPECT_EQ(prioritized_dependency_task.task.get(), task.get());
  work_queue.CompleteTask(std::move(prioritized_dependency_task));
  EXPECT_FALSE(work_queue.HasReadyToRunTasks());
}

// Tasks with different priority run in a priority order. But need to guarantee
// its dependences are completed.
TEST(TaskGraphWorkQueueTest, TestTaskWithDifferentPriority) {
  TaskGraphWorkQueue work_queue;
  NamespaceToken token = work_queue.GenerateNamespaceToken();
  {
    // Create a graph where task has a dependency
    TaskGraph graph;
    scoped_refptr<FakeTaskImpl> task(new FakeTaskImpl());
    scoped_refptr<FakeTaskImpl> dependency_task1(new FakeTaskImpl());
    scoped_refptr<FakeTaskImpl> dependency_task2(new FakeTaskImpl());
    scoped_refptr<FakeTaskImpl> dependency_task3(new FakeTaskImpl());

    // | task| has the lowest priority and 3 dependences, will run last.
    graph.nodes.push_back(TaskGraph::Node(task.get(), 0u, 2u, 3u));
    graph.nodes.push_back(TaskGraph::Node(dependency_task1.get(), 0u, 3u, 0u));
    graph.nodes.push_back(TaskGraph::Node(dependency_task2.get(), 0u, 2u, 0u));
    graph.nodes.push_back(TaskGraph::Node(dependency_task3.get(), 0u, 1u, 0u));

    graph.edges.push_back(TaskGraph::Edge(dependency_task1.get(), task.get()));
    graph.edges.push_back(TaskGraph::Edge(dependency_task2.get(), task.get()));
    graph.edges.push_back(TaskGraph::Edge(dependency_task3.get(), task.get()));

    // Schedule the graph.
    work_queue.ScheduleTasks(token, &graph);

    // Run the |dependency_task|
    TaskGraphWorkQueue::PrioritizedTask prioritized_dependency_task =
        work_queue.GetNextTaskToRun(0u);
    EXPECT_EQ(prioritized_dependency_task.task.get(), dependency_task3.get());
    work_queue.CompleteTask(std::move(prioritized_dependency_task));
    EXPECT_TRUE(work_queue.HasReadyToRunTasks());

    prioritized_dependency_task = work_queue.GetNextTaskToRun(0u);
    EXPECT_EQ(prioritized_dependency_task.task.get(), dependency_task2.get());
    work_queue.CompleteTask(std::move(prioritized_dependency_task));
    EXPECT_TRUE(work_queue.HasReadyToRunTasks());

    prioritized_dependency_task = work_queue.GetNextTaskToRun(0u);
    EXPECT_EQ(prioritized_dependency_task.task.get(), dependency_task1.get());
    work_queue.CompleteTask(std::move(prioritized_dependency_task));
    EXPECT_TRUE(work_queue.HasReadyToRunTasks());

    // | task| runs last.
    prioritized_dependency_task = work_queue.GetNextTaskToRun(0u);
    EXPECT_EQ(prioritized_dependency_task.task.get(), task.get());
    work_queue.CompleteTask(std::move(prioritized_dependency_task));
    EXPECT_FALSE(work_queue.HasReadyToRunTasks());
  }

  {
    // Create a graph where task has dependencies
    TaskGraph graph;
    scoped_refptr<FakeTaskImpl> task(new FakeTaskImpl());
    scoped_refptr<FakeTaskImpl> dependency_task1(new FakeTaskImpl());
    scoped_refptr<FakeTaskImpl> dependency_task2(new FakeTaskImpl());
    scoped_refptr<FakeTaskImpl> dependency_task3(new FakeTaskImpl());

    // | task| has the highest priority and 3 dependences, also will run last.
    graph.nodes.push_back(TaskGraph::Node(task.get(), 0u, 0u, 3u));
    graph.nodes.push_back(TaskGraph::Node(dependency_task1.get(), 0u, 3u, 0u));
    graph.nodes.push_back(TaskGraph::Node(dependency_task2.get(), 0u, 2u, 0u));
    graph.nodes.push_back(TaskGraph::Node(dependency_task3.get(), 0u, 1u, 0u));

    graph.edges.push_back(TaskGraph::Edge(dependency_task1.get(), task.get()));
    graph.edges.push_back(TaskGraph::Edge(dependency_task2.get(), task.get()));
    graph.edges.push_back(TaskGraph::Edge(dependency_task3.get(), task.get()));

    // Schedule the graph.
    work_queue.ScheduleTasks(token, &graph);

    // Run the |dependency_task|
    TaskGraphWorkQueue::PrioritizedTask prioritized_dependency_task =
        work_queue.GetNextTaskToRun(0u);
    EXPECT_EQ(prioritized_dependency_task.task.get(), dependency_task3.get());
    work_queue.CompleteTask(std::move(prioritized_dependency_task));
    EXPECT_TRUE(work_queue.HasReadyToRunTasks());

    prioritized_dependency_task = work_queue.GetNextTaskToRun(0u);
    EXPECT_EQ(prioritized_dependency_task.task.get(), dependency_task2.get());
    work_queue.CompleteTask(std::move(prioritized_dependency_task));
    EXPECT_TRUE(work_queue.HasReadyToRunTasks());

    prioritized_dependency_task = work_queue.GetNextTaskToRun(0u);
    EXPECT_EQ(prioritized_dependency_task.task.get(), dependency_task1.get());
    work_queue.CompleteTask(std::move(prioritized_dependency_task));
    EXPECT_TRUE(work_queue.HasReadyToRunTasks());

    // | task| runs last.
    prioritized_dependency_task = work_queue.GetNextTaskToRun(0u);
    EXPECT_EQ(prioritized_dependency_task.task.get(), task.get());
    work_queue.CompleteTask(std::move(prioritized_dependency_task));
    EXPECT_FALSE(work_queue.HasReadyToRunTasks());
  }
}

}  // namespace
}  // namespace cc
