aboutsummaryrefslogtreecommitdiff
path: root/unittests/DebugInfo/PDB
diff options
context:
space:
mode:
Diffstat (limited to 'unittests/DebugInfo/PDB')
-rw-r--r--unittests/DebugInfo/PDB/CMakeLists.txt3
-rw-r--r--unittests/DebugInfo/PDB/HashTableTest.cpp168
-rw-r--r--unittests/DebugInfo/PDB/MappedBlockStreamTest.cpp90
-rw-r--r--unittests/DebugInfo/PDB/PDBApiTest.cpp1
-rw-r--r--unittests/DebugInfo/PDB/StringTableBuilderTest.cpp55
-rw-r--r--unittests/DebugInfo/PDB/TypeServerHandlerTest.cpp175
6 files changed, 449 insertions, 43 deletions
diff --git a/unittests/DebugInfo/PDB/CMakeLists.txt b/unittests/DebugInfo/PDB/CMakeLists.txt
index 406b487e7a18..cbbbd8177483 100644
--- a/unittests/DebugInfo/PDB/CMakeLists.txt
+++ b/unittests/DebugInfo/PDB/CMakeLists.txt
@@ -5,9 +5,12 @@ set(LLVM_LINK_COMPONENTS
)
set(DebugInfoPDBSources
+ HashTableTest.cpp
MappedBlockStreamTest.cpp
+ StringTableBuilderTest.cpp
MSFBuilderTest.cpp
PDBApiTest.cpp
+ TypeServerHandlerTest.cpp
)
add_llvm_unittest(DebugInfoPDBTests
diff --git a/unittests/DebugInfo/PDB/HashTableTest.cpp b/unittests/DebugInfo/PDB/HashTableTest.cpp
new file mode 100644
index 000000000000..94c9ee86c4a6
--- /dev/null
+++ b/unittests/DebugInfo/PDB/HashTableTest.cpp
@@ -0,0 +1,168 @@
+//===- llvm/unittest/DebugInfo/PDB/HashTableTest.cpp ----------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ErrorChecking.h"
+#include "gtest/gtest.h"
+
+#include "llvm/DebugInfo/PDB/Native/HashTable.h"
+#include "llvm/Support/BinaryByteStream.h"
+#include "llvm/Support/BinaryStreamReader.h"
+#include "llvm/Support/BinaryStreamWriter.h"
+
+#include <vector>
+
+using namespace llvm;
+using namespace llvm::pdb;
+using namespace llvm::support;
+
+namespace {
+class HashTableInternals : public HashTable {
+public:
+ using HashTable::Buckets;
+ using HashTable::Present;
+ using HashTable::Deleted;
+};
+}
+
+TEST(HashTableTest, TestSimple) {
+ HashTable Table;
+ EXPECT_EQ(0u, Table.size());
+ EXPECT_GT(Table.capacity(), 0u);
+
+ Table.set(3, 7);
+ EXPECT_EQ(1u, Table.size());
+ ASSERT_NE(Table.end(), Table.find(3));
+ EXPECT_EQ(7u, Table.get(3));
+}
+
+TEST(HashTableTest, TestCollision) {
+ HashTable Table;
+ EXPECT_EQ(0u, Table.size());
+ EXPECT_GT(Table.capacity(), 0u);
+
+ // We use knowledge of the hash table's implementation details to make sure
+ // to add another value that is the equivalent to the first value modulo the
+ // hash table's capacity.
+ uint32_t N1 = Table.capacity() + 1;
+ uint32_t N2 = 2 * N1;
+
+ Table.set(N1, 7);
+ Table.set(N2, 12);
+ EXPECT_EQ(2u, Table.size());
+ ASSERT_NE(Table.end(), Table.find(N1));
+ ASSERT_NE(Table.end(), Table.find(N2));
+
+ EXPECT_EQ(7u, Table.get(N1));
+ EXPECT_EQ(12u, Table.get(N2));
+}
+
+TEST(HashTableTest, TestRemove) {
+ HashTable Table;
+ EXPECT_EQ(0u, Table.size());
+ EXPECT_GT(Table.capacity(), 0u);
+
+ Table.set(1, 2);
+ Table.set(3, 4);
+ EXPECT_EQ(2u, Table.size());
+ ASSERT_NE(Table.end(), Table.find(1));
+ ASSERT_NE(Table.end(), Table.find(3));
+
+ EXPECT_EQ(2u, Table.get(1));
+ EXPECT_EQ(4u, Table.get(3));
+
+ Table.remove(1u);
+ EXPECT_EQ(1u, Table.size());
+ EXPECT_EQ(Table.end(), Table.find(1));
+ ASSERT_NE(Table.end(), Table.find(3));
+ EXPECT_EQ(4u, Table.get(3));
+}
+
+TEST(HashTableTest, TestCollisionAfterMultipleProbes) {
+ HashTable Table;
+ EXPECT_EQ(0u, Table.size());
+ EXPECT_GT(Table.capacity(), 0u);
+
+ // Probing looks for the first available slot. A slot may already be filled
+ // as a result of an item with a *different* hash value already being there.
+ // Test that when this happens, the probe still finds the value.
+ uint32_t N1 = Table.capacity() + 1;
+ uint32_t N2 = N1 + 1;
+ uint32_t N3 = 2 * N1;
+
+ Table.set(N1, 7);
+ Table.set(N2, 11);
+ Table.set(N3, 13);
+ EXPECT_EQ(3u, Table.size());
+ ASSERT_NE(Table.end(), Table.find(N1));
+ ASSERT_NE(Table.end(), Table.find(N2));
+ ASSERT_NE(Table.end(), Table.find(N3));
+
+ EXPECT_EQ(7u, Table.get(N1));
+ EXPECT_EQ(11u, Table.get(N2));
+ EXPECT_EQ(13u, Table.get(N3));
+
+ // Remove the one that had been filled in the middle, then insert another one
+ // with a collision. It should fill the newly emptied slot.
+ Table.remove(N2);
+ uint32_t N4 = N1 * 3;
+ Table.set(N4, 17);
+ EXPECT_EQ(3u, Table.size());
+ ASSERT_NE(Table.end(), Table.find(N1));
+ ASSERT_NE(Table.end(), Table.find(N3));
+ ASSERT_NE(Table.end(), Table.find(N4));
+
+ EXPECT_EQ(7u, Table.get(N1));
+ EXPECT_EQ(13u, Table.get(N3));
+ EXPECT_EQ(17u, Table.get(N4));
+}
+
+TEST(HashTableTest, Grow) {
+ // So that we are independent of the load factor, `capacity` items, which is
+ // guaranteed to trigger a grow. Then verify that the size is the same, the
+ // capacity is larger, and all the original items are still in the table.
+
+ HashTable Table;
+ uint32_t OldCapacity = Table.capacity();
+ for (uint32_t I = 0; I < OldCapacity; ++I) {
+ Table.set(OldCapacity + I * 2 + 1, I * 2 + 3);
+ }
+ EXPECT_EQ(OldCapacity, Table.size());
+ EXPECT_GT(Table.capacity(), OldCapacity);
+ for (uint32_t I = 0; I < OldCapacity; ++I) {
+ ASSERT_NE(Table.end(), Table.find(OldCapacity + I * 2 + 1));
+ EXPECT_EQ(I * 2 + 3, Table.get(OldCapacity + I * 2 + 1));
+ }
+}
+
+TEST(HashTableTest, Serialization) {
+ HashTableInternals Table;
+ uint32_t Cap = Table.capacity();
+ for (uint32_t I = 0; I < Cap; ++I) {
+ Table.set(Cap + I * 2 + 1, I * 2 + 3);
+ }
+
+ std::vector<uint8_t> Buffer(Table.calculateSerializedLength());
+ MutableBinaryByteStream Stream(Buffer, little);
+ BinaryStreamWriter Writer(Stream);
+ EXPECT_NO_ERROR(Table.commit(Writer));
+ // We should have written precisely the number of bytes we calculated earlier.
+ EXPECT_EQ(Buffer.size(), Writer.getOffset());
+
+ HashTableInternals Table2;
+ BinaryStreamReader Reader(Stream);
+ EXPECT_NO_ERROR(Table2.load(Reader));
+ // We should have read precisely the number of bytes we calculated earlier.
+ EXPECT_EQ(Buffer.size(), Reader.getOffset());
+
+ EXPECT_EQ(Table.size(), Table2.size());
+ EXPECT_EQ(Table.capacity(), Table2.capacity());
+ EXPECT_EQ(Table.Buckets, Table2.Buckets);
+ EXPECT_EQ(Table.Present, Table2.Present);
+ EXPECT_EQ(Table.Deleted, Table2.Deleted);
+}
diff --git a/unittests/DebugInfo/PDB/MappedBlockStreamTest.cpp b/unittests/DebugInfo/PDB/MappedBlockStreamTest.cpp
index 07591ca69d30..9f8940b77f28 100644
--- a/unittests/DebugInfo/PDB/MappedBlockStreamTest.cpp
+++ b/unittests/DebugInfo/PDB/MappedBlockStreamTest.cpp
@@ -9,26 +9,28 @@
#include "ErrorChecking.h"
-#include "llvm/DebugInfo/MSF/ByteStream.h"
#include "llvm/DebugInfo/MSF/IMSFFile.h"
-#include "llvm/DebugInfo/MSF/MappedBlockStream.h"
+#include "llvm/DebugInfo/MSF/MSFError.h"
#include "llvm/DebugInfo/MSF/MSFStreamLayout.h"
-#include "llvm/DebugInfo/MSF/StreamReader.h"
-#include "llvm/DebugInfo/MSF/StreamRef.h"
-#include "llvm/DebugInfo/MSF/StreamWriter.h"
+#include "llvm/DebugInfo/MSF/MappedBlockStream.h"
+#include "llvm/Support/BinaryByteStream.h"
+#include "llvm/Support/BinaryStreamReader.h"
+#include "llvm/Support/BinaryStreamRef.h"
+#include "llvm/Support/BinaryStreamWriter.h"
#include "gtest/gtest.h"
#include <unordered_map>
using namespace llvm;
using namespace llvm::msf;
+using namespace llvm::support;
namespace {
static const uint32_t BlocksAry[] = {0, 1, 2, 5, 4, 3, 6, 7, 8, 9};
static uint8_t DataAry[] = {'A', 'B', 'C', 'F', 'E', 'D', 'G', 'H', 'I', 'J'};
-class DiscontiguousStream : public WritableStream {
+class DiscontiguousStream : public WritableBinaryStream {
public:
DiscontiguousStream(ArrayRef<uint32_t> Blocks, MutableArrayRef<uint8_t> Data)
: Blocks(Blocks.begin(), Blocks.end()), Data(Data.begin(), Data.end()) {}
@@ -36,31 +38,33 @@ public:
uint32_t block_size() const { return 1; }
uint32_t block_count() const { return Blocks.size(); }
+ endianness getEndian() const override { return little; }
+
Error readBytes(uint32_t Offset, uint32_t Size,
- ArrayRef<uint8_t> &Buffer) const override {
- if (Offset + Size > Data.size())
- return make_error<MSFError>(msf_error_code::insufficient_buffer);
+ ArrayRef<uint8_t> &Buffer) override {
+ if (auto EC = checkOffset(Offset, Size))
+ return EC;
Buffer = Data.slice(Offset, Size);
return Error::success();
}
Error readLongestContiguousChunk(uint32_t Offset,
- ArrayRef<uint8_t> &Buffer) const override {
- if (Offset >= Data.size())
- return make_error<MSFError>(msf_error_code::insufficient_buffer);
+ ArrayRef<uint8_t> &Buffer) override {
+ if (auto EC = checkOffset(Offset, 1))
+ return EC;
Buffer = Data.drop_front(Offset);
return Error::success();
}
- uint32_t getLength() const override { return Data.size(); }
+ uint32_t getLength() override { return Data.size(); }
- Error writeBytes(uint32_t Offset, ArrayRef<uint8_t> SrcData) const override {
- if (Offset + SrcData.size() > Data.size())
- return make_error<MSFError>(msf_error_code::insufficient_buffer);
+ Error writeBytes(uint32_t Offset, ArrayRef<uint8_t> SrcData) override {
+ if (auto EC = checkOffset(Offset, SrcData.size()))
+ return EC;
::memcpy(&Data[Offset], SrcData.data(), SrcData.size());
return Error::success();
}
- Error commit() const override { return Error::success(); }
+ Error commit() override { return Error::success(); }
MSFStreamLayout layout() const {
return MSFStreamLayout{static_cast<uint32_t>(Data.size()), Blocks};
@@ -78,8 +82,8 @@ TEST(MappedBlockStreamTest, ReadBeyondEndOfStreamRef) {
auto S = MappedBlockStream::createStream(F.block_size(), F.block_count(),
F.layout(), F);
- StreamReader R(*S);
- ReadableStreamRef SR;
+ BinaryStreamReader R(*S);
+ BinaryStreamRef SR;
EXPECT_NO_ERROR(R.readStreamRef(SR, 0U));
ArrayRef<uint8_t> Buffer;
EXPECT_ERROR(SR.readBytes(0U, 1U, Buffer));
@@ -94,7 +98,7 @@ TEST(MappedBlockStreamTest, ReadOntoNonEmptyBuffer) {
auto S = MappedBlockStream::createStream(F.block_size(), F.block_count(),
F.layout(), F);
- StreamReader R(*S);
+ BinaryStreamReader R(*S);
StringRef Str = "ZYXWVUTSRQPONMLKJIHGFEDCBA";
EXPECT_NO_ERROR(R.readFixedString(Str, 1));
EXPECT_EQ(Str, StringRef("A"));
@@ -108,7 +112,7 @@ TEST(MappedBlockStreamTest, ZeroCopyReadContiguousBreak) {
DiscontiguousStream F(BlocksAry, DataAry);
auto S = MappedBlockStream::createStream(F.block_size(), F.block_count(),
F.layout(), F);
- StreamReader R(*S);
+ BinaryStreamReader R(*S);
StringRef Str;
EXPECT_NO_ERROR(R.readFixedString(Str, 2));
EXPECT_EQ(Str, StringRef("AB"));
@@ -127,7 +131,7 @@ TEST(MappedBlockStreamTest, CopyReadNonContiguousBreak) {
DiscontiguousStream F(BlocksAry, DataAry);
auto S = MappedBlockStream::createStream(F.block_size(), F.block_count(),
F.layout(), F);
- StreamReader R(*S);
+ BinaryStreamReader R(*S);
StringRef Str;
EXPECT_NO_ERROR(R.readFixedString(Str, 10));
EXPECT_EQ(Str, StringRef("ABCDEFGHIJ"));
@@ -140,7 +144,7 @@ TEST(MappedBlockStreamTest, InvalidReadSizeNoBreak) {
DiscontiguousStream F(BlocksAry, DataAry);
auto S = MappedBlockStream::createStream(F.block_size(), F.block_count(),
F.layout(), F);
- StreamReader R(*S);
+ BinaryStreamReader R(*S);
StringRef Str;
R.setOffset(10);
@@ -154,7 +158,7 @@ TEST(MappedBlockStreamTest, InvalidReadSizeContiguousBreak) {
DiscontiguousStream F(BlocksAry, DataAry);
auto S = MappedBlockStream::createStream(F.block_size(), F.block_count(),
F.layout(), F);
- StreamReader R(*S);
+ BinaryStreamReader R(*S);
StringRef Str;
R.setOffset(6);
@@ -168,7 +172,7 @@ TEST(MappedBlockStreamTest, InvalidReadSizeNonContiguousBreak) {
DiscontiguousStream F(BlocksAry, DataAry);
auto S = MappedBlockStream::createStream(F.block_size(), F.block_count(),
F.layout(), F);
- StreamReader R(*S);
+ BinaryStreamReader R(*S);
StringRef Str;
EXPECT_ERROR(R.readFixedString(Str, 11));
@@ -181,7 +185,7 @@ TEST(MappedBlockStreamTest, ZeroCopyReadNoBreak) {
DiscontiguousStream F(BlocksAry, DataAry);
auto S = MappedBlockStream::createStream(F.block_size(), F.block_count(),
F.layout(), F);
- StreamReader R(*S);
+ BinaryStreamReader R(*S);
StringRef Str;
EXPECT_NO_ERROR(R.readFixedString(Str, 1));
EXPECT_EQ(Str, StringRef("A"));
@@ -195,7 +199,7 @@ TEST(MappedBlockStreamTest, UnalignedOverlappingRead) {
DiscontiguousStream F(BlocksAry, DataAry);
auto S = MappedBlockStream::createStream(F.block_size(), F.block_count(),
F.layout(), F);
- StreamReader R(*S);
+ BinaryStreamReader R(*S);
StringRef Str1;
StringRef Str2;
EXPECT_NO_ERROR(R.readFixedString(Str1, 7));
@@ -216,7 +220,7 @@ TEST(MappedBlockStreamTest, UnalignedOverlappingReadFail) {
DiscontiguousStream F(BlocksAry, DataAry);
auto S = MappedBlockStream::createStream(F.block_size(), F.block_count(),
F.layout(), F);
- StreamReader R(*S);
+ BinaryStreamReader R(*S);
StringRef Str1;
StringRef Str2;
EXPECT_NO_ERROR(R.readFixedString(Str1, 6));
@@ -323,8 +327,8 @@ TEST(MappedBlockStreamTest, TestWriteThenRead) {
uint32_t intArr1[] = {890723408, 29082234};
ArrayRef<uint32_t> intArray[] = {intArr0, intArr1};
- StreamReader Reader(*S);
- StreamWriter Writer(*S);
+ BinaryStreamReader Reader(*S);
+ BinaryStreamWriter Writer(*S);
EXPECT_NO_ERROR(Writer.writeInteger(u16[0]));
EXPECT_NO_ERROR(Reader.readInteger(u16[1]));
EXPECT_EQ(u16[0], u16[1]);
@@ -352,8 +356,8 @@ TEST(MappedBlockStreamTest, TestWriteThenRead) {
Reader.setOffset(0);
Writer.setOffset(0);
::memset(DataBytes.data(), 0, 10);
- EXPECT_NO_ERROR(Writer.writeZeroString(ZStr[0]));
- EXPECT_NO_ERROR(Reader.readZeroString(ZStr[1]));
+ EXPECT_NO_ERROR(Writer.writeCString(ZStr[0]));
+ EXPECT_NO_ERROR(Reader.readCString(ZStr[1]));
EXPECT_EQ(ZStr[0], ZStr[1]);
EXPECT_EQ(
std::vector<uint8_t>({'r', 'e', 'Z', ' ', 'S', 't', 'o', 'r', 0, 0}),
@@ -399,22 +403,22 @@ TEST(MappedBlockStreamTest, TestWriteContiguousStreamRef) {
F.block_size(), F.block_count(), F.layout(), F);
// First write "Test Str" into the source stream.
- MutableByteStream SourceStream(SrcData);
- StreamWriter SourceWriter(SourceStream);
- EXPECT_NO_ERROR(SourceWriter.writeZeroString("Test Str"));
+ MutableBinaryByteStream SourceStream(SrcData, little);
+ BinaryStreamWriter SourceWriter(SourceStream);
+ EXPECT_NO_ERROR(SourceWriter.writeCString("Test Str"));
EXPECT_EQ(SrcDataBytes, std::vector<uint8_t>(
{'T', 'e', 's', 't', ' ', 'S', 't', 'r', 0, 0}));
// Then write the source stream into the dest stream.
- StreamWriter DestWriter(*DestStream);
+ BinaryStreamWriter DestWriter(*DestStream);
EXPECT_NO_ERROR(DestWriter.writeStreamRef(SourceStream));
EXPECT_EQ(DestDataBytes, std::vector<uint8_t>(
{'s', 'e', 'T', ' ', 'S', 't', 't', 'r', 0, 0}));
// Then read the string back out of the dest stream.
StringRef Result;
- StreamReader DestReader(*DestStream);
- EXPECT_NO_ERROR(DestReader.readZeroString(Result));
+ BinaryStreamReader DestReader(*DestStream);
+ EXPECT_NO_ERROR(DestReader.readCString(Result));
EXPECT_EQ(Result, "Test Str");
}
@@ -436,21 +440,21 @@ TEST(MappedBlockStreamTest, TestWriteDiscontiguousStreamRef) {
SrcF.block_size(), SrcF.block_count(), SrcF.layout(), SrcF);
// First write "Test Str" into the source stream.
- StreamWriter SourceWriter(*Src);
- EXPECT_NO_ERROR(SourceWriter.writeZeroString("Test Str"));
+ BinaryStreamWriter SourceWriter(*Src);
+ EXPECT_NO_ERROR(SourceWriter.writeCString("Test Str"));
EXPECT_EQ(SrcDataBytes, std::vector<uint8_t>(
{'e', 'T', 't', 't', ' ', 'S', 's', 'r', 0, 0}));
// Then write the source stream into the dest stream.
- StreamWriter DestWriter(*Dest);
+ BinaryStreamWriter DestWriter(*Dest);
EXPECT_NO_ERROR(DestWriter.writeStreamRef(*Src));
EXPECT_EQ(DestDataBytes, std::vector<uint8_t>(
{'s', 'e', 'T', ' ', 'S', 't', 't', 'r', 0, 0}));
// Then read the string back out of the dest stream.
StringRef Result;
- StreamReader DestReader(*Dest);
- EXPECT_NO_ERROR(DestReader.readZeroString(Result));
+ BinaryStreamReader DestReader(*Dest);
+ EXPECT_NO_ERROR(DestReader.readCString(Result));
EXPECT_EQ(Result, "Test Str");
}
diff --git a/unittests/DebugInfo/PDB/PDBApiTest.cpp b/unittests/DebugInfo/PDB/PDBApiTest.cpp
index cd0f928a08ab..6afe83cd90dd 100644
--- a/unittests/DebugInfo/PDB/PDBApiTest.cpp
+++ b/unittests/DebugInfo/PDB/PDBApiTest.cpp
@@ -226,6 +226,7 @@ public:
MOCK_SYMBOL_ACCESSOR(getMachineType)
MOCK_SYMBOL_ACCESSOR(getThunkOrdinal)
MOCK_SYMBOL_ACCESSOR(getLength)
+ MOCK_SYMBOL_ACCESSOR(getVirtualBaseTableType)
MOCK_SYMBOL_ACCESSOR(getLiveRangeLength)
MOCK_SYMBOL_ACCESSOR(getVirtualAddress)
MOCK_SYMBOL_ACCESSOR(getUdtKind)
diff --git a/unittests/DebugInfo/PDB/StringTableBuilderTest.cpp b/unittests/DebugInfo/PDB/StringTableBuilderTest.cpp
new file mode 100644
index 000000000000..7c4838778e43
--- /dev/null
+++ b/unittests/DebugInfo/PDB/StringTableBuilderTest.cpp
@@ -0,0 +1,55 @@
+//===- StringTableBuilderTest.cpp -----------------------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ErrorChecking.h"
+
+#include "llvm/DebugInfo/PDB/Native/StringTable.h"
+#include "llvm/DebugInfo/PDB/Native/StringTableBuilder.h"
+#include "llvm/Support/BinaryByteStream.h"
+#include "llvm/Support/BinaryStreamReader.h"
+#include "llvm/Support/BinaryStreamWriter.h"
+
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace llvm::pdb;
+using namespace llvm::support;
+
+namespace {
+class StringTableBuilderTest : public ::testing::Test {};
+}
+
+TEST_F(StringTableBuilderTest, Simple) {
+ // Create /names table contents.
+ StringTableBuilder Builder;
+ EXPECT_EQ(1U, Builder.insert("foo"));
+ EXPECT_EQ(5U, Builder.insert("bar"));
+ EXPECT_EQ(1U, Builder.insert("foo"));
+ EXPECT_EQ(9U, Builder.insert("baz"));
+
+ std::vector<uint8_t> Buffer(Builder.finalize());
+ MutableBinaryByteStream OutStream(Buffer, little);
+ BinaryStreamWriter Writer(OutStream);
+ EXPECT_NO_ERROR(Builder.commit(Writer));
+
+ // Reads the contents back.
+ BinaryByteStream InStream(Buffer, little);
+ BinaryStreamReader Reader(InStream);
+ StringTable Table;
+ EXPECT_NO_ERROR(Table.load(Reader));
+
+ EXPECT_EQ(3U, Table.getNameCount());
+ EXPECT_EQ(1U, Table.getHashVersion());
+ EXPECT_EQ("foo", Table.getStringForID(1));
+ EXPECT_EQ("bar", Table.getStringForID(5));
+ EXPECT_EQ("baz", Table.getStringForID(9));
+ EXPECT_EQ(1U, Table.getIDForString("foo"));
+ EXPECT_EQ(5U, Table.getIDForString("bar"));
+ EXPECT_EQ(9U, Table.getIDForString("baz"));
+}
diff --git a/unittests/DebugInfo/PDB/TypeServerHandlerTest.cpp b/unittests/DebugInfo/PDB/TypeServerHandlerTest.cpp
new file mode 100644
index 000000000000..6995e8f9dded
--- /dev/null
+++ b/unittests/DebugInfo/PDB/TypeServerHandlerTest.cpp
@@ -0,0 +1,175 @@
+//===- llvm/unittest/DebugInfo/PDB/TypeServerHandlerTest.cpp --------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ErrorChecking.h"
+
+#include "llvm/DebugInfo/CodeView/CVTypeVisitor.h"
+#include "llvm/DebugInfo/CodeView/TypeRecord.h"
+#include "llvm/DebugInfo/CodeView/TypeRecordMapping.h"
+#include "llvm/DebugInfo/CodeView/TypeSerializer.h"
+#include "llvm/DebugInfo/CodeView/TypeServerHandler.h"
+#include "llvm/DebugInfo/CodeView/TypeTableBuilder.h"
+#include "llvm/DebugInfo/CodeView/TypeVisitorCallbackPipeline.h"
+#include "llvm/DebugInfo/CodeView/TypeVisitorCallbacks.h"
+#include "llvm/DebugInfo/PDB/Native/RawTypes.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/Error.h"
+
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace llvm::codeview;
+using namespace llvm::pdb;
+
+namespace {
+
+constexpr uint8_t Guid[] = {0x2a, 0x2c, 0x1c, 0x2a, 0xcb, 0x9e, 0x48, 0x18,
+ 0x82, 0x82, 0x7a, 0x87, 0xc3, 0xfe, 0x16, 0xe8};
+StringRef GuidStr(reinterpret_cast<const char *>(Guid),
+ llvm::array_lengthof(Guid));
+
+constexpr const char *Name = "Test Name";
+constexpr int Age = 1;
+
+class MockTypeServerHandler : public TypeServerHandler {
+public:
+ explicit MockTypeServerHandler(bool HandleAlways)
+ : HandleAlways(HandleAlways) {}
+
+ Expected<bool> handle(TypeServer2Record &TS,
+ TypeVisitorCallbacks &Callbacks) override {
+ if (TS.Age != Age || TS.Guid != GuidStr || TS.Name != Name)
+ return make_error<CodeViewError>(cv_error_code::corrupt_record,
+ "Invalid TypeServer record!");
+
+ if (Handled && !HandleAlways)
+ return false;
+
+ Handled = true;
+ return true;
+ }
+
+ bool Handled = false;
+ bool HandleAlways;
+};
+
+class MockTypeVisitorCallbacks : public TypeVisitorCallbacks {
+public:
+ enum class State {
+ Ready,
+ VisitTypeBegin,
+ VisitKnownRecord,
+ VisitTypeEnd,
+ };
+ Error visitTypeBegin(CVType &CVT) override {
+ if (S != State::Ready)
+ return make_error<CodeViewError>(cv_error_code::unspecified,
+ "Invalid visitor state!");
+
+ S = State::VisitTypeBegin;
+ return Error::success();
+ }
+
+ Error visitKnownRecord(CVType &CVT, TypeServer2Record &TS) override {
+ if (S != State::VisitTypeBegin)
+ return make_error<CodeViewError>(cv_error_code::unspecified,
+ "Invalid visitor state!");
+
+ S = State::VisitKnownRecord;
+ return Error::success();
+ }
+
+ Error visitTypeEnd(CVType &CVT) override {
+ if (S != State::VisitKnownRecord)
+ return make_error<CodeViewError>(cv_error_code::unspecified,
+ "Invalid visitor state!");
+
+ S = State::VisitTypeEnd;
+ return Error::success();
+ }
+
+ State S = State::Ready;
+};
+
+class TypeServerHandlerTest : public testing::Test {
+public:
+ void SetUp() override {
+ TypeServer2Record R(TypeRecordKind::TypeServer2);
+ R.Age = Age;
+ R.Guid = GuidStr;
+ R.Name = Name;
+
+ TypeTableBuilder Builder(Allocator);
+ Builder.writeKnownType(R);
+ TypeServerRecord.RecordData = Builder.records().front();
+ TypeServerRecord.Type = TypeLeafKind::LF_TYPESERVER2;
+ }
+
+protected:
+ BumpPtrAllocator Allocator;
+ CVType TypeServerRecord;
+};
+
+// Test that when no type server handler is registered, it gets handled by the
+// normal
+// visitor callbacks.
+TEST_F(TypeServerHandlerTest, VisitRecordNoTypeServer) {
+ MockTypeVisitorCallbacks C2;
+ MockTypeVisitorCallbacks C1;
+ TypeVisitorCallbackPipeline Pipeline;
+
+ Pipeline.addCallbackToPipeline(C1);
+ Pipeline.addCallbackToPipeline(C2);
+ CVTypeVisitor Visitor(Pipeline);
+ EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord));
+
+ EXPECT_EQ(MockTypeVisitorCallbacks::State::VisitTypeEnd, C1.S);
+ EXPECT_EQ(MockTypeVisitorCallbacks::State::VisitTypeEnd, C2.S);
+}
+
+// Test that when a TypeServerHandler is registered, it gets consumed by the
+// handler if and only if the handler returns true.
+TEST_F(TypeServerHandlerTest, VisitRecordWithTypeServerOnce) {
+ MockTypeServerHandler Handler(false);
+
+ MockTypeVisitorCallbacks C1;
+ CVTypeVisitor Visitor(C1);
+ Visitor.addTypeServerHandler(Handler);
+
+ // Our mock server returns true the first time.
+ EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord));
+ EXPECT_TRUE(Handler.Handled);
+ EXPECT_EQ(MockTypeVisitorCallbacks::State::Ready, C1.S);
+
+ // And false the second time.
+ EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord));
+ EXPECT_TRUE(Handler.Handled);
+ EXPECT_EQ(MockTypeVisitorCallbacks::State::VisitTypeEnd, C1.S);
+}
+
+// Test that when a type server handler is registered, if the handler keeps
+// returning true, it will keep getting consumed by the handler and not go
+// to the default processor.
+TEST_F(TypeServerHandlerTest, VisitRecordWithTypeServerAlways) {
+ MockTypeServerHandler Handler(true);
+
+ MockTypeVisitorCallbacks C1;
+ CVTypeVisitor Visitor(C1);
+ Visitor.addTypeServerHandler(Handler);
+
+ EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord));
+ EXPECT_TRUE(Handler.Handled);
+ EXPECT_EQ(MockTypeVisitorCallbacks::State::Ready, C1.S);
+
+ EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord));
+ EXPECT_TRUE(Handler.Handled);
+ EXPECT_EQ(MockTypeVisitorCallbacks::State::Ready, C1.S);
+}
+
+} // end anonymous namespace