//===-- PythonDataObjectsTests.cpp ----------------------------------------===//
//
// 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 "gtest/gtest.h"

#include "llvm/ADT/STLExtras.h"
#include "llvm/DebugInfo/PDB/PDBSymbolData.h"
#include "llvm/DebugInfo/PDB/PDBSymbolExe.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Testing/Support/Error.h"

#include "Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h"
#include "Plugins/SymbolFile/DWARF/SymbolFileDWARF.h"
#include "Plugins/SymbolFile/PDB/SymbolFilePDB.h"
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
#include "TestingSupport/TestUtilities.h"
#include "lldb/Core/Address.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Symbol/CompileUnit.h"
#include "lldb/Symbol/LineTable.h"
#include "lldb/Symbol/TypeMap.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/FileSpec.h"

#if defined(_WIN32)
#include "lldb/Host/windows/windows.h"
#include <objbase.h>
#endif

#include <algorithm>

using namespace lldb_private;

class SymbolFilePDBTests : public testing::Test {
public:
  void SetUp() override {
// Initialize and TearDown the plugin every time, so we get a brand new
// AST every time so that modifications to the AST from each test don't
// leak into the next test.
#if defined(_WIN32)
    ::CoInitializeEx(nullptr, COINIT_MULTITHREADED);
#endif

    FileSystem::Initialize();
    HostInfo::Initialize();
    ObjectFilePECOFF::Initialize();
    plugin::dwarf::SymbolFileDWARF::Initialize();
    TypeSystemClang::Initialize();
    SymbolFilePDB::Initialize();

    m_pdb_test_exe = GetInputFilePath("test-pdb.exe");
    m_types_test_exe = GetInputFilePath("test-pdb-types.exe");
  }

  void TearDown() override {
    SymbolFilePDB::Terminate();
    TypeSystemClang::Initialize();
    plugin::dwarf::SymbolFileDWARF::Terminate();
    ObjectFilePECOFF::Terminate();
    HostInfo::Terminate();
    FileSystem::Terminate();

#if defined(_WIN32)
    ::CoUninitialize();
#endif
  }

protected:
  std::string m_pdb_test_exe;
  std::string m_types_test_exe;

  bool FileSpecMatchesAsBaseOrFull(const FileSpec &left,
                                   const FileSpec &right) const {
    // If the filenames don't match, the paths can't be equal
    if (!left.FileEquals(right))
      return false;
    // If BOTH have a directory, also compare the directories.
    if (left.GetDirectory() && right.GetDirectory())
      return left.DirectoryEquals(right);

    // If one has a directory but not the other, they match.
    return true;
  }

  void VerifyLineEntry(lldb::ModuleSP module, const SymbolContext &sc,
                       const FileSpec &spec, LineTable &lt, uint32_t line,
                       lldb::addr_t addr) {
    LineEntry entry;
    Address address;
    EXPECT_TRUE(module->ResolveFileAddress(addr, address));

    EXPECT_TRUE(lt.FindLineEntryByAddress(address, entry));
    EXPECT_EQ(line, entry.line);
    EXPECT_EQ(address, entry.range.GetBaseAddress());

    EXPECT_TRUE(FileSpecMatchesAsBaseOrFull(spec, entry.GetFile()));
  }

  bool ContainsCompileUnit(const SymbolContextList &sc_list,
                           const FileSpec &spec) const {
    for (size_t i = 0; i < sc_list.GetSize(); ++i) {
      const SymbolContext &sc = sc_list[i];
      if (FileSpecMatchesAsBaseOrFull(sc.comp_unit->GetPrimaryFile(), spec))
        return true;
    }
    return false;
  }

  uint64_t GetGlobalConstantInteger(llvm::pdb::IPDBSession &session,
                                    llvm::StringRef var) const {
    auto global = session.getGlobalScope();
    auto results =
        global->findChildren(llvm::pdb::PDB_SymType::Data, var,
                             llvm::pdb::PDB_NameSearchFlags::NS_Default);
    uint32_t count = results->getChildCount();
    if (count == 0)
      return -1;

    auto item = results->getChildAtIndex(0);
    auto symbol = llvm::dyn_cast<llvm::pdb::PDBSymbolData>(item.get());
    if (!symbol)
      return -1;
    llvm::pdb::Variant value = symbol->getValue();
    switch (value.Type) {
    case llvm::pdb::PDB_VariantType::Int16:
      return value.Value.Int16;
    case llvm::pdb::PDB_VariantType::Int32:
      return value.Value.Int32;
    case llvm::pdb::PDB_VariantType::UInt16:
      return value.Value.UInt16;
    case llvm::pdb::PDB_VariantType::UInt32:
      return value.Value.UInt32;
    default:
      return 0;
    }
  }
};

TEST_F(SymbolFilePDBTests, TestAbilitiesForPDB) {
  // Test that when we have PDB debug info, SymbolFilePDB is used.
  FileSpec fspec(m_pdb_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFile *symfile = module->GetSymbolFile();
  EXPECT_NE(nullptr, symfile);
  EXPECT_EQ(symfile->GetPluginName(), SymbolFilePDB::GetPluginNameStatic());

  uint32_t expected_abilities = SymbolFile::kAllAbilities;
  EXPECT_EQ(expected_abilities, symfile->CalculateAbilities());
}

TEST_F(SymbolFilePDBTests, TestResolveSymbolContextBasename) {
  // Test that attempting to call ResolveSymbolContext with only a basename
  // finds all full paths
  // with the same basename
  FileSpec fspec(m_pdb_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFile *symfile = module->GetSymbolFile();

  FileSpec header_spec("test-pdb.cpp");
  SymbolContextList sc_list;
  SourceLocationSpec location_spec(header_spec, /*line=*/0);
  uint32_t result_count = symfile->ResolveSymbolContext(
      location_spec, lldb::eSymbolContextCompUnit, sc_list);
  EXPECT_EQ(1u, result_count);
  EXPECT_TRUE(ContainsCompileUnit(sc_list, header_spec));
}

TEST_F(SymbolFilePDBTests, TestResolveSymbolContextFullPath) {
  // Test that attempting to call ResolveSymbolContext with a full path only
  // finds the one source
  // file that matches the full path.
  FileSpec fspec(m_pdb_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFile *symfile = module->GetSymbolFile();

  FileSpec header_spec(
      R"spec(D:\src\llvm\tools\lldb\unittests\SymbolFile\PDB\Inputs\test-pdb.cpp)spec");
  SymbolContextList sc_list;
  SourceLocationSpec location_spec(header_spec, /*line=*/0);
  uint32_t result_count = symfile->ResolveSymbolContext(
      location_spec, lldb::eSymbolContextCompUnit, sc_list);
  EXPECT_GE(1u, result_count);
  EXPECT_TRUE(ContainsCompileUnit(sc_list, header_spec));
}

TEST_F(SymbolFilePDBTests, TestLookupOfHeaderFileWithInlines) {
  // Test that when looking up a header file via ResolveSymbolContext (i.e. a
  // file that was not by itself
  // compiled, but only contributes to the combined code of other source files),
  // a SymbolContext is returned
  // for each compiland which has line contributions from the requested header.
  FileSpec fspec(m_pdb_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFile *symfile = module->GetSymbolFile();

  FileSpec header_specs[] = {FileSpec("test-pdb.h"),
                             FileSpec("test-pdb-nested.h")};
  FileSpec main_cpp_spec("test-pdb.cpp");
  FileSpec alt_cpp_spec("test-pdb-alt.cpp");
  for (const auto &hspec : header_specs) {
    SymbolContextList sc_list;
    SourceLocationSpec location_spec(hspec, /*line=*/0, /*column=*/std::nullopt,
                                     /*check_inlines=*/true);
    uint32_t result_count = symfile->ResolveSymbolContext(
        location_spec, lldb::eSymbolContextCompUnit, sc_list);
    EXPECT_EQ(2u, result_count);
    EXPECT_TRUE(ContainsCompileUnit(sc_list, main_cpp_spec));
    EXPECT_TRUE(ContainsCompileUnit(sc_list, alt_cpp_spec));
  }
}

TEST_F(SymbolFilePDBTests, TestLookupOfHeaderFileWithNoInlines) {
  // Test that when looking up a header file via ResolveSymbolContext (i.e. a
  // file that was not by itself
  // compiled, but only contributes to the combined code of other source files),
  // that if check_inlines
  // is false, no SymbolContexts are returned.
  FileSpec fspec(m_pdb_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFile *symfile = module->GetSymbolFile();

  FileSpec header_specs[] = {FileSpec("test-pdb.h"),
                             FileSpec("test-pdb-nested.h")};
  for (const auto &hspec : header_specs) {
    SymbolContextList sc_list;
    SourceLocationSpec location_spec(hspec, /*line=*/0);
    uint32_t result_count = symfile->ResolveSymbolContext(
        location_spec, lldb::eSymbolContextCompUnit, sc_list);
    EXPECT_EQ(0u, result_count);
  }
}

TEST_F(SymbolFilePDBTests, TestLineTablesMatchAll) {
  // Test that when calling ResolveSymbolContext with a line number of 0, all
  // line entries from
  // the specified files are returned.
  FileSpec fspec(m_pdb_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFile *symfile = module->GetSymbolFile();

  FileSpec source_file("test-pdb.cpp");
  FileSpec header1("test-pdb.h");
  FileSpec header2("test-pdb-nested.h");
  uint32_t cus = symfile->GetNumCompileUnits();
  EXPECT_EQ(2u, cus);

  SymbolContextList sc_list;
  lldb::SymbolContextItem scope =
      lldb::eSymbolContextCompUnit | lldb::eSymbolContextLineEntry;

  SourceLocationSpec location_spec(
      source_file, /*line=*/0, /*column=*/std::nullopt, /*check_inlines=*/true);
  uint32_t count = symfile->ResolveSymbolContext(location_spec, scope, sc_list);
  EXPECT_EQ(1u, count);
  SymbolContext sc;
  EXPECT_TRUE(sc_list.GetContextAtIndex(0, sc));

  LineTable *lt = sc.comp_unit->GetLineTable();
  EXPECT_NE(nullptr, lt);
  count = lt->GetSize();
  // We expect one extra entry for termination (per function)
  EXPECT_EQ(16u, count);

  VerifyLineEntry(module, sc, source_file, *lt, 7, 0x401040);
  VerifyLineEntry(module, sc, source_file, *lt, 8, 0x401043);
  VerifyLineEntry(module, sc, source_file, *lt, 9, 0x401045);

  VerifyLineEntry(module, sc, source_file, *lt, 13, 0x401050);
  VerifyLineEntry(module, sc, source_file, *lt, 14, 0x401054);
  VerifyLineEntry(module, sc, source_file, *lt, 15, 0x401070);

  VerifyLineEntry(module, sc, header1, *lt, 9, 0x401090);
  VerifyLineEntry(module, sc, header1, *lt, 10, 0x401093);
  VerifyLineEntry(module, sc, header1, *lt, 11, 0x4010a2);

  VerifyLineEntry(module, sc, header2, *lt, 5, 0x401080);
  VerifyLineEntry(module, sc, header2, *lt, 6, 0x401083);
  VerifyLineEntry(module, sc, header2, *lt, 7, 0x401089);
}

TEST_F(SymbolFilePDBTests, TestLineTablesMatchSpecific) {
  // Test that when calling ResolveSymbolContext with a specific line number,
  // only line entries
  // which match the requested line are returned.
  FileSpec fspec(m_pdb_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFile *symfile = module->GetSymbolFile();

  FileSpec source_file("test-pdb.cpp");
  FileSpec header1("test-pdb.h");
  FileSpec header2("test-pdb-nested.h");
  uint32_t cus = symfile->GetNumCompileUnits();
  EXPECT_EQ(2u, cus);

  SymbolContextList sc_list;
  lldb::SymbolContextItem scope =
      lldb::eSymbolContextCompUnit | lldb::eSymbolContextLineEntry;

  // First test with line 7, and verify that only line 7 entries are added.
  SourceLocationSpec location_spec(
      source_file, /*line=*/7, /*column=*/std::nullopt, /*check_inlines=*/true);
  uint32_t count = symfile->ResolveSymbolContext(location_spec, scope, sc_list);
  EXPECT_EQ(1u, count);
  SymbolContext sc;
  EXPECT_TRUE(sc_list.GetContextAtIndex(0, sc));

  LineTable *lt = sc.comp_unit->GetLineTable();
  EXPECT_NE(nullptr, lt);
  count = lt->GetSize();
  // We expect one extra entry for termination
  EXPECT_EQ(3u, count);

  VerifyLineEntry(module, sc, source_file, *lt, 7, 0x401040);
  VerifyLineEntry(module, sc, header2, *lt, 7, 0x401089);

  sc_list.Clear();
  // Then test with line 9, and verify that only line 9 entries are added.
  location_spec = SourceLocationSpec(
      source_file, /*line=*/9, /*column=*/std::nullopt, /*check_inlines=*/true);
  count = symfile->ResolveSymbolContext(location_spec, scope, sc_list);
  EXPECT_EQ(1u, count);
  EXPECT_TRUE(sc_list.GetContextAtIndex(0, sc));

  lt = sc.comp_unit->GetLineTable();
  EXPECT_NE(nullptr, lt);
  count = lt->GetSize();
  // We expect one extra entry for termination
  EXPECT_EQ(3u, count);

  VerifyLineEntry(module, sc, source_file, *lt, 9, 0x401045);
  VerifyLineEntry(module, sc, header1, *lt, 9, 0x401090);
}

TEST_F(SymbolFilePDBTests, TestSimpleClassTypes) {
  FileSpec fspec(m_types_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFilePDB *symfile =
      static_cast<SymbolFilePDB *>(module->GetSymbolFile());
  llvm::pdb::IPDBSession &session = symfile->GetPDBSession();
  TypeResults query_results;
  symfile->FindTypes(TypeQuery("Class"), query_results);
  TypeMap &results = query_results.GetTypeMap();
  EXPECT_EQ(1u, results.GetSize());
  lldb::TypeSP udt_type = results.GetTypeAtIndex(0);
  EXPECT_EQ(ConstString("Class"), udt_type->GetName());
  CompilerType compiler_type = udt_type->GetForwardCompilerType();
  EXPECT_TRUE(TypeSystemClang::IsClassType(compiler_type.GetOpaqueQualType()));
  EXPECT_EQ(GetGlobalConstantInteger(session, "sizeof_Class"),
            udt_type->GetByteSize(nullptr));
}

TEST_F(SymbolFilePDBTests, TestNestedClassTypes) {
  FileSpec fspec(m_types_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFilePDB *symfile =
      static_cast<SymbolFilePDB *>(module->GetSymbolFile());
  llvm::pdb::IPDBSession &session = symfile->GetPDBSession();

  auto clang_ast_ctx_or_err =
      symfile->GetTypeSystemForLanguage(lldb::eLanguageTypeC_plus_plus);
  ASSERT_THAT_EXPECTED(clang_ast_ctx_or_err, llvm::Succeeded());

  auto clang_ast_ctx =
      llvm::dyn_cast_or_null<TypeSystemClang>(clang_ast_ctx_or_err->get());
  EXPECT_NE(nullptr, clang_ast_ctx);

  TypeResults query_results;
  symfile->FindTypes(TypeQuery("Class"), query_results);
  TypeMap &results = query_results.GetTypeMap();

  EXPECT_EQ(1u, results.GetSize());

  auto Class = results.GetTypeAtIndex(0);
  EXPECT_TRUE(Class);
  EXPECT_TRUE(Class->IsValidType());

  auto ClassCompilerType = Class->GetFullCompilerType();
  EXPECT_TRUE(ClassCompilerType.IsValid());

  auto ClassDeclCtx = clang_ast_ctx->GetDeclContextForType(ClassCompilerType);
  EXPECT_NE(nullptr, ClassDeclCtx);

  // There are two symbols for nested classes: one belonging to enclosing class
  // and one is global. We process correctly this case and create the same
  // compiler type for both, but `FindTypes` may return more than one type
  // (with the same compiler type) because the symbols have different IDs.

  auto ClassCompilerDeclCtx = CompilerDeclContext(clang_ast_ctx, ClassDeclCtx);
  TypeResults query_results_nested;
  symfile->FindTypes(
      TypeQuery(ClassCompilerDeclCtx, ConstString("NestedClass")),
      query_results_nested);
  TypeMap &more_results = query_results_nested.GetTypeMap();
  EXPECT_LE(1u, more_results.GetSize());

  lldb::TypeSP udt_type = more_results.GetTypeAtIndex(0);
  EXPECT_EQ(ConstString("NestedClass"), udt_type->GetName());

  CompilerType compiler_type = udt_type->GetForwardCompilerType();
  EXPECT_TRUE(TypeSystemClang::IsClassType(compiler_type.GetOpaqueQualType()));

  EXPECT_EQ(GetGlobalConstantInteger(session, "sizeof_NestedClass"),
            udt_type->GetByteSize(nullptr));
}

TEST_F(SymbolFilePDBTests, TestClassInNamespace) {
  FileSpec fspec(m_types_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFilePDB *symfile =
      static_cast<SymbolFilePDB *>(module->GetSymbolFile());
  llvm::pdb::IPDBSession &session = symfile->GetPDBSession();
  auto clang_ast_ctx_or_err =
      symfile->GetTypeSystemForLanguage(lldb::eLanguageTypeC_plus_plus);
  ASSERT_THAT_EXPECTED(clang_ast_ctx_or_err, llvm::Succeeded());

  auto clang_ast_ctx =
      llvm::dyn_cast_or_null<TypeSystemClang>(clang_ast_ctx_or_err->get());
  EXPECT_NE(nullptr, clang_ast_ctx);

  clang::ASTContext &ast_ctx = clang_ast_ctx->getASTContext();

  auto tu = ast_ctx.getTranslationUnitDecl();
  EXPECT_NE(nullptr, tu);

  symfile->ParseDeclsForContext(CompilerDeclContext(
      clang_ast_ctx, static_cast<clang::DeclContext *>(tu)));

  auto ns_namespace_decl_ctx =
      symfile->FindNamespace(ConstString("NS"), CompilerDeclContext(), true);
  EXPECT_TRUE(ns_namespace_decl_ctx.IsValid());

  TypeResults query_results;
  symfile->FindTypes(TypeQuery(ns_namespace_decl_ctx, ConstString("NSClass")),
                     query_results);
  TypeMap &results = query_results.GetTypeMap();
  EXPECT_EQ(1u, results.GetSize());

  lldb::TypeSP udt_type = results.GetTypeAtIndex(0);
  EXPECT_EQ(ConstString("NSClass"), udt_type->GetName());

  CompilerType compiler_type = udt_type->GetForwardCompilerType();
  EXPECT_TRUE(TypeSystemClang::IsClassType(compiler_type.GetOpaqueQualType()));

  EXPECT_EQ(GetGlobalConstantInteger(session, "sizeof_NSClass"),
            udt_type->GetByteSize(nullptr));
}

TEST_F(SymbolFilePDBTests, TestEnumTypes) {
  FileSpec fspec(m_types_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFilePDB *symfile =
      static_cast<SymbolFilePDB *>(module->GetSymbolFile());
  llvm::pdb::IPDBSession &session = symfile->GetPDBSession();
  const char *EnumsToCheck[] = {"Enum", "ShortEnum"};
  for (auto Enum : EnumsToCheck) {

    TypeResults query_results;
    symfile->FindTypes(TypeQuery(Enum), query_results);
    TypeMap &results = query_results.GetTypeMap();
    EXPECT_EQ(1u, results.GetSize());
    lldb::TypeSP enum_type = results.GetTypeAtIndex(0);
    EXPECT_EQ(ConstString(Enum), enum_type->GetName());
    CompilerType compiler_type = enum_type->GetFullCompilerType();
    EXPECT_TRUE(TypeSystemClang::IsEnumType(compiler_type.GetOpaqueQualType()));
    clang::EnumDecl *enum_decl = TypeSystemClang::GetAsEnumDecl(compiler_type);
    EXPECT_NE(nullptr, enum_decl);
    EXPECT_EQ(2, std::distance(enum_decl->enumerator_begin(),
                               enum_decl->enumerator_end()));

    std::string sizeof_var = "sizeof_";
    sizeof_var.append(Enum);
    EXPECT_EQ(GetGlobalConstantInteger(session, sizeof_var),
              enum_type->GetByteSize(nullptr));
  }
}

TEST_F(SymbolFilePDBTests, TestArrayTypes) {
  // In order to get this test working, we need to support lookup by symbol
  // name.  Because array
  // types themselves do not have names, only the symbols have names (i.e. the
  // name of the array).
}

TEST_F(SymbolFilePDBTests, TestFunctionTypes) {
  // In order to get this test working, we need to support lookup by symbol
  // name.  Because array
  // types themselves do not have names, only the symbols have names (i.e. the
  // name of the array).
}

TEST_F(SymbolFilePDBTests, TestTypedefs) {
  FileSpec fspec(m_types_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFilePDB *symfile =
      static_cast<SymbolFilePDB *>(module->GetSymbolFile());
  llvm::pdb::IPDBSession &session = symfile->GetPDBSession();

  const char *TypedefsToCheck[] = {"ClassTypedef", "NSClassTypedef",
                                   "FuncPointerTypedef",
                                   "VariadicFuncPointerTypedef"};
  for (auto Typedef : TypedefsToCheck) {
    TypeResults query_results;
    symfile->FindTypes(TypeQuery(Typedef), query_results);
    TypeMap &results = query_results.GetTypeMap();
    EXPECT_EQ(1u, results.GetSize());
    lldb::TypeSP typedef_type = results.GetTypeAtIndex(0);
    EXPECT_EQ(ConstString(Typedef), typedef_type->GetName());
    CompilerType compiler_type = typedef_type->GetFullCompilerType();
    auto clang_type_system =
        compiler_type.GetTypeSystem().dyn_cast_or_null<TypeSystemClang>();
    EXPECT_TRUE(
        clang_type_system->IsTypedefType(compiler_type.GetOpaqueQualType()));

    std::string sizeof_var = "sizeof_";
    sizeof_var.append(Typedef);
    EXPECT_EQ(GetGlobalConstantInteger(session, sizeof_var),
              typedef_type->GetByteSize(nullptr));
  }
}

TEST_F(SymbolFilePDBTests, TestRegexNameMatch) {
  FileSpec fspec(m_types_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFilePDB *symfile =
      static_cast<SymbolFilePDB *>(module->GetSymbolFile());
  TypeMap results;

  symfile->FindTypesByRegex(RegularExpression(".*"), 0, results);
  EXPECT_GT(results.GetSize(), 1u);

  // We expect no exception thrown if the given regex can't be compiled
  results.Clear();
  symfile->FindTypesByRegex(RegularExpression("**"), 0, results);
  EXPECT_EQ(0u, results.GetSize());
}

TEST_F(SymbolFilePDBTests, TestMaxMatches) {
  FileSpec fspec(m_types_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFilePDB *symfile =
      static_cast<SymbolFilePDB *>(module->GetSymbolFile());

  // Make a type query object we can use for all types and for one type
  TypeQuery query("NestedClass");
  {
    // Find all types that match
    TypeResults query_results;
    symfile->FindTypes(query, query_results);
    TypeMap &results = query_results.GetTypeMap();
    // We expect to find Class::NestedClass and ClassTypedef::NestedClass.
    EXPECT_EQ(results.GetSize(), 2u);
  }
  {
    // Find a single type that matches
    query.SetFindOne(true);
    TypeResults query_results;
    symfile->FindTypes(query, query_results);
    TypeMap &results = query_results.GetTypeMap();
    EXPECT_EQ(results.GetSize(), 1u);
  }
}

TEST_F(SymbolFilePDBTests, TestNullName) {
  FileSpec fspec(m_types_test_exe);
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolFilePDB *symfile =
      static_cast<SymbolFilePDB *>(module->GetSymbolFile());

  TypeResults query_results;
  symfile->FindTypes(TypeQuery(llvm::StringRef()), query_results);
  TypeMap &results = query_results.GetTypeMap();
  EXPECT_EQ(0u, results.GetSize());
}

TEST_F(SymbolFilePDBTests, TestFindSymbolsWithNameAndType) {
  FileSpec fspec(m_pdb_test_exe.c_str());
  ArchSpec aspec("i686-pc-windows");
  lldb::ModuleSP module = std::make_shared<Module>(fspec, aspec);

  SymbolContextList sc_list;
  module->FindSymbolsWithNameAndType(ConstString("?foo@@YAHH@Z"),
                                     lldb::eSymbolTypeAny, sc_list);
  EXPECT_EQ(1u, sc_list.GetSize());

  SymbolContext sc;
  EXPECT_TRUE(sc_list.GetContextAtIndex(0, sc));
  EXPECT_STREQ("int foo(int)",
               sc.GetFunctionName(Mangled::ePreferDemangled).AsCString());
}
