// Copyright 2014 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 "content/renderer/gpu/frame_swap_message_queue.h"

#include <utility>

#include "base/memory/ptr_util.h"
#include "ipc/ipc_message.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

class FrameSwapMessageQueueTest : public testing::Test {
 public:
  FrameSwapMessageQueueTest()
      : first_message_(41, 1, IPC::Message::PRIORITY_NORMAL),
        second_message_(42, 2, IPC::Message::PRIORITY_NORMAL),
        third_message_(43, 3, IPC::Message::PRIORITY_NORMAL),
        queue_(new FrameSwapMessageQueue()) {}

 protected:
  void QueueNextSwapMessage(std::unique_ptr<IPC::Message> msg) {
    queue_->QueueMessageForFrame(MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 0,
                                 std::move(msg), NULL);
  }

  void QueueNextSwapMessage(std::unique_ptr<IPC::Message> msg, bool* first) {
    queue_->QueueMessageForFrame(MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 0,
                                 std::move(msg), first);
  }

  void QueueVisualStateMessage(int source_frame_number,
                               std::unique_ptr<IPC::Message> msg) {
    queue_->QueueMessageForFrame(MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE,
                                 source_frame_number, std::move(msg), NULL);
  }

  void QueueVisualStateMessage(int source_frame_number,
                               std::unique_ptr<IPC::Message> msg,
                               bool* first) {
    queue_->QueueMessageForFrame(MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE,
                                 source_frame_number, std::move(msg), first);
  }

  void DrainMessages(int source_frame_number,
                     std::vector<std::unique_ptr<IPC::Message>>* messages) {
    messages->clear();
    queue_->DidActivate(source_frame_number);
    queue_->DidSwap(source_frame_number);
    std::unique_ptr<FrameSwapMessageQueue::SendMessageScope>
        send_message_scope = queue_->AcquireSendMessageScope();
    queue_->DrainMessages(messages);
  }

  bool HasMessageForId(
      const std::vector<std::unique_ptr<IPC::Message>>& messages,
      int routing_id) {
    for (const auto& msg : messages) {
      if (msg->routing_id() == routing_id)
        return true;
    }
    return false;
  }

  std::unique_ptr<IPC::Message> CloneMessage(const IPC::Message& other) {
    return base::WrapUnique(new IPC::Message(other));
  }

  void TestDidNotSwap(cc::SwapPromise::DidNotSwapReason reason);

  IPC::Message first_message_;
  IPC::Message second_message_;
  IPC::Message third_message_;
  scoped_refptr<FrameSwapMessageQueue> queue_;
};

TEST_F(FrameSwapMessageQueueTest, TestEmptyQueueDrain) {
  std::vector<std::unique_ptr<IPC::Message>> messages;

  DrainMessages(0, &messages);
  ASSERT_TRUE(messages.empty());
}

TEST_F(FrameSwapMessageQueueTest, TestEmpty) {
  std::vector<std::unique_ptr<IPC::Message>> messages;
  ASSERT_TRUE(queue_->Empty());
  QueueNextSwapMessage(CloneMessage(first_message_));
  ASSERT_FALSE(queue_->Empty());
  DrainMessages(0, &messages);
  ASSERT_TRUE(queue_->Empty());
  QueueVisualStateMessage(1, CloneMessage(first_message_));
  ASSERT_FALSE(queue_->Empty());
  queue_->DidActivate(1);
  queue_->DidSwap(1);
  ASSERT_FALSE(queue_->Empty());
}

TEST_F(FrameSwapMessageQueueTest, TestQueueMessageFirst) {
  std::vector<std::unique_ptr<IPC::Message>> messages;
  bool visual_state_first = false;
  bool next_swap_first = false;

  // Queuing the first time should result in true.
  QueueVisualStateMessage(1, CloneMessage(first_message_), &visual_state_first);
  ASSERT_TRUE(visual_state_first);
  // Queuing the second time should result in true.
  QueueVisualStateMessage(
      1, CloneMessage(second_message_), &visual_state_first);
  ASSERT_FALSE(visual_state_first);
  // Queuing for a different frame should result in true.
  QueueVisualStateMessage(2, CloneMessage(first_message_), &visual_state_first);
  ASSERT_TRUE(visual_state_first);

  // Queuing for a different policy should result in true.
  QueueNextSwapMessage(CloneMessage(first_message_), &next_swap_first);
  ASSERT_TRUE(next_swap_first);
  // Second time for the same policy is still false.
  QueueNextSwapMessage(CloneMessage(first_message_), &next_swap_first);
  ASSERT_FALSE(next_swap_first);

  DrainMessages(4, &messages);
  // Queuing after all messages are drained is a true again.
  QueueVisualStateMessage(4, CloneMessage(first_message_), &visual_state_first);
  ASSERT_TRUE(visual_state_first);
}

TEST_F(FrameSwapMessageQueueTest, TestNextSwapMessageSentWithNextFrame) {
  std::vector<std::unique_ptr<IPC::Message>> messages;

  DrainMessages(1, &messages);
  QueueNextSwapMessage(CloneMessage(first_message_));
  DrainMessages(2, &messages);
  ASSERT_EQ(1u, messages.size());
  ASSERT_EQ(first_message_.routing_id(), messages.front()->routing_id());
  messages.clear();

  DrainMessages(2, &messages);
  ASSERT_TRUE(messages.empty());
}

TEST_F(FrameSwapMessageQueueTest, TestNextSwapMessageSentWithCurrentFrame) {
  std::vector<std::unique_ptr<IPC::Message>> messages;

  DrainMessages(1, &messages);
  QueueNextSwapMessage(CloneMessage(first_message_));
  DrainMessages(1, &messages);
  ASSERT_EQ(1u, messages.size());
  ASSERT_EQ(first_message_.routing_id(), messages.front()->routing_id());
  messages.clear();

  DrainMessages(1, &messages);
  ASSERT_TRUE(messages.empty());
}

TEST_F(FrameSwapMessageQueueTest,
       TestDrainsVisualStateMessagesForCorrespondingFrames) {
  std::vector<std::unique_ptr<IPC::Message>> messages;

  QueueVisualStateMessage(1, CloneMessage(first_message_));
  QueueVisualStateMessage(2, CloneMessage(second_message_));
  QueueVisualStateMessage(3, CloneMessage(third_message_));
  DrainMessages(0, &messages);
  ASSERT_TRUE(messages.empty());

  DrainMessages(2, &messages);
  ASSERT_EQ(2u, messages.size());
  ASSERT_TRUE(HasMessageForId(messages, first_message_.routing_id()));
  ASSERT_TRUE(HasMessageForId(messages, second_message_.routing_id()));
  messages.clear();

  DrainMessages(2, &messages);
  ASSERT_TRUE(messages.empty());

  DrainMessages(5, &messages);
  ASSERT_EQ(1u, messages.size());
  ASSERT_EQ(third_message_.routing_id(), messages.front()->routing_id());
}

TEST_F(FrameSwapMessageQueueTest,
       TestQueueNextSwapMessagePreservesFifoOrdering) {
  std::vector<std::unique_ptr<IPC::Message>> messages;

  QueueNextSwapMessage(CloneMessage(first_message_));
  QueueNextSwapMessage(CloneMessage(second_message_));
  DrainMessages(1, &messages);
  ASSERT_EQ(2u, messages.size());
  ASSERT_EQ(first_message_.routing_id(), messages[0]->routing_id());
  ASSERT_EQ(second_message_.routing_id(), messages[1]->routing_id());
}

TEST_F(FrameSwapMessageQueueTest,
       TestQueueVisualStateMessagePreservesFifoOrdering) {
  std::vector<std::unique_ptr<IPC::Message>> messages;

  QueueVisualStateMessage(1, CloneMessage(first_message_));
  QueueVisualStateMessage(1, CloneMessage(second_message_));
  DrainMessages(1, &messages);
  ASSERT_EQ(2u, messages.size());
  ASSERT_EQ(first_message_.routing_id(), messages[0]->routing_id());
  ASSERT_EQ(second_message_.routing_id(), messages[1]->routing_id());
}

void FrameSwapMessageQueueTest::TestDidNotSwap(
    cc::SwapPromise::DidNotSwapReason reason) {
  std::vector<std::unique_ptr<IPC::Message>> messages;

  QueueNextSwapMessage(CloneMessage(first_message_));
  QueueVisualStateMessage(2, CloneMessage(second_message_));
  QueueVisualStateMessage(3, CloneMessage(third_message_));
  const int rid[] = {first_message_.routing_id(),
                     second_message_.routing_id(),
                     third_message_.routing_id()};

  bool msg_delivered = reason != cc::SwapPromise::COMMIT_FAILS &&
                       reason != cc::SwapPromise::ACTIVATION_FAILS;

  queue_->DidNotSwap(2, reason, &messages);
  ASSERT_TRUE(msg_delivered == HasMessageForId(messages, rid[0]));
  ASSERT_TRUE(msg_delivered == HasMessageForId(messages, rid[1]));
  ASSERT_FALSE(HasMessageForId(messages, rid[2]));
  messages.clear();

  queue_->DidNotSwap(3, reason, &messages);
  ASSERT_FALSE(HasMessageForId(messages, rid[0]));
  ASSERT_FALSE(HasMessageForId(messages, rid[1]));
  ASSERT_TRUE(msg_delivered == HasMessageForId(messages, rid[2]));
  messages.clear();

  // all undelivered messages should still be available for RenderFrameHostImpl
  // to deliver.
  DrainMessages(3, &messages);
  ASSERT_TRUE(msg_delivered != HasMessageForId(messages, rid[0]));
  ASSERT_TRUE(msg_delivered != HasMessageForId(messages, rid[1]));
  ASSERT_TRUE(msg_delivered != HasMessageForId(messages, rid[2]));
}

TEST_F(FrameSwapMessageQueueTest, TestDidNotSwapNoUpdate) {
  TestDidNotSwap(cc::SwapPromise::COMMIT_NO_UPDATE);
}

TEST_F(FrameSwapMessageQueueTest, TestDidNotSwapSwapFails) {
  TestDidNotSwap(cc::SwapPromise::SWAP_FAILS);
}

TEST_F(FrameSwapMessageQueueTest, TestDidNotSwapCommitFails) {
  TestDidNotSwap(cc::SwapPromise::COMMIT_FAILS);
}

TEST_F(FrameSwapMessageQueueTest, TestDidNotSwapActivationFails) {
  TestDidNotSwap(cc::SwapPromise::ACTIVATION_FAILS);
}

class NotifiesDeletionMessage : public IPC::Message {
 public:
  NotifiesDeletionMessage(bool* deleted, const IPC::Message& other)
      : IPC::Message(other), deleted_(deleted) {}
  ~NotifiesDeletionMessage() override { *deleted_ = true; }

 private:
  bool* deleted_;
};

TEST_F(FrameSwapMessageQueueTest, TestDeletesNextSwapMessage) {
  bool message_deleted = false;
  QueueNextSwapMessage(base::WrapUnique(
      new NotifiesDeletionMessage(&message_deleted, first_message_)));
  queue_ = NULL;
  ASSERT_TRUE(message_deleted);
}

TEST_F(FrameSwapMessageQueueTest, TestDeletesVisualStateMessage) {
  bool message_deleted = false;
  QueueVisualStateMessage(1, base::WrapUnique(new NotifiesDeletionMessage(
                                 &message_deleted, first_message_)));
  queue_ = NULL;
  ASSERT_TRUE(message_deleted);
}

TEST_F(FrameSwapMessageQueueTest, TestDeletesQueuedVisualStateMessage) {
  bool message_deleted = false;
  QueueVisualStateMessage(1, base::WrapUnique(new NotifiesDeletionMessage(
                                 &message_deleted, first_message_)));
  queue_->DidActivate(1);
  queue_->DidSwap(1);
  queue_ = NULL;
  ASSERT_TRUE(message_deleted);
}

}  // namespace content
