// Copyright (c) 2016 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 "quiche/quic/core/quic_arena_scoped_ptr.h"

#include "quiche/quic/core/quic_one_block_arena.h"
#include "quiche/quic/platform/api/quic_test.h"

namespace quic::test {
namespace {

enum class TestParam { kFromHeap, kFromArena };

struct TestObject {
  explicit TestObject(uintptr_t value) : value(value) { buffer.resize(1024); }
  uintptr_t value;

  // Ensure that we have a non-trivial destructor that will leak memory if it's
  // not called.
  std::vector<char> buffer;
};

// Used by ::testing::PrintToStringParamName().
std::string PrintToString(const TestParam& p) {
  switch (p) {
    case TestParam::kFromHeap:
      return "heap";
    case TestParam::kFromArena:
      return "arena";
  }
  QUICHE_DCHECK(false);
  return "?";
}

class QuicArenaScopedPtrParamTest : public QuicTestWithParam<TestParam> {
 protected:
  QuicArenaScopedPtr<TestObject> CreateObject(uintptr_t value) {
    QuicArenaScopedPtr<TestObject> ptr;
    switch (GetParam()) {
      case TestParam::kFromHeap:
        ptr = QuicArenaScopedPtr<TestObject>(new TestObject(value));
        QUICHE_CHECK(!ptr.is_from_arena());
        break;
      case TestParam::kFromArena:
        ptr = arena_.New<TestObject>(value);
        QUICHE_CHECK(ptr.is_from_arena());
        break;
    }
    return ptr;
  }

 private:
  QuicOneBlockArena<1024> arena_;
};

INSTANTIATE_TEST_SUITE_P(QuicArenaScopedPtrParamTest,
                         QuicArenaScopedPtrParamTest,
                         testing::Values(TestParam::kFromHeap,
                                         TestParam::kFromArena),
                         ::testing::PrintToStringParamName());

TEST_P(QuicArenaScopedPtrParamTest, NullObjects) {
  QuicArenaScopedPtr<TestObject> def;
  QuicArenaScopedPtr<TestObject> null(nullptr);
  EXPECT_EQ(def, null);
  EXPECT_EQ(def, nullptr);
  EXPECT_EQ(null, nullptr);
}

TEST_P(QuicArenaScopedPtrParamTest, FromArena) {
  QuicOneBlockArena<1024> arena_;
  EXPECT_TRUE(arena_.New<TestObject>(0).is_from_arena());
  EXPECT_FALSE(
      QuicArenaScopedPtr<TestObject>(new TestObject(0)).is_from_arena());
}

TEST_P(QuicArenaScopedPtrParamTest, Assign) {
  QuicArenaScopedPtr<TestObject> ptr = CreateObject(12345);
  ptr = CreateObject(54321);
  EXPECT_EQ(54321u, ptr->value);
}

TEST_P(QuicArenaScopedPtrParamTest, MoveConstruct) {
  QuicArenaScopedPtr<TestObject> ptr1 = CreateObject(12345);
  QuicArenaScopedPtr<TestObject> ptr2(std::move(ptr1));
  EXPECT_EQ(nullptr, ptr1);
  EXPECT_EQ(12345u, ptr2->value);
}

TEST_P(QuicArenaScopedPtrParamTest, Accessors) {
  QuicArenaScopedPtr<TestObject> ptr = CreateObject(12345);
  EXPECT_EQ(12345u, (*ptr).value);
  EXPECT_EQ(12345u, ptr->value);
  // We explicitly want to test that get() returns a valid pointer to the data,
  // but the call looks redundant.
  EXPECT_EQ(12345u, ptr.get()->value);  // NOLINT
}

TEST_P(QuicArenaScopedPtrParamTest, Reset) {
  QuicArenaScopedPtr<TestObject> ptr = CreateObject(12345);
  ptr.reset(new TestObject(54321));
  EXPECT_EQ(54321u, ptr->value);
}

TEST_P(QuicArenaScopedPtrParamTest, Swap) {
  QuicArenaScopedPtr<TestObject> ptr1 = CreateObject(12345);
  QuicArenaScopedPtr<TestObject> ptr2 = CreateObject(54321);
  ptr1.swap(ptr2);
  EXPECT_EQ(12345u, ptr2->value);
  EXPECT_EQ(54321u, ptr1->value);
}

}  // namespace
}  // namespace quic::test
