//===----- SymbolStringPoolTest.cpp - Unit tests for SymbolStringPool -----===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "llvm/ExecutionEngine/Orc/SymbolStringPool.h"
#include "llvm/ExecutionEngine/Orc/DebugUtils.h"
#include "gtest/gtest.h"

using namespace llvm;
using namespace llvm::orc;

namespace llvm::orc {

class SymbolStringPoolTest : public testing::Test {
public:
  size_t getRefCount(const SymbolStringPtrBase &S) const {
    return SP.getRefCount(S);
  }

protected:
  SymbolStringPool SP;
};
} // namespace llvm::orc

namespace {

TEST_F(SymbolStringPoolTest, UniquingAndComparisons) {
  auto P1 = SP.intern("hello");

  std::string S("hel");
  S += "lo";
  auto P2 = SP.intern(S);

  auto P3 = SP.intern("goodbye");

  EXPECT_EQ(P1, P2) << "Failed to unique entries";
  EXPECT_NE(P1, P3) << "Unequal pooled symbol strings comparing equal";

  // We want to test that less-than comparison of SymbolStringPtrs compiles,
  // however we can't test the actual result as this is a pointer comparison and
  // SymbolStringPtr doesn't expose the underlying address of the string.
  (void)(P1 < P3);
}

TEST_F(SymbolStringPoolTest, Dereference) {
  auto Foo = SP.intern("foo");
  EXPECT_EQ(*Foo, "foo") << "Equality on dereferenced string failed";
}

TEST_F(SymbolStringPoolTest, ClearDeadEntries) {
  {
    auto P1 = SP.intern("s1");
    SP.clearDeadEntries();
    EXPECT_FALSE(SP.empty()) << "\"s1\" entry in pool should still be retained";
  }
  SP.clearDeadEntries();
  EXPECT_TRUE(SP.empty()) << "pool should be empty";
}

TEST_F(SymbolStringPoolTest, DebugDump) {
  auto A1 = SP.intern("a");
  auto A2 = A1;
  auto B = SP.intern("b");

  std::string DumpString;
  raw_string_ostream(DumpString) << SP;

  EXPECT_EQ(DumpString, "a: 2\nb: 1\n");
}

TEST_F(SymbolStringPoolTest, NonOwningPointerBasics) {
  auto A = SP.intern("a");
  auto B = SP.intern("b");

  NonOwningSymbolStringPtr ANP1(A);    // Constuct from SymbolStringPtr.
  NonOwningSymbolStringPtr ANP2(ANP1); // Copy-construct.
  NonOwningSymbolStringPtr BNP(B);

  // Equality comparisons.
  EXPECT_EQ(A, ANP1);
  EXPECT_EQ(ANP1, ANP2);
  EXPECT_NE(ANP1, BNP);

  EXPECT_EQ(*ANP1, "a"); // Dereference.

  // Assignment.
  ANP2 = ANP1;
  ANP2 = A;

  SymbolStringPtr S(ANP1); // Construct SymbolStringPtr from non-owning.
  EXPECT_EQ(S, A);

  DenseMap<SymbolStringPtr, int> M;
  M[A] = 42;
  EXPECT_EQ(M.find_as(ANP1)->second, 42);
  EXPECT_EQ(M.find_as(BNP), M.end());
}

TEST_F(SymbolStringPoolTest, NonOwningPointerRefCounts) {
  // Check that creating and destroying non-owning pointers doesn't affect
  // ref-counts.
  auto A = SP.intern("a");
  EXPECT_EQ(getRefCount(A), 1U);

  NonOwningSymbolStringPtr ANP(A);
  EXPECT_EQ(getRefCount(ANP), 1U)
      << "Construction of NonOwningSymbolStringPtr from SymbolStringPtr "
         "changed ref-count";

  {
    NonOwningSymbolStringPtr ANP2(ANP);
    EXPECT_EQ(getRefCount(ANP2), 1U)
        << "Copy-construction of NonOwningSymbolStringPtr changed ref-count";
  }

  EXPECT_EQ(getRefCount(ANP), 1U)
      << "Destruction of NonOwningSymbolStringPtr changed ref-count";

  {
    NonOwningSymbolStringPtr ANP2;
    ANP2 = ANP;
    EXPECT_EQ(getRefCount(ANP2), 1U)
        << "Copy-assignment of NonOwningSymbolStringPtr changed ref-count";
  }

  {
    NonOwningSymbolStringPtr ANP2(ANP);
    NonOwningSymbolStringPtr ANP3(std::move(ANP2));
    EXPECT_EQ(getRefCount(ANP3), 1U)
        << "Move-construction of NonOwningSymbolStringPtr changed ref-count";
  }

  {
    NonOwningSymbolStringPtr ANP2(ANP);
    NonOwningSymbolStringPtr ANP3;
    ANP3 = std::move(ANP2);
    EXPECT_EQ(getRefCount(ANP3), 1U)
        << "Copy-assignment of NonOwningSymbolStringPtr changed ref-count";
  }
}

TEST_F(SymbolStringPoolTest, SymbolStringPoolEntryUnsafe) {

  auto A = SP.intern("a");
  EXPECT_EQ(getRefCount(A), 1U);

  {
    // Try creating an unsafe pool entry ref from the given SymbolStringPtr.
    // This should not affect the ref-count.
    auto AUnsafe = SymbolStringPoolEntryUnsafe::from(A);
    EXPECT_EQ(getRefCount(A), 1U);

    // Create a new SymbolStringPtr from the unsafe ref. This should increment
    // the ref-count.
    auto ACopy = AUnsafe.copyToSymbolStringPtr();
    EXPECT_EQ(getRefCount(A), 2U);
  }

  {
    // Create a copy of the original string. Move it into an unsafe ref, and
    // then move it back. None of these operations should affect the ref-count.
    auto ACopy = A;
    EXPECT_EQ(getRefCount(A), 2U);
    auto AUnsafe = SymbolStringPoolEntryUnsafe::take(std::move(ACopy));
    EXPECT_EQ(getRefCount(A), 2U);
    ACopy = AUnsafe.moveToSymbolStringPtr();
    EXPECT_EQ(getRefCount(A), 2U);
  }

  // Test manual retain / release.
  auto AUnsafe = SymbolStringPoolEntryUnsafe::from(A);
  EXPECT_EQ(getRefCount(A), 1U);
  AUnsafe.retain();
  EXPECT_EQ(getRefCount(A), 2U);
  AUnsafe.release();
  EXPECT_EQ(getRefCount(A), 1U);
}

} // namespace
