// 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/resources/worker_pool.h"

#include <vector>

#include "cc/base/completion_event.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace cc {

namespace {

class FakeWorkerPoolTaskImpl : public internal::WorkerPoolTask {
 public:
  FakeWorkerPoolTaskImpl(const base::Closure& callback,
                         const base::Closure& reply)
      : callback_(callback),
        reply_(reply) {
  }

  // Overridden from internal::WorkerPoolTask:
  virtual void RunOnWorkerThread(unsigned thread_index) OVERRIDE {
    if (!callback_.is_null())
      callback_.Run();
  }
  virtual void CompleteOnOriginThread() OVERRIDE {
    if (!reply_.is_null())
      reply_.Run();
  }

 private:
  virtual ~FakeWorkerPoolTaskImpl() {}

  const base::Closure callback_;
  const base::Closure reply_;

  DISALLOW_COPY_AND_ASSIGN(FakeWorkerPoolTaskImpl);
};

class FakeWorkerPool : public WorkerPool {
 public:
  struct Task {
    Task(const base::Closure& callback,
         const base::Closure& reply,
         const base::Closure& dependent,
         unsigned dependent_count,
         unsigned priority) : callback(callback),
                              reply(reply),
                              dependent(dependent),
                              dependent_count(dependent_count),
                              priority(priority) {
    }

    base::Closure callback;
    base::Closure reply;
    base::Closure dependent;
    unsigned dependent_count;
    unsigned priority;
  };
  FakeWorkerPool() : WorkerPool(1, "test") {}
  virtual ~FakeWorkerPool() {}

  static scoped_ptr<FakeWorkerPool> Create() {
    return make_scoped_ptr(new FakeWorkerPool);
  }

  void ScheduleTasks(const std::vector<Task>& tasks) {
    TaskVector new_tasks;
    TaskVector new_dependents;
    TaskGraph new_graph;

    scoped_refptr<FakeWorkerPoolTaskImpl> new_completion_task(
        new FakeWorkerPoolTaskImpl(
            base::Bind(&FakeWorkerPool::OnTasksCompleted,
                       base::Unretained(this)),
            base::Closure()));
    scoped_ptr<internal::GraphNode> completion_node(
        new internal::GraphNode(new_completion_task.get(), 0u));

    for (std::vector<Task>::const_iterator it = tasks.begin();
         it != tasks.end(); ++it) {
      scoped_refptr<FakeWorkerPoolTaskImpl> new_task(
          new FakeWorkerPoolTaskImpl(it->callback, it->reply));
      scoped_ptr<internal::GraphNode> node(
          new internal::GraphNode(new_task.get(), it->priority));

      DCHECK(it->dependent_count);
      for (unsigned i = 0; i < it->dependent_count; ++i) {
        scoped_refptr<FakeWorkerPoolTaskImpl> new_dependent_task(
            new FakeWorkerPoolTaskImpl(it->dependent, base::Closure()));
        scoped_ptr<internal::GraphNode> dependent_node(
            new internal::GraphNode(new_dependent_task.get(), it->priority));
        dependent_node->add_dependent(completion_node.get());
        completion_node->add_dependency();
        node->add_dependent(dependent_node.get());
        dependent_node->add_dependency();
        new_graph.set(new_dependent_task.get(), dependent_node.Pass());
        new_dependents.push_back(new_dependent_task.get());
      }

      new_graph.set(new_task.get(), node.Pass());
      new_tasks.push_back(new_task.get());
    }

    new_graph.set(new_completion_task.get(), completion_node.Pass());

    scheduled_tasks_completion_.reset(new CompletionEvent);

    SetTaskGraph(&new_graph);

    dependents_.swap(new_dependents);
    completion_task_.swap(new_completion_task);
    tasks_.swap(new_tasks);
  }

  void WaitForTasksToComplete() {
    DCHECK(scheduled_tasks_completion_);
    scheduled_tasks_completion_->Wait();
  }

 private:
  typedef std::vector<scoped_refptr<internal::WorkerPoolTask> > TaskVector;

  void OnTasksCompleted() {
    DCHECK(scheduled_tasks_completion_);
    scheduled_tasks_completion_->Signal();
  }

  TaskVector tasks_;
  TaskVector dependents_;
  scoped_refptr<FakeWorkerPoolTaskImpl> completion_task_;
  scoped_ptr<CompletionEvent> scheduled_tasks_completion_;

  DISALLOW_COPY_AND_ASSIGN(FakeWorkerPool);
};

class WorkerPoolTest : public testing::Test {
 public:
  WorkerPoolTest() {}
  virtual ~WorkerPoolTest() {}

  // Overridden from testing::Test:
  virtual void SetUp() OVERRIDE {
    worker_pool_ = FakeWorkerPool::Create();
  }
  virtual void TearDown() OVERRIDE {
    worker_pool_->Shutdown();
    worker_pool_->CheckForCompletedTasks();
  }

  void ResetIds() {
    run_task_ids_.clear();
    on_task_completed_ids_.clear();
  }

  void RunAllTasks() {
    worker_pool_->WaitForTasksToComplete();
    worker_pool_->CheckForCompletedTasks();
  }

  FakeWorkerPool* worker_pool() {
    return worker_pool_.get();
  }

  void RunTask(unsigned id) {
    run_task_ids_.push_back(id);
  }

  void OnTaskCompleted(unsigned id) {
    on_task_completed_ids_.push_back(id);
  }

  const std::vector<unsigned>& run_task_ids() {
    return run_task_ids_;
  }

  const std::vector<unsigned>& on_task_completed_ids() {
    return on_task_completed_ids_;
  }

 private:
  scoped_ptr<FakeWorkerPool> worker_pool_;
  std::vector<unsigned> run_task_ids_;
  std::vector<unsigned> on_task_completed_ids_;
};

TEST_F(WorkerPoolTest, Basic) {
  EXPECT_EQ(0u, run_task_ids().size());
  EXPECT_EQ(0u, on_task_completed_ids().size());

  worker_pool()->ScheduleTasks(
      std::vector<FakeWorkerPool::Task>(
          1,
          FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
                                          base::Unretained(this),
                                          0u),
                               base::Bind(&WorkerPoolTest::OnTaskCompleted,
                                          base::Unretained(this),
                                          0u),
                               base::Closure(),
                               1u,
                               0u)));
  RunAllTasks();

  EXPECT_EQ(1u, run_task_ids().size());
  EXPECT_EQ(1u, on_task_completed_ids().size());

  worker_pool()->ScheduleTasks(
      std::vector<FakeWorkerPool::Task>(
          1,
          FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
                                          base::Unretained(this),
                                          0u),
                               base::Bind(&WorkerPoolTest::OnTaskCompleted,
                                          base::Unretained(this),
                                          0u),
                               base::Bind(&WorkerPoolTest::RunTask,
                                          base::Unretained(this),
                                          0u),
                               1u,
                               0u)));
  RunAllTasks();

  EXPECT_EQ(3u, run_task_ids().size());
  EXPECT_EQ(2u, on_task_completed_ids().size());

  worker_pool()->ScheduleTasks(
      std::vector<FakeWorkerPool::Task>(
          1, FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
                                             base::Unretained(this),
                                             0u),
                                  base::Bind(&WorkerPoolTest::OnTaskCompleted,
                                             base::Unretained(this),
                                             0u),
                                  base::Bind(&WorkerPoolTest::RunTask,
                                             base::Unretained(this),
                                             0u),
                                  2u,
                                  0u)));
  RunAllTasks();

  EXPECT_EQ(6u, run_task_ids().size());
  EXPECT_EQ(3u, on_task_completed_ids().size());
}

TEST_F(WorkerPoolTest, Dependencies) {
  worker_pool()->ScheduleTasks(
      std::vector<FakeWorkerPool::Task>(
          1, FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
                                             base::Unretained(this),
                                             0u),
                                  base::Bind(&WorkerPoolTest::OnTaskCompleted,
                                             base::Unretained(this),
                                             0u),
                                  base::Bind(&WorkerPoolTest::RunTask,
                                             base::Unretained(this),
                                             1u),
                                  1u,
                                  0u)));
  RunAllTasks();

  // Check if task ran before dependent.
  ASSERT_EQ(2u, run_task_ids().size());
  EXPECT_EQ(0u, run_task_ids()[0]);
  EXPECT_EQ(1u, run_task_ids()[1]);
  ASSERT_EQ(1u, on_task_completed_ids().size());
  EXPECT_EQ(0u, on_task_completed_ids()[0]);

  worker_pool()->ScheduleTasks(
      std::vector<FakeWorkerPool::Task>(
          1, FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
                                             base::Unretained(this),
                                             2u),
                                  base::Bind(&WorkerPoolTest::OnTaskCompleted,
                                             base::Unretained(this),
                                             2u),
                                  base::Bind(&WorkerPoolTest::RunTask,
                                             base::Unretained(this),
                                             3u),
                                  2u,
                                  0u)));
  RunAllTasks();

  // Task should only run once.
  ASSERT_EQ(5u, run_task_ids().size());
  EXPECT_EQ(2u, run_task_ids()[2]);
  EXPECT_EQ(3u, run_task_ids()[3]);
  EXPECT_EQ(3u, run_task_ids()[4]);
  ASSERT_EQ(2u, on_task_completed_ids().size());
  EXPECT_EQ(2u, on_task_completed_ids()[1]);
}

TEST_F(WorkerPoolTest, Priority) {
  {
    FakeWorkerPool::Task tasks[] = {
        FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
                                        base::Unretained(this),
                                        0u),
                             base::Bind(&WorkerPoolTest::OnTaskCompleted,
                                        base::Unretained(this),
                                        0u),
                             base::Bind(&WorkerPoolTest::RunTask,
                                        base::Unretained(this),
                                        2u),
                             1u,
                             1u),  // Priority 1
        FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
                                        base::Unretained(this),
                                        1u),
                             base::Bind(&WorkerPoolTest::OnTaskCompleted,
                                        base::Unretained(this),
                                        1u),
                             base::Bind(&WorkerPoolTest::RunTask,
                                        base::Unretained(this),
                                        3u),
                             1u,
                             0u)  // Priority 0
    };
    worker_pool()->ScheduleTasks(
        std::vector<FakeWorkerPool::Task>(tasks, tasks + arraysize(tasks)));
  }
  RunAllTasks();

  // Check if tasks ran in order of priority.
  ASSERT_EQ(4u, run_task_ids().size());
  EXPECT_EQ(1u, run_task_ids()[0]);
  EXPECT_EQ(3u, run_task_ids()[1]);
  EXPECT_EQ(0u, run_task_ids()[2]);
  EXPECT_EQ(2u, run_task_ids()[3]);
  ASSERT_EQ(2u, on_task_completed_ids().size());
  EXPECT_EQ(1u, on_task_completed_ids()[0]);
  EXPECT_EQ(0u, on_task_completed_ids()[1]);

  ResetIds();
  {
    std::vector<FakeWorkerPool::Task> tasks;
    tasks.push_back(
        FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
                                        base::Unretained(this),
                                        0u),
                             base::Bind(&WorkerPoolTest::OnTaskCompleted,
                                        base::Unretained(this),
                                        0u),
                             base::Bind(&WorkerPoolTest::RunTask,
                                        base::Unretained(this),
                                        3u),
                             1u,    // 1 dependent
                             1u));  // Priority 1
    tasks.push_back(
        FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
                                        base::Unretained(this),
                                        1u),
                             base::Bind(&WorkerPoolTest::OnTaskCompleted,
                                        base::Unretained(this),
                                        1u),
                             base::Bind(&WorkerPoolTest::RunTask,
                                        base::Unretained(this),
                                        4u),
                             2u,    // 2 dependents
                             1u));  // Priority 1
    tasks.push_back(
        FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
                                        base::Unretained(this),
                                        2u),
                             base::Bind(&WorkerPoolTest::OnTaskCompleted,
                                        base::Unretained(this),
                                        2u),
                             base::Bind(&WorkerPoolTest::RunTask,
                                        base::Unretained(this),
                                        5u),
                             1u,    // 1 dependent
                             0u));  // Priority 0
    worker_pool()->ScheduleTasks(tasks);
  }
  RunAllTasks();

  // Check if tasks ran in order of priority and that task with more
  // dependents ran first when priority is the same.
  ASSERT_LE(3u, run_task_ids().size());
  EXPECT_EQ(2u, run_task_ids()[0]);
  EXPECT_EQ(5u, run_task_ids()[1]);
  EXPECT_EQ(1u, run_task_ids()[2]);
  ASSERT_EQ(3u, on_task_completed_ids().size());
  EXPECT_EQ(2u, on_task_completed_ids()[0]);
  EXPECT_EQ(1u, on_task_completed_ids()[1]);
  EXPECT_EQ(0u, on_task_completed_ids()[2]);
}

}  // namespace

}  // namespace cc