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

// Tests for the Command Buffer Helper.

#include "gpu/command_buffer/client/transfer_buffer.h"

#include <stddef.h>
#include <stdint.h>

#include <memory>

#include "base/compiler_specific.h"
#include "base/memory/aligned_memory.h"
#include "gpu/command_buffer/client/client_test_helper.h"
#include "gpu/command_buffer/client/cmd_buffer_helper.h"
#include "gpu/command_buffer/common/command_buffer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;
using ::testing::AtMost;
using ::testing::DoAll;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StrictMock;

namespace gpu {


class TransferBufferTest : public testing::Test {
 protected:
  static const int32_t kNumCommandEntries = 400;
  static const int32_t kCommandBufferSizeBytes =
      kNumCommandEntries * sizeof(CommandBufferEntry);
  static const uint32_t kStartingOffset = 64;
  static const uint32_t kAlignment = 4;
  static const uint32_t kTransferBufferSize = 256;

  TransferBufferTest()
      : transfer_buffer_id_(0) {
  }

  void SetUp() override;
  void TearDown() override;

  virtual void Initialize() {
    ASSERT_TRUE(transfer_buffer_->Initialize(
        kTransferBufferSize, kStartingOffset, kTransferBufferSize,
        kTransferBufferSize, kAlignment));
  }

  MockClientCommandBufferMockFlush* command_buffer() const {
    return command_buffer_.get();
  }

  std::unique_ptr<MockClientCommandBufferMockFlush> command_buffer_;
  std::unique_ptr<CommandBufferHelper> helper_;
  std::unique_ptr<TransferBuffer> transfer_buffer_;
  int32_t transfer_buffer_id_;
};

void TransferBufferTest::SetUp() {
  command_buffer_ =
      std::make_unique<StrictMock<MockClientCommandBufferMockFlush>>();

  helper_ = std::make_unique<CommandBufferHelper>(command_buffer());
  ASSERT_EQ(helper_->Initialize(kCommandBufferSizeBytes),
            gpu::ContextResult::kSuccess);

  transfer_buffer_id_ = command_buffer()->GetNextFreeTransferBufferId();

  transfer_buffer_ = std::make_unique<TransferBuffer>(helper_.get());
}

void TransferBufferTest::TearDown() {
  if (transfer_buffer_->HaveBuffer()) {
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
  }
  // For command buffer.
  EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*command_buffer(), Flush(_)).Times(AtMost(1));
  EXPECT_CALL(*command_buffer(), OrderingBarrier(_)).Times(AtMost(2));
  transfer_buffer_.reset();
}

const int32_t TransferBufferTest::kNumCommandEntries;
const int32_t TransferBufferTest::kCommandBufferSizeBytes;
const uint32_t TransferBufferTest::kStartingOffset;
const uint32_t TransferBufferTest::kAlignment;
const uint32_t TransferBufferTest::kTransferBufferSize;

TEST_F(TransferBufferTest, Basic) {
  Initialize();
  EXPECT_TRUE(transfer_buffer_->HaveBuffer());
  EXPECT_EQ(transfer_buffer_id_, transfer_buffer_->GetShmId());
  EXPECT_EQ(
      kTransferBufferSize - kStartingOffset,
      transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
  EXPECT_NE(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());
}

TEST_F(TransferBufferTest, Free) {
  Initialize();
  EXPECT_TRUE(transfer_buffer_->HaveBuffer());
  EXPECT_EQ(transfer_buffer_id_, transfer_buffer_->GetShmId());
  EXPECT_NE(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());

  // Free buffer.
  EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
      .Times(1)
      .RetiresOnSaturation();
  transfer_buffer_->Free();
  // See it's freed.
  EXPECT_FALSE(transfer_buffer_->HaveBuffer());
  EXPECT_EQ(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());
  // See that it gets reallocated.
  EXPECT_EQ(transfer_buffer_id_, transfer_buffer_->GetShmId());
  EXPECT_TRUE(transfer_buffer_->HaveBuffer());
  EXPECT_NE(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());

  // Free buffer.
  EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
      .Times(1)
      .RetiresOnSaturation();
  transfer_buffer_->Free();
  // See it's freed.
  EXPECT_FALSE(transfer_buffer_->HaveBuffer());
  EXPECT_EQ(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());

  // See that it gets reallocated.
  EXPECT_TRUE(transfer_buffer_->AcquireResultBuffer() != nullptr);
  transfer_buffer_->ReleaseResultBuffer();
  EXPECT_TRUE(transfer_buffer_->HaveBuffer());
  EXPECT_NE(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());

  // Free buffer.
  EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
      .Times(1)
      .RetiresOnSaturation();
  transfer_buffer_->Free();
  // See it's freed.
  EXPECT_FALSE(transfer_buffer_->HaveBuffer());
  EXPECT_EQ(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());

  // See that it gets reallocated.
  uint32_t size = 0;
  void* data = transfer_buffer_->AllocUpTo(1, &size);
  EXPECT_TRUE(data != nullptr);
  EXPECT_TRUE(transfer_buffer_->HaveBuffer());
  EXPECT_NE(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());
  int32_t token = helper_->InsertToken();
  int32_t put_offset = helper_->GetPutOffsetForTest();
  transfer_buffer_->FreePendingToken(data, token);

  // Free buffer. Should cause an ordering barrier.
  EXPECT_CALL(*command_buffer(), OrderingBarrier(_)).Times(AtMost(1));
  EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
      .Times(1)
      .RetiresOnSaturation();
  transfer_buffer_->Free();
  // See it's freed.
  EXPECT_FALSE(transfer_buffer_->HaveBuffer());
  EXPECT_EQ(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());
  // Free should not have caused a finish.
  EXPECT_LT(command_buffer_->GetState().get_offset, put_offset);

  // See that it gets reallocated.
  transfer_buffer_->GetShmId();
  EXPECT_TRUE(transfer_buffer_->HaveBuffer());
  EXPECT_NE(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());

  EXPECT_EQ(
      kTransferBufferSize - kStartingOffset,
      transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());

  // Test freeing twice.
  EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
      .Times(1)
      .RetiresOnSaturation();
  transfer_buffer_->Free();
  transfer_buffer_->Free();
}

TEST_F(TransferBufferTest, TooLargeAllocation) {
  Initialize();
  // Check that we can't allocate large than max size.
  void* ptr = transfer_buffer_->Alloc(kTransferBufferSize + 1);
  EXPECT_TRUE(ptr == nullptr);
  // Check we if we try to allocate larger than max we get max.
  uint32_t size_allocated = 0;
  ptr = transfer_buffer_->AllocUpTo(
      kTransferBufferSize + 1, &size_allocated);
  ASSERT_TRUE(ptr != nullptr);
  EXPECT_EQ(kTransferBufferSize - kStartingOffset, size_allocated);
  transfer_buffer_->FreePendingToken(ptr, 1);
}

TEST_F(TransferBufferTest, MemoryAlignmentAfterZeroAllocation) {
  Initialize();
  void* ptr = transfer_buffer_->Alloc(0);
  EXPECT_TRUE(base::IsAligned(ptr, kAlignment));
  transfer_buffer_->FreePendingToken(ptr, helper_->InsertToken());
  // Check that the pointer is aligned on the following allocation.
  ptr = transfer_buffer_->Alloc(4);
  EXPECT_TRUE(base::IsAligned(ptr, kAlignment));
  transfer_buffer_->FreePendingToken(ptr, helper_->InsertToken());
}

class MockClientCommandBufferCanFail : public MockClientCommandBufferMockFlush {
 public:
  MockClientCommandBufferCanFail() = default;
  ~MockClientCommandBufferCanFail() override = default;

  MOCK_METHOD3(CreateTransferBuffer,
               scoped_refptr<Buffer>(uint32_t size,
                                     int32_t* id,
                                     TransferBufferAllocationOption option));

  scoped_refptr<gpu::Buffer> RealCreateTransferBuffer(
      uint32_t size,
      int32_t* id,
      TransferBufferAllocationOption option) {
    return MockClientCommandBufferMockFlush::CreateTransferBuffer(size, id,
                                                                  option);
  }
};

class TransferBufferExpandContractTest : public testing::Test {
 protected:
  static const int32_t kNumCommandEntries = 400;
  static const int32_t kCommandBufferSizeBytes =
      kNumCommandEntries * sizeof(CommandBufferEntry);
  static const uint32_t kStartingOffset = 64;
  static const uint32_t kAlignment = 4;
  static const uint32_t kStartTransferBufferSize = 256;
  static const uint32_t kMaxTransferBufferSize = 1024;
  static const uint32_t kMinTransferBufferSize = 128;

  TransferBufferExpandContractTest()
      : transfer_buffer_id_(0) {
  }

  void SetUp() override;
  void TearDown() override;

  MockClientCommandBufferCanFail* command_buffer() const {
    return command_buffer_.get();
  }

  std::unique_ptr<MockClientCommandBufferCanFail> command_buffer_;
  std::unique_ptr<CommandBufferHelper> helper_;
  std::unique_ptr<TransferBuffer> transfer_buffer_;
  int32_t transfer_buffer_id_;
};

void TransferBufferExpandContractTest::SetUp() {
  command_buffer_ =
      std::make_unique<StrictMock<MockClientCommandBufferCanFail>>();
  command_buffer_->SetTokenForSetGetBuffer(0);

  EXPECT_CALL(*command_buffer(),
              CreateTransferBuffer(kCommandBufferSizeBytes, _, _))
      .WillOnce(
          Invoke(command_buffer(),
                 &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
      .RetiresOnSaturation();

  helper_ = std::make_unique<CommandBufferHelper>(command_buffer());
  ASSERT_EQ(helper_->Initialize(kCommandBufferSizeBytes),
            gpu::ContextResult::kSuccess);

  transfer_buffer_id_ = command_buffer()->GetNextFreeTransferBufferId();

  EXPECT_CALL(*command_buffer(),
              CreateTransferBuffer(kStartTransferBufferSize, _, _))
      .WillOnce(
          Invoke(command_buffer(),
                 &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
      .RetiresOnSaturation();

  transfer_buffer_ = std::make_unique<TransferBuffer>(helper_.get());
  ASSERT_TRUE(transfer_buffer_->Initialize(
      kStartTransferBufferSize, kStartingOffset, kMinTransferBufferSize,
      kMaxTransferBufferSize, kAlignment));
}

void TransferBufferExpandContractTest::TearDown() {
  if (transfer_buffer_->HaveBuffer()) {
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
        .Times(1)
        .RetiresOnSaturation();
  }
  // For command buffer.
  EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*command_buffer(), Flush(_)).Times(1).RetiresOnSaturation();
  transfer_buffer_.reset();
}

const int32_t TransferBufferExpandContractTest::kNumCommandEntries;
const int32_t TransferBufferExpandContractTest::kCommandBufferSizeBytes;
const uint32_t TransferBufferExpandContractTest::kStartingOffset;
const uint32_t TransferBufferExpandContractTest::kAlignment;
const uint32_t TransferBufferExpandContractTest::kStartTransferBufferSize;
const uint32_t TransferBufferExpandContractTest::kMaxTransferBufferSize;
const uint32_t TransferBufferExpandContractTest::kMinTransferBufferSize;

TEST_F(TransferBufferExpandContractTest, ExpandWithSmallAllocations) {
  int32_t token = helper_->InsertToken();
  EXPECT_FALSE(helper_->HasTokenPassed(token));

  auto ExpectCreateTransferBuffer = [&](int size) {
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*command_buffer(), CreateTransferBuffer(size, _, _))
        .WillOnce(
            Invoke(command_buffer(),
                   &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
        .RetiresOnSaturation();
  };

  // Check it starts at starting size.
  EXPECT_EQ(
      kStartTransferBufferSize - kStartingOffset,
      transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());

  // Fill the free space.
  uint32_t size_allocated = 0;
  void* ptr = transfer_buffer_->AllocUpTo(transfer_buffer_->GetFreeSize(),
                                          &size_allocated);
  transfer_buffer_->FreePendingToken(ptr, token);

  // Allocate one more byte to force expansion.
  ExpectCreateTransferBuffer(kStartTransferBufferSize * 2);
  ptr = transfer_buffer_->AllocUpTo(1, &size_allocated);
  ASSERT_TRUE(ptr != nullptr);
  EXPECT_EQ(1u, size_allocated);
  transfer_buffer_->FreePendingToken(ptr, token);

  // Fill free space and expand again.
  ptr = transfer_buffer_->AllocUpTo(transfer_buffer_->GetFreeSize(),
                                    &size_allocated);
  transfer_buffer_->FreePendingToken(ptr, token);
  ExpectCreateTransferBuffer(kStartTransferBufferSize * 4);
  ptr = transfer_buffer_->AllocUpTo(1, &size_allocated);
  ASSERT_TRUE(ptr != nullptr);
  EXPECT_EQ(1u, size_allocated);
  transfer_buffer_->FreePendingToken(ptr, token);

  // Try to expand again, no expansion should occur because we are at max.
  ptr = transfer_buffer_->AllocUpTo(transfer_buffer_->GetFreeSize(),
                                    &size_allocated);
  transfer_buffer_->FreePendingToken(ptr, token);
  EXPECT_CALL(*command_buffer(), Flush(_)).Times(1).RetiresOnSaturation();
  ptr = transfer_buffer_->AllocUpTo(1, &size_allocated);
  ASSERT_TRUE(ptr != nullptr);
  EXPECT_EQ(1u, size_allocated);
  transfer_buffer_->FreePendingToken(ptr, token);
  EXPECT_EQ(kMaxTransferBufferSize - kStartingOffset,
            transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
}

// Verify that expansion does not happen when there are blocks in use.
TEST_F(TransferBufferExpandContractTest, NoExpandWithInUseAllocation) {
  EXPECT_CALL(*command_buffer(), Flush(_)).Times(1).RetiresOnSaturation();

  int32_t token = helper_->InsertToken();
  EXPECT_FALSE(helper_->HasTokenPassed(token));

  // Check it starts at starting size.
  EXPECT_EQ(kStartTransferBufferSize - kStartingOffset,
            transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());

  // Fill the free space in two blocks.
  uint32_t block_size_1 = transfer_buffer_->GetFreeSize() / 2;
  uint32_t block_size_2 = transfer_buffer_->GetFreeSize() - block_size_1;
  uint32_t size_allocated = 0;
  void* block1 = transfer_buffer_->AllocUpTo(block_size_1, &size_allocated);
  EXPECT_EQ(block_size_1, size_allocated);
  void* block2 = transfer_buffer_->AllocUpTo(block_size_2, &size_allocated);
  EXPECT_EQ(block_size_2, size_allocated);
  transfer_buffer_->FreePendingToken(block1, token);

  // Expansion tries to happens when GetFreeSize() is not enough for the
  // allocation.
  EXPECT_EQ(0u, transfer_buffer_->GetFreeSize());

  // Allocate one more byte to try to force expansion, however there are
  // blocks in use, so this should not expand.
  void* block3 = transfer_buffer_->AllocUpTo(1, &size_allocated);
  ASSERT_TRUE(block3 != nullptr);
  EXPECT_EQ(1u, size_allocated);
  transfer_buffer_->FreePendingToken(block3, token);
  transfer_buffer_->FreePendingToken(block2, token);

  // No reallocs should have occurred.
  EXPECT_EQ(kStartTransferBufferSize - kStartingOffset,
            transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
}

TEST_F(TransferBufferExpandContractTest, ExpandWithLargeAllocations) {
  int32_t token = helper_->InsertToken();
  EXPECT_FALSE(helper_->HasTokenPassed(token));

  auto ExpectCreateTransferBuffer = [&](int size) {
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*command_buffer(), CreateTransferBuffer(size, _, _))
        .WillOnce(
            Invoke(command_buffer(),
                   &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
        .RetiresOnSaturation();
  };

  // Check it starts at starting size.
  EXPECT_EQ(kStartTransferBufferSize - kStartingOffset,
            transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());

  // Allocate one byte more than the free space to force expansion.
  uint32_t size_allocated = 0;
  ExpectCreateTransferBuffer(kStartTransferBufferSize * 2);
  void* ptr = transfer_buffer_->AllocUpTo(transfer_buffer_->GetFreeSize() + 1,
                                          &size_allocated);
  transfer_buffer_->FreePendingToken(ptr, token);

  // Expand again.
  ExpectCreateTransferBuffer(kStartTransferBufferSize * 4);
  uint32_t size_requested = transfer_buffer_->GetFreeSize() + 1;
  ptr = transfer_buffer_->AllocUpTo(size_requested, &size_allocated);
  ASSERT_TRUE(ptr != nullptr);
  EXPECT_EQ(size_requested, size_allocated);
  transfer_buffer_->FreePendingToken(ptr, token);

  // Try to expand again, no expansion should occur because we are at max.
  EXPECT_CALL(*command_buffer(), Flush(_)).Times(1).RetiresOnSaturation();
  size_requested =
      transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc() + 1;
  ptr = transfer_buffer_->AllocUpTo(size_requested, &size_allocated);
  EXPECT_LT(size_allocated, size_requested);
  transfer_buffer_->FreePendingToken(ptr, token);
  EXPECT_EQ(kMaxTransferBufferSize - kStartingOffset,
            transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
}

TEST_F(TransferBufferExpandContractTest, ShrinkRingBuffer) {
  int32_t token = helper_->InsertToken();
  // For this test we want all allocations to be freed immediately.
  command_buffer_->SetToken(token);
  EXPECT_TRUE(helper_->HasTokenPassed(token));

  auto ExpectCreateTransferBuffer = [&](int size) {
    EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*command_buffer(), CreateTransferBuffer(size, _, _))
        .WillOnce(
            Invoke(command_buffer(),
                   &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
        .RetiresOnSaturation();
  };

  // Expand the ring buffer to the maximum size.
  ExpectCreateTransferBuffer(kMaxTransferBufferSize);
  void* ptr = transfer_buffer_->Alloc(kMaxTransferBufferSize - kStartingOffset);
  EXPECT_TRUE(ptr != nullptr);
  transfer_buffer_->FreePendingToken(ptr, token);

  // We shouldn't shrink before we reach the allocation threshold.
  for (uint32_t allocated = kMaxTransferBufferSize - kStartingOffset;
       allocated < (kStartTransferBufferSize + kStartingOffset) *
                       (TransferBuffer::kShrinkThreshold);) {
    ptr = transfer_buffer_->Alloc(kStartTransferBufferSize);
    EXPECT_TRUE(ptr != nullptr);
    transfer_buffer_->FreePendingToken(ptr, token);
    allocated += kStartTransferBufferSize;
  }
  // The next allocation should trip the threshold and shrink.
  ExpectCreateTransferBuffer(kStartTransferBufferSize * 2);
  ptr = transfer_buffer_->Alloc(1);
  EXPECT_TRUE(ptr != nullptr);
  transfer_buffer_->FreePendingToken(ptr, token);
}

TEST_F(TransferBufferExpandContractTest, Contract) {
  // Check it starts at starting size.
  EXPECT_EQ(
      kStartTransferBufferSize - kStartingOffset,
      transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());

  // Free buffer.
  EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
      .Times(1)
      .RetiresOnSaturation();
  transfer_buffer_->Free();
  // See it's freed.
  EXPECT_FALSE(transfer_buffer_->HaveBuffer());

  // Try to allocate again, fail first request
  EXPECT_CALL(*command_buffer(),
              CreateTransferBuffer(kStartTransferBufferSize, _, _))
      .WillOnce(
          DoAll(SetArgPointee<1>(-1), Return(scoped_refptr<gpu::Buffer>())))
      .RetiresOnSaturation();
  EXPECT_CALL(*command_buffer(),
              CreateTransferBuffer(kMinTransferBufferSize, _, _))
      .WillOnce(
          Invoke(command_buffer(),
                 &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
      .RetiresOnSaturation();

  const uint32_t kSize1 = 256 - kStartingOffset;
  const uint32_t kSize2 = 128 - kStartingOffset;
  uint32_t size_allocated = 0;
  void* ptr = transfer_buffer_->AllocUpTo(kSize1, &size_allocated);
  ASSERT_TRUE(ptr != nullptr);
  EXPECT_EQ(kSize2, size_allocated);
  EXPECT_EQ(kSize2, transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
  transfer_buffer_->FreePendingToken(ptr, 1);

  // Free buffer.
  EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
      .Times(1)
      .RetiresOnSaturation();
  transfer_buffer_->Free();
  // See it's freed.
  EXPECT_FALSE(transfer_buffer_->HaveBuffer());

  // Try to allocate again,
  EXPECT_CALL(*command_buffer(),
              CreateTransferBuffer(kMinTransferBufferSize, _, _))
      .WillOnce(
          Invoke(command_buffer(),
                 &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
      .RetiresOnSaturation();

  ptr = transfer_buffer_->AllocUpTo(kSize1, &size_allocated);
  ASSERT_TRUE(ptr != nullptr);
  EXPECT_EQ(kSize2, size_allocated);
  EXPECT_EQ(kSize2, transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
  transfer_buffer_->FreePendingToken(ptr, 1);
}

TEST_F(TransferBufferExpandContractTest, OutOfMemory) {
  // Free buffer.
  EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
      .Times(1)
      .RetiresOnSaturation();
  transfer_buffer_->Free();
  // See it's freed.
  EXPECT_FALSE(transfer_buffer_->HaveBuffer());

  // Try to allocate again, fail both requests.
  EXPECT_CALL(*command_buffer(), CreateTransferBuffer(_, _, _))
      .WillOnce(
          DoAll(SetArgPointee<1>(-1), Return(scoped_refptr<gpu::Buffer>())))
      .WillOnce(
          DoAll(SetArgPointee<1>(-1), Return(scoped_refptr<gpu::Buffer>())))
      .WillOnce(
          DoAll(SetArgPointee<1>(-1), Return(scoped_refptr<gpu::Buffer>())))
      .RetiresOnSaturation();

  const uint32_t kSize1 = 512 - kStartingOffset;
  uint32_t size_allocated = 0;
  void* ptr = transfer_buffer_->AllocUpTo(kSize1, &size_allocated);
  ASSERT_TRUE(ptr == nullptr);
  EXPECT_FALSE(transfer_buffer_->HaveBuffer());
}

TEST_F(TransferBufferExpandContractTest, ReallocsToDefault) {
  // Free buffer.
  EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
      .Times(1)
      .RetiresOnSaturation();
  transfer_buffer_->Free();
  // See it's freed.
  EXPECT_FALSE(transfer_buffer_->HaveBuffer());

  // See that it gets reallocated.
  EXPECT_CALL(*command_buffer(),
              CreateTransferBuffer(kStartTransferBufferSize, _, _))
      .WillOnce(
          Invoke(command_buffer(),
                 &MockClientCommandBufferCanFail::RealCreateTransferBuffer))
      .RetiresOnSaturation();
  EXPECT_EQ(transfer_buffer_id_, transfer_buffer_->GetShmId());
  EXPECT_TRUE(transfer_buffer_->HaveBuffer());

  // Check it's the default size.
  EXPECT_EQ(
      kStartTransferBufferSize - kStartingOffset,
      transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
}

TEST_F(TransferBufferExpandContractTest, Shrink) {
  uint32_t alloc_size = transfer_buffer_->GetFreeSize();
  EXPECT_EQ(kStartTransferBufferSize - kStartingOffset, alloc_size);
  uint32_t size_allocated = 0;
  void* ptr = transfer_buffer_->AllocUpTo(alloc_size, &size_allocated);

  ASSERT_NE(ptr, nullptr);
  EXPECT_EQ(alloc_size, size_allocated);
  EXPECT_GT(alloc_size, 0u);
  EXPECT_EQ(0u, transfer_buffer_->GetFreeSize());

  // Shrink once.
  const uint32_t shrink_size1 = 64;
  EXPECT_LT(shrink_size1, alloc_size);
  transfer_buffer_->ShrinkLastBlock(shrink_size1 - kAlignment + 1);
  EXPECT_EQ(alloc_size - shrink_size1, transfer_buffer_->GetFreeSize());

  // Shrink again.
  const uint32_t shrink_size2 = 32;
  EXPECT_LT(shrink_size2, shrink_size1);
  transfer_buffer_->ShrinkLastBlock(shrink_size2);
  EXPECT_EQ(alloc_size - shrink_size2, transfer_buffer_->GetFreeSize());

  // Shrink to zero (minimum size is kAlignment).
  transfer_buffer_->ShrinkLastBlock(0);
  EXPECT_EQ(alloc_size - kAlignment, transfer_buffer_->GetFreeSize());

  transfer_buffer_->FreePendingToken(ptr, 1);
}

TEST_F(TransferBufferTest, MultipleAllocsAndFrees) {
  // An arbitrary size, but is aligned so no padding needed.
  constexpr uint32_t kArbitrarySize = 16;

  Initialize();
  uint32_t original_free_size = transfer_buffer_->GetFreeSize();
  EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
  EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(), original_free_size);

  void* ptr1 = transfer_buffer_->Alloc(kArbitrarySize);
  EXPECT_NE(ptr1, nullptr);
  EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
  EXPECT_EQ(transfer_buffer_->GetFreeSize(),
            original_free_size - kArbitrarySize);
  EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(),
            original_free_size - kArbitrarySize);

  void* ptr2 = transfer_buffer_->Alloc(kArbitrarySize);
  EXPECT_NE(ptr2, nullptr);
  EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
  EXPECT_EQ(transfer_buffer_->GetFreeSize(),
            original_free_size - kArbitrarySize * 2);
  EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(),
            original_free_size - kArbitrarySize * 2);

  void* ptr3 = transfer_buffer_->Alloc(kArbitrarySize);
  EXPECT_NE(ptr3, nullptr);
  EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
  EXPECT_EQ(transfer_buffer_->GetFreeSize(),
            original_free_size - kArbitrarySize * 3);
  EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(),
            original_free_size - kArbitrarySize * 3);

  // Generate tokens in order, but submit out of order.
  auto token1 = helper_->InsertToken();
  auto token2 = helper_->InsertToken();
  auto token3 = helper_->InsertToken();
  auto token4 = helper_->InsertToken();

  // Freeing the final block here, is not perceivable because it's a hole.
  transfer_buffer_->FreePendingToken(ptr3, token3);
  EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
  EXPECT_EQ(transfer_buffer_->GetFreeSize(),
            original_free_size - kArbitrarySize * 3);
  EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(),
            original_free_size - kArbitrarySize * 3);

  // Freeing the first block here leaves the second plus a hole after, so
  // perceived two blocks not free yet.  The free size (no waiting) has not
  // changed because the free_offset_ has not moved, but the fragmented free
  // size gets bigger because in_use_offset_ has moved past the first block.
  transfer_buffer_->FreePendingToken(ptr1, token1);
  EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
  EXPECT_EQ(transfer_buffer_->GetFreeSize(),
            original_free_size - kArbitrarySize * 3);
  EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(),
            original_free_size - kArbitrarySize * 2);

  // Allocate a 4th block.  This leaves the state as: Freed Used Freed Used
  void* ptr4 = transfer_buffer_->Alloc(kArbitrarySize);
  EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
  EXPECT_EQ(transfer_buffer_->GetFreeSize(),
            original_free_size - kArbitrarySize * 4);
  EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(),
            original_free_size - kArbitrarySize * 3);

  // Freeing the second and fourth block makes everything free, so back to
  // original size.
  transfer_buffer_->FreePendingToken(ptr4, token4);
  transfer_buffer_->FreePendingToken(ptr2, token2);
  EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
  EXPECT_EQ(transfer_buffer_->GetFreeSize(), original_free_size);
  EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(), original_free_size);
}

#if defined(GTEST_HAS_DEATH_TEST) && DCHECK_IS_ON()

TEST_F(TransferBufferTest, ResizeDuringScopedResultPtr) {
  Initialize();
  ScopedResultPtr<int> ptr(transfer_buffer_.get());
  // If an attempt is made to resize the transfer buffer while a result
  // pointer exists, we should hit a CHECK. Allocate just enough to force a
  // resize.
  uint32_t size_allocated;
  ASSERT_DEATH(transfer_buffer_->AllocUpTo(transfer_buffer_->GetFreeSize() + 1,
                                           &size_allocated),
               "outstanding_result_pointer_");
}

TEST_F(TransferBufferTest, AllocDuringScopedResultPtr) {
  Initialize();
  ScopedResultPtr<int> ptr(transfer_buffer_.get());
  // If an attempt is made to allocate any amount in the transfer buffer while a
  // result pointer exists, we should hit a DCHECK.
  uint32_t size_allocated;
  ASSERT_DEATH(transfer_buffer_->AllocUpTo(transfer_buffer_->GetFreeSize() + 1,
                                           &size_allocated),
               "outstanding_result_pointer_");
}

TEST_F(TransferBufferTest, TwoScopedResultPtrs) {
  Initialize();
  // Attempting to create two ScopedResultPtrs at the same time should DCHECK.
  ScopedResultPtr<int> ptr(transfer_buffer_.get());
  ASSERT_DEATH(ScopedResultPtr<int>(transfer_buffer_.get()),
               "outstanding_result_pointer_");
}

#endif  // defined(GTEST_HAS_DEATH_TEST) && DCHECK_IS_ON()

}  // namespace gpu