diff options
Diffstat (limited to 'clang-tools-extra/unittests/clangd')
52 files changed, 0 insertions, 16901 deletions
diff --git a/clang-tools-extra/unittests/clangd/Annotations.cpp b/clang-tools-extra/unittests/clangd/Annotations.cpp deleted file mode 100644 index edb0ea9a3af..00000000000 --- a/clang-tools-extra/unittests/clangd/Annotations.cpp +++ /dev/null @@ -1,53 +0,0 @@ -//===--- Annotations.cpp - Annotated source code for unit tests --*- C++-*-===// -// -// 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 "Annotations.h" -#include "SourceCode.h" - -namespace clang { -namespace clangd { - -Position Annotations::point(llvm::StringRef Name) const { - return offsetToPosition(code(), Base::point(Name)); -} - -std::vector<Position> Annotations::points(llvm::StringRef Name) const { - auto Offsets = Base::points(Name); - - std::vector<Position> Ps; - Ps.reserve(Offsets.size()); - for (size_t O : Offsets) - Ps.push_back(offsetToPosition(code(), O)); - - return Ps; -} - -static clangd::Range toLSPRange(llvm::StringRef Code, Annotations::Range R) { - clangd::Range LSPRange; - LSPRange.start = offsetToPosition(Code, R.Begin); - LSPRange.end = offsetToPosition(Code, R.End); - return LSPRange; -} - -clangd::Range Annotations::range(llvm::StringRef Name) const { - return toLSPRange(code(), Base::range(Name)); -} - -std::vector<clangd::Range> Annotations::ranges(llvm::StringRef Name) const { - auto OffsetRanges = Base::ranges(Name); - - std::vector<clangd::Range> Rs; - Rs.reserve(OffsetRanges.size()); - for (Annotations::Range R : OffsetRanges) - Rs.push_back(toLSPRange(code(), R)); - - return Rs; -} - -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/Annotations.h b/clang-tools-extra/unittests/clangd/Annotations.h deleted file mode 100644 index 846c36a5b42..00000000000 --- a/clang-tools-extra/unittests/clangd/Annotations.h +++ /dev/null @@ -1,39 +0,0 @@ -//===--- Annotations.h - Annotated source code for tests ---------*- C++-*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// -// A clangd-specific version of llvm/Testing/Support/Annotations.h, replaces -// offsets and offset-based ranges with types from the LSP protocol. -//===---------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_ANNOTATIONS_H -#define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_ANNOTATIONS_H - -#include "Protocol.h" -#include "llvm/Testing/Support/Annotations.h" - -namespace clang { -namespace clangd { - -/// Same as llvm::Annotations, but adjusts functions to LSP-specific types for -/// positions and ranges. -class Annotations : public llvm::Annotations { - using Base = llvm::Annotations; - -public: - using llvm::Annotations::Annotations; - - Position point(llvm::StringRef Name = "") const; - std::vector<Position> points(llvm::StringRef Name = "") const; - - clangd::Range range(llvm::StringRef Name = "") const; - std::vector<clangd::Range> ranges(llvm::StringRef Name = "") const; -}; - -} // namespace clangd -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_ANNOTATIONS_H diff --git a/clang-tools-extra/unittests/clangd/BackgroundIndexTests.cpp b/clang-tools-extra/unittests/clangd/BackgroundIndexTests.cpp deleted file mode 100644 index 91eb6dec28c..00000000000 --- a/clang-tools-extra/unittests/clangd/BackgroundIndexTests.cpp +++ /dev/null @@ -1,465 +0,0 @@ -#include "SyncAPI.h" -#include "TestFS.h" -#include "index/Background.h" -#include "llvm/Support/ScopedPrinter.h" -#include "llvm/Support/Threading.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include <thread> - -using testing::_; -using testing::AllOf; -using testing::Contains; -using testing::ElementsAre; -using testing::Not; -using testing::UnorderedElementsAre; - -namespace clang { -namespace clangd { - -MATCHER_P(Named, N, "") { return arg.Name == N; } -MATCHER(Declared, "") { - return !StringRef(arg.CanonicalDeclaration.FileURI).empty(); -} -MATCHER(Defined, "") { return !StringRef(arg.Definition.FileURI).empty(); } -MATCHER_P(FileURI, F, "") { return StringRef(arg.Location.FileURI) == F; } -testing::Matcher<const RefSlab &> -RefsAre(std::vector<testing::Matcher<Ref>> Matchers) { - return ElementsAre(testing::Pair(_, UnorderedElementsAreArray(Matchers))); -} -// URI cannot be empty since it references keys in the IncludeGraph. -MATCHER(EmptyIncludeNode, "") { - return !arg.IsTU && !arg.URI.empty() && arg.Digest == FileDigest{{0}} && - arg.DirectIncludes.empty(); -} - -class MemoryShardStorage : public BackgroundIndexStorage { - mutable std::mutex StorageMu; - llvm::StringMap<std::string> &Storage; - size_t &CacheHits; - -public: - MemoryShardStorage(llvm::StringMap<std::string> &Storage, size_t &CacheHits) - : Storage(Storage), CacheHits(CacheHits) {} - llvm::Error storeShard(llvm::StringRef ShardIdentifier, - IndexFileOut Shard) const override { - std::lock_guard<std::mutex> Lock(StorageMu); - AccessedPaths.insert(ShardIdentifier); - Storage[ShardIdentifier] = llvm::to_string(Shard); - return llvm::Error::success(); - } - std::unique_ptr<IndexFileIn> - loadShard(llvm::StringRef ShardIdentifier) const override { - std::lock_guard<std::mutex> Lock(StorageMu); - AccessedPaths.insert(ShardIdentifier); - if (Storage.find(ShardIdentifier) == Storage.end()) { - return nullptr; - } - auto IndexFile = readIndexFile(Storage[ShardIdentifier]); - if (!IndexFile) { - ADD_FAILURE() << "Error while reading " << ShardIdentifier << ':' - << IndexFile.takeError(); - return nullptr; - } - CacheHits++; - return llvm::make_unique<IndexFileIn>(std::move(*IndexFile)); - } - - mutable llvm::StringSet<> AccessedPaths; -}; - -class BackgroundIndexTest : public ::testing::Test { -protected: - BackgroundIndexTest() { BackgroundIndex::preventThreadStarvationInTests(); } -}; - -TEST_F(BackgroundIndexTest, NoCrashOnErrorFile) { - MockFSProvider FS; - FS.Files[testPath("root/A.cc")] = "error file"; - llvm::StringMap<std::string> Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - - tooling::CompileCommand Cmd; - Cmd.Filename = testPath("root/A.cc"); - Cmd.Directory = testPath("root"); - Cmd.CommandLine = {"clang++", "-DA=1", testPath("root/A.cc")}; - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - - ASSERT_TRUE(Idx.blockUntilIdleForTest()); -} - -TEST_F(BackgroundIndexTest, IndexTwoFiles) { - MockFSProvider FS; - // a.h yields different symbols when included by A.cc vs B.cc. - FS.Files[testPath("root/A.h")] = R"cpp( - void common(); - void f_b(); - #if A - class A_CC {}; - #else - class B_CC{}; - #endif - )cpp"; - FS.Files[testPath("root/A.cc")] = - "#include \"A.h\"\nvoid g() { (void)common; }"; - FS.Files[testPath("root/B.cc")] = - R"cpp( - #define A 0 - #include "A.h" - void f_b() { - (void)common; - })cpp"; - llvm::StringMap<std::string> Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - - tooling::CompileCommand Cmd; - Cmd.Filename = testPath("root/A.cc"); - Cmd.Directory = testPath("root"); - Cmd.CommandLine = {"clang++", "-DA=1", testPath("root/A.cc")}; - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - EXPECT_THAT( - runFuzzyFind(Idx, ""), - UnorderedElementsAre(Named("common"), Named("A_CC"), Named("g"), - AllOf(Named("f_b"), Declared(), Not(Defined())))); - - Cmd.Filename = testPath("root/B.cc"); - Cmd.CommandLine = {"clang++", Cmd.Filename}; - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - // B_CC is dropped as we don't collect symbols from A.h in this compilation. - EXPECT_THAT(runFuzzyFind(Idx, ""), - UnorderedElementsAre(Named("common"), Named("A_CC"), Named("g"), - AllOf(Named("f_b"), Declared(), Defined()))); - - auto Syms = runFuzzyFind(Idx, "common"); - EXPECT_THAT(Syms, UnorderedElementsAre(Named("common"))); - auto Common = *Syms.begin(); - EXPECT_THAT(getRefs(Idx, Common.ID), - RefsAre({FileURI("unittest:///root/A.h"), - FileURI("unittest:///root/A.cc"), - FileURI("unittest:///root/B.cc")})); -} - -TEST_F(BackgroundIndexTest, ShardStorageTest) { - MockFSProvider FS; - FS.Files[testPath("root/A.h")] = R"cpp( - void common(); - void f_b(); - class A_CC {}; - )cpp"; - std::string A_CC = "#include \"A.h\"\nvoid g() { (void)common; }"; - FS.Files[testPath("root/A.cc")] = A_CC; - - llvm::StringMap<std::string> Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - - tooling::CompileCommand Cmd; - Cmd.Filename = testPath("root/A.cc"); - Cmd.Directory = testPath("root"); - Cmd.CommandLine = {"clang++", testPath("root/A.cc")}; - // Check nothing is loaded from Storage, but A.cc and A.h has been stored. - { - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - EXPECT_EQ(CacheHits, 0U); - EXPECT_EQ(Storage.size(), 2U); - - { - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - EXPECT_EQ(CacheHits, 2U); // Check both A.cc and A.h loaded from cache. - EXPECT_EQ(Storage.size(), 2U); - - auto ShardHeader = MSS.loadShard(testPath("root/A.h")); - EXPECT_NE(ShardHeader, nullptr); - EXPECT_THAT( - *ShardHeader->Symbols, - UnorderedElementsAre(Named("common"), Named("A_CC"), - AllOf(Named("f_b"), Declared(), Not(Defined())))); - for (const auto &Ref : *ShardHeader->Refs) - EXPECT_THAT(Ref.second, - UnorderedElementsAre(FileURI("unittest:///root/A.h"))); - - auto ShardSource = MSS.loadShard(testPath("root/A.cc")); - EXPECT_NE(ShardSource, nullptr); - EXPECT_THAT(*ShardSource->Symbols, UnorderedElementsAre(Named("g"))); - EXPECT_THAT(*ShardSource->Refs, RefsAre({FileURI("unittest:///root/A.cc")})); -} - -TEST_F(BackgroundIndexTest, DirectIncludesTest) { - MockFSProvider FS; - FS.Files[testPath("root/B.h")] = ""; - FS.Files[testPath("root/A.h")] = R"cpp( - #include "B.h" - void common(); - void f_b(); - class A_CC {}; - )cpp"; - std::string A_CC = "#include \"A.h\"\nvoid g() { (void)common; }"; - FS.Files[testPath("root/A.cc")] = A_CC; - - llvm::StringMap<std::string> Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - - tooling::CompileCommand Cmd; - Cmd.Filename = testPath("root/A.cc"); - Cmd.Directory = testPath("root"); - Cmd.CommandLine = {"clang++", testPath("root/A.cc")}; - { - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - - auto ShardSource = MSS.loadShard(testPath("root/A.cc")); - EXPECT_TRUE(ShardSource->Sources); - EXPECT_EQ(ShardSource->Sources->size(), 2U); // A.cc, A.h - EXPECT_THAT( - ShardSource->Sources->lookup("unittest:///root/A.cc").DirectIncludes, - UnorderedElementsAre("unittest:///root/A.h")); - EXPECT_NE(ShardSource->Sources->lookup("unittest:///root/A.cc").Digest, - FileDigest{{0}}); - EXPECT_THAT(ShardSource->Sources->lookup("unittest:///root/A.h"), - EmptyIncludeNode()); - - auto ShardHeader = MSS.loadShard(testPath("root/A.h")); - EXPECT_TRUE(ShardHeader->Sources); - EXPECT_EQ(ShardHeader->Sources->size(), 2U); // A.h, B.h - EXPECT_THAT( - ShardHeader->Sources->lookup("unittest:///root/A.h").DirectIncludes, - UnorderedElementsAre("unittest:///root/B.h")); - EXPECT_NE(ShardHeader->Sources->lookup("unittest:///root/A.h").Digest, - FileDigest{{0}}); - EXPECT_THAT(ShardHeader->Sources->lookup("unittest:///root/B.h"), - EmptyIncludeNode()); -} - -// FIXME: figure out the right timeouts or rewrite to not use the timeouts and -// re-enable. -TEST_F(BackgroundIndexTest, DISABLED_PeriodicalIndex) { - MockFSProvider FS; - llvm::StringMap<std::string> Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx( - Context::empty(), FS, CDB, [&](llvm::StringRef) { return &MSS; }, - /*BuildIndexPeriodMs=*/500); - - FS.Files[testPath("root/A.cc")] = "#include \"A.h\""; - - tooling::CompileCommand Cmd; - FS.Files[testPath("root/A.h")] = "class X {};"; - Cmd.Filename = testPath("root/A.cc"); - Cmd.CommandLine = {"clang++", Cmd.Filename}; - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - EXPECT_THAT(runFuzzyFind(Idx, ""), ElementsAre()); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - EXPECT_THAT(runFuzzyFind(Idx, ""), ElementsAre(Named("X"))); - - FS.Files[testPath("root/A.h")] = "class Y {};"; - FS.Files[testPath("root/A.cc")] += " "; // Force reindex the file. - Cmd.CommandLine = {"clang++", "-DA=1", testPath("root/A.cc")}; - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - EXPECT_THAT(runFuzzyFind(Idx, ""), ElementsAre(Named("X"))); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - EXPECT_THAT(runFuzzyFind(Idx, ""), ElementsAre(Named("Y"))); -} - -TEST_F(BackgroundIndexTest, ShardStorageLoad) { - MockFSProvider FS; - FS.Files[testPath("root/A.h")] = R"cpp( - void common(); - void f_b(); - class A_CC {}; - )cpp"; - FS.Files[testPath("root/A.cc")] = - "#include \"A.h\"\nvoid g() { (void)common; }"; - - llvm::StringMap<std::string> Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - - tooling::CompileCommand Cmd; - Cmd.Filename = testPath("root/A.cc"); - Cmd.Directory = testPath("root"); - Cmd.CommandLine = {"clang++", testPath("root/A.cc")}; - // Check nothing is loaded from Storage, but A.cc and A.h has been stored. - { - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - - // Change header. - FS.Files[testPath("root/A.h")] = R"cpp( - void common(); - void f_b(); - class A_CC {}; - class A_CCnew {}; - )cpp"; - { - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - EXPECT_EQ(CacheHits, 2U); // Check both A.cc and A.h loaded from cache. - - // Check if the new symbol has arrived. - auto ShardHeader = MSS.loadShard(testPath("root/A.h")); - EXPECT_NE(ShardHeader, nullptr); - EXPECT_THAT(*ShardHeader->Symbols, Contains(Named("A_CCnew"))); - - // Change source. - FS.Files[testPath("root/A.cc")] = - "#include \"A.h\"\nvoid g() { (void)common; }\nvoid f_b() {}"; - { - CacheHits = 0; - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - EXPECT_EQ(CacheHits, 2U); // Check both A.cc and A.h loaded from cache. - - // Check if the new symbol has arrived. - ShardHeader = MSS.loadShard(testPath("root/A.h")); - EXPECT_NE(ShardHeader, nullptr); - EXPECT_THAT(*ShardHeader->Symbols, Contains(Named("A_CCnew"))); - auto ShardSource = MSS.loadShard(testPath("root/A.cc")); - EXPECT_NE(ShardSource, nullptr); - EXPECT_THAT(*ShardSource->Symbols, - Contains(AllOf(Named("f_b"), Declared(), Defined()))); -} - -TEST_F(BackgroundIndexTest, ShardStorageEmptyFile) { - MockFSProvider FS; - FS.Files[testPath("root/A.h")] = R"cpp( - void common(); - void f_b(); - class A_CC {}; - )cpp"; - FS.Files[testPath("root/B.h")] = R"cpp( - #include "A.h" - )cpp"; - FS.Files[testPath("root/A.cc")] = - "#include \"B.h\"\nvoid g() { (void)common; }"; - - llvm::StringMap<std::string> Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - - tooling::CompileCommand Cmd; - Cmd.Filename = testPath("root/A.cc"); - Cmd.Directory = testPath("root"); - Cmd.CommandLine = {"clang++", testPath("root/A.cc")}; - // Check that A.cc, A.h and B.h has been stored. - { - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - EXPECT_THAT(Storage.keys(), - UnorderedElementsAre(testPath("root/A.cc"), testPath("root/A.h"), - testPath("root/B.h"))); - auto ShardHeader = MSS.loadShard(testPath("root/B.h")); - EXPECT_NE(ShardHeader, nullptr); - EXPECT_TRUE(ShardHeader->Symbols->empty()); - - // Check that A.cc, A.h and B.h has been loaded. - { - CacheHits = 0; - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - EXPECT_EQ(CacheHits, 3U); - - // Update B.h to contain some symbols. - FS.Files[testPath("root/B.h")] = R"cpp( - #include "A.h" - void new_func(); - )cpp"; - // Check that B.h has been stored with new contents. - { - CacheHits = 0; - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - EXPECT_EQ(CacheHits, 3U); - ShardHeader = MSS.loadShard(testPath("root/B.h")); - EXPECT_NE(ShardHeader, nullptr); - EXPECT_THAT(*ShardHeader->Symbols, - Contains(AllOf(Named("new_func"), Declared(), Not(Defined())))); -} - -TEST_F(BackgroundIndexTest, NoDotsInAbsPath) { - MockFSProvider FS; - llvm::StringMap<std::string> Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - - tooling::CompileCommand Cmd; - FS.Files[testPath("root/A.cc")] = ""; - Cmd.Filename = "../A.cc"; - Cmd.Directory = testPath("root/build"); - Cmd.CommandLine = {"clang++", "../A.cc"}; - CDB.setCompileCommand(testPath("root/build/../A.cc"), Cmd); - - FS.Files[testPath("root/B.cc")] = ""; - Cmd.Filename = "./B.cc"; - Cmd.Directory = testPath("root"); - Cmd.CommandLine = {"clang++", "./B.cc"}; - CDB.setCompileCommand(testPath("root/./B.cc"), Cmd); - - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - for (llvm::StringRef AbsPath : MSS.AccessedPaths.keys()) { - EXPECT_FALSE(AbsPath.contains("./")) << AbsPath; - EXPECT_FALSE(AbsPath.contains("../")) << AbsPath; - } -} - -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/CMakeLists.txt b/clang-tools-extra/unittests/clangd/CMakeLists.txt deleted file mode 100644 index 61a720ddd57..00000000000 --- a/clang-tools-extra/unittests/clangd/CMakeLists.txt +++ /dev/null @@ -1,82 +0,0 @@ -set(LLVM_LINK_COMPONENTS - support - ) - -get_filename_component(CLANGD_SOURCE_DIR - ${CMAKE_CURRENT_SOURCE_DIR}/../../clangd REALPATH) -get_filename_component(CLANGD_BINARY_DIR - ${CMAKE_CURRENT_BINARY_DIR}/../../clangd REALPATH) -include_directories( - ${CLANGD_SOURCE_DIR} - ${CLANGD_BINARY_DIR} - ) - -add_extra_unittest(ClangdTests - Annotations.cpp - BackgroundIndexTests.cpp - CancellationTests.cpp - ClangdTests.cpp - ClangdUnitTests.cpp - CodeCompleteTests.cpp - CodeCompletionStringsTests.cpp - ContextTests.cpp - DexTests.cpp - DiagnosticsTests.cpp - DraftStoreTests.cpp - ExpectedTypeTest.cpp - FileDistanceTests.cpp - FileIndexTests.cpp - FindSymbolsTests.cpp - FSTests.cpp - FunctionTests.cpp - FuzzyMatchTests.cpp - GlobalCompilationDatabaseTests.cpp - HeadersTests.cpp - IndexActionTests.cpp - IndexTests.cpp - JSONTransportTests.cpp - PrintASTTests.cpp - QualityTests.cpp - RIFFTests.cpp - SelectionTests.cpp - SerializationTests.cpp - SourceCodeTests.cpp - SymbolCollectorTests.cpp - SymbolInfoTests.cpp - SyncAPI.cpp - TUSchedulerTests.cpp - TestFS.cpp - TestIndex.cpp - TestTU.cpp - ThreadingTests.cpp - TraceTests.cpp - TypeHierarchyTests.cpp - TweakTests.cpp - URITests.cpp - XRefsTests.cpp - - $<TARGET_OBJECTS:obj.clangDaemonTweaks> - ) - -target_link_libraries(ClangdTests - PRIVATE - clangAST - clangBasic - clangDaemon - clangFormat - clangFrontend - clangIndex - clangLex - clangSema - clangSerialization - clangTidy - clangTooling - clangToolingCore - clangToolingInclusions - LLVMSupport - LLVMTestingSupport - ) - -if (CLANGD_BUILD_XPC) - add_subdirectory(xpc) -endif () diff --git a/clang-tools-extra/unittests/clangd/CancellationTests.cpp b/clang-tools-extra/unittests/clangd/CancellationTests.cpp deleted file mode 100644 index 611ce07dd8e..00000000000 --- a/clang-tools-extra/unittests/clangd/CancellationTests.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "Cancellation.h" -#include "Context.h" -#include "Threading.h" -#include "llvm/Support/Error.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include <atomic> -#include <memory> -#include <thread> - -namespace clang { -namespace clangd { -namespace { - -TEST(CancellationTest, CancellationTest) { - auto Task = cancelableTask(); - WithContext ContextWithCancellation(std::move(Task.first)); - EXPECT_FALSE(isCancelled()); - Task.second(); - EXPECT_TRUE(isCancelled()); -} - -TEST(CancellationTest, CancelerDiesContextLives) { - llvm::Optional<WithContext> ContextWithCancellation; - { - auto Task = cancelableTask(); - ContextWithCancellation.emplace(std::move(Task.first)); - EXPECT_FALSE(isCancelled()); - Task.second(); - EXPECT_TRUE(isCancelled()); - } - EXPECT_TRUE(isCancelled()); -} - -TEST(CancellationTest, TaskContextDiesHandleLives) { - auto Task = cancelableTask(); - { - WithContext ContextWithCancellation(std::move(Task.first)); - EXPECT_FALSE(isCancelled()); - Task.second(); - EXPECT_TRUE(isCancelled()); - } - // Still should be able to cancel without any problems. - Task.second(); -} - -TEST(CancellationTest, AsynCancellationTest) { - std::atomic<bool> HasCancelled(false); - Notification Cancelled; - auto TaskToBeCancelled = [&](Context Ctx) { - WithContext ContextGuard(std::move(Ctx)); - Cancelled.wait(); - HasCancelled = isCancelled(); - }; - auto Task = cancelableTask(); - std::thread AsyncTask(TaskToBeCancelled, std::move(Task.first)); - Task.second(); - Cancelled.notify(); - AsyncTask.join(); - - EXPECT_TRUE(HasCancelled); -} -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/ClangdTests.cpp b/clang-tools-extra/unittests/clangd/ClangdTests.cpp deleted file mode 100644 index 5d98bdc251c..00000000000 --- a/clang-tools-extra/unittests/clangd/ClangdTests.cpp +++ /dev/null @@ -1,1162 +0,0 @@ -//===-- ClangdTests.cpp - Clangd unit tests ---------------------*- C++ -*-===// -// -// 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 "Annotations.h" -#include "ClangdLSPServer.h" -#include "ClangdServer.h" -#include "GlobalCompilationDatabase.h" -#include "Matchers.h" -#include "SyncAPI.h" -#include "TestFS.h" -#include "Threading.h" -#include "URI.h" -#include "clang/Config/config.h" -#include "clang/Sema/CodeCompleteConsumer.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringMap.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/Regex.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include <algorithm> -#include <chrono> -#include <iostream> -#include <random> -#include <string> -#include <thread> -#include <vector> - -namespace clang { -namespace clangd { - -namespace { - -using ::testing::ElementsAre; -using ::testing::Field; -using ::testing::Gt; -using ::testing::IsEmpty; -using ::testing::Pair; -using ::testing::UnorderedElementsAre; - -MATCHER_P2(DeclAt, File, Range, "") { - return arg.PreferredDeclaration == - Location{URIForFile::canonicalize(File, testRoot()), Range}; -} - -bool diagsContainErrors(const std::vector<Diag> &Diagnostics) { - for (auto D : Diagnostics) { - if (D.Severity == DiagnosticsEngine::Error || - D.Severity == DiagnosticsEngine::Fatal) - return true; - } - return false; -} - -class ErrorCheckingDiagConsumer : public DiagnosticsConsumer { -public: - void onDiagnosticsReady(PathRef File, - std::vector<Diag> Diagnostics) override { - bool HadError = diagsContainErrors(Diagnostics); - std::lock_guard<std::mutex> Lock(Mutex); - HadErrorInLastDiags = HadError; - } - - bool hadErrorInLastDiags() { - std::lock_guard<std::mutex> Lock(Mutex); - return HadErrorInLastDiags; - } - -private: - std::mutex Mutex; - bool HadErrorInLastDiags = false; -}; - -/// For each file, record whether the last published diagnostics contained at -/// least one error. -class MultipleErrorCheckingDiagConsumer : public DiagnosticsConsumer { -public: - void onDiagnosticsReady(PathRef File, - std::vector<Diag> Diagnostics) override { - bool HadError = diagsContainErrors(Diagnostics); - - std::lock_guard<std::mutex> Lock(Mutex); - LastDiagsHadError[File] = HadError; - } - - /// Exposes all files consumed by onDiagnosticsReady in an unspecified order. - /// For each file, a bool value indicates whether the last diagnostics - /// contained an error. - std::vector<std::pair<Path, bool>> filesWithDiags() const { - std::vector<std::pair<Path, bool>> Result; - std::lock_guard<std::mutex> Lock(Mutex); - - for (const auto &it : LastDiagsHadError) { - Result.emplace_back(it.first(), it.second); - } - - return Result; - } - - void clear() { - std::lock_guard<std::mutex> Lock(Mutex); - LastDiagsHadError.clear(); - } - -private: - mutable std::mutex Mutex; - llvm::StringMap<bool> LastDiagsHadError; -}; - -/// Replaces all patterns of the form 0x123abc with spaces -std::string replacePtrsInDump(std::string const &Dump) { - llvm::Regex RE("0x[0-9a-fA-F]+"); - llvm::SmallVector<llvm::StringRef, 1> Matches; - llvm::StringRef Pending = Dump; - - std::string Result; - while (RE.match(Pending, &Matches)) { - assert(Matches.size() == 1 && "Exactly one match expected"); - auto MatchPos = Matches[0].data() - Pending.data(); - - Result += Pending.take_front(MatchPos); - Pending = Pending.drop_front(MatchPos + Matches[0].size()); - } - Result += Pending; - - return Result; -} - -std::string dumpASTWithoutMemoryLocs(ClangdServer &Server, PathRef File) { - auto DumpWithMemLocs = runDumpAST(Server, File); - return replacePtrsInDump(DumpWithMemLocs); -} - -class ClangdVFSTest : public ::testing::Test { -protected: - std::string parseSourceAndDumpAST( - PathRef SourceFileRelPath, llvm::StringRef SourceContents, - std::vector<std::pair<PathRef, llvm::StringRef>> ExtraFiles = {}, - bool ExpectErrors = false) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - for (const auto &FileWithContents : ExtraFiles) - FS.Files[testPath(FileWithContents.first)] = FileWithContents.second; - - auto SourceFilename = testPath(SourceFileRelPath); - Server.addDocument(SourceFilename, SourceContents); - auto Result = dumpASTWithoutMemoryLocs(Server, SourceFilename); - EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_EQ(ExpectErrors, DiagConsumer.hadErrorInLastDiags()); - return Result; - } -}; - -TEST_F(ClangdVFSTest, Parse) { - // FIXME: figure out a stable format for AST dumps, so that we can check the - // output of the dump itself is equal to the expected one, not just that it's - // different. - auto Empty = parseSourceAndDumpAST("foo.cpp", "", {}); - auto OneDecl = parseSourceAndDumpAST("foo.cpp", "int a;", {}); - auto SomeDecls = parseSourceAndDumpAST("foo.cpp", "int a; int b; int c;", {}); - EXPECT_NE(Empty, OneDecl); - EXPECT_NE(Empty, SomeDecls); - EXPECT_NE(SomeDecls, OneDecl); - - auto Empty2 = parseSourceAndDumpAST("foo.cpp", ""); - auto OneDecl2 = parseSourceAndDumpAST("foo.cpp", "int a;"); - auto SomeDecls2 = parseSourceAndDumpAST("foo.cpp", "int a; int b; int c;"); - EXPECT_EQ(Empty, Empty2); - EXPECT_EQ(OneDecl, OneDecl2); - EXPECT_EQ(SomeDecls, SomeDecls2); -} - -TEST_F(ClangdVFSTest, ParseWithHeader) { - parseSourceAndDumpAST("foo.cpp", "#include \"foo.h\"", {}, - /*ExpectErrors=*/true); - parseSourceAndDumpAST("foo.cpp", "#include \"foo.h\"", {{"foo.h", ""}}, - /*ExpectErrors=*/false); - - const auto SourceContents = R"cpp( -#include "foo.h" -int b = a; -)cpp"; - parseSourceAndDumpAST("foo.cpp", SourceContents, {{"foo.h", ""}}, - /*ExpectErrors=*/true); - parseSourceAndDumpAST("foo.cpp", SourceContents, {{"foo.h", "int a;"}}, - /*ExpectErrors=*/false); -} - -TEST_F(ClangdVFSTest, Reparse) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - const auto SourceContents = R"cpp( -#include "foo.h" -int b = a; -)cpp"; - - auto FooCpp = testPath("foo.cpp"); - - FS.Files[testPath("foo.h")] = "int a;"; - FS.Files[FooCpp] = SourceContents; - - Server.addDocument(FooCpp, SourceContents); - auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - - Server.addDocument(FooCpp, ""); - auto DumpParseEmpty = dumpASTWithoutMemoryLocs(Server, FooCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - - Server.addDocument(FooCpp, SourceContents); - auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - - EXPECT_EQ(DumpParse1, DumpParse2); - EXPECT_NE(DumpParse1, DumpParseEmpty); -} - -TEST_F(ClangdVFSTest, ReparseOnHeaderChange) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - const auto SourceContents = R"cpp( -#include "foo.h" -int b = a; -)cpp"; - - auto FooCpp = testPath("foo.cpp"); - auto FooH = testPath("foo.h"); - - FS.Files[FooH] = "int a;"; - FS.Files[FooCpp] = SourceContents; - - Server.addDocument(FooCpp, SourceContents); - auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - - FS.Files[FooH] = ""; - Server.addDocument(FooCpp, SourceContents); - auto DumpParseDifferent = dumpASTWithoutMemoryLocs(Server, FooCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); - - FS.Files[FooH] = "int a;"; - Server.addDocument(FooCpp, SourceContents); - auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - - EXPECT_EQ(DumpParse1, DumpParse2); - EXPECT_NE(DumpParse1, DumpParseDifferent); -} - -TEST_F(ClangdVFSTest, PropagatesContexts) { - static Key<int> Secret; - struct FSProvider : public FileSystemProvider { - IntrusiveRefCntPtr<llvm::vfs::FileSystem> getFileSystem() const override { - Got = Context::current().getExisting(Secret); - return buildTestFS({}); - } - mutable int Got; - } FS; - struct DiagConsumer : public DiagnosticsConsumer { - void onDiagnosticsReady(PathRef File, - std::vector<Diag> Diagnostics) override { - Got = Context::current().getExisting(Secret); - } - int Got; - } DiagConsumer; - MockCompilationDatabase CDB; - - // Verify that the context is plumbed to the FS provider and diagnostics. - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - { - WithContextValue Entrypoint(Secret, 42); - Server.addDocument(testPath("foo.cpp"), "void main(){}"); - } - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_EQ(FS.Got, 42); - EXPECT_EQ(DiagConsumer.Got, 42); -} - -// Only enable this test on Unix -#ifdef LLVM_ON_UNIX -TEST_F(ClangdVFSTest, SearchLibDir) { - // Checks that searches for GCC installation is done through vfs. - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - CDB.ExtraClangFlags.insert(CDB.ExtraClangFlags.end(), - {"-xc++", "-target", "x86_64-linux-unknown", - "-m64", "--gcc-toolchain=/randomusr", - "-stdlib=libstdc++"}); - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - // Just a random gcc version string - SmallString<8> Version("4.9.3"); - - // A lib dir for gcc installation - SmallString<64> LibDir("/randomusr/lib/gcc/x86_64-linux-gnu"); - llvm::sys::path::append(LibDir, Version); - - // Put crtbegin.o into LibDir/64 to trick clang into thinking there's a gcc - // installation there. - SmallString<64> DummyLibFile; - llvm::sys::path::append(DummyLibFile, LibDir, "64", "crtbegin.o"); - FS.Files[DummyLibFile] = ""; - - SmallString<64> IncludeDir("/randomusr/include/c++"); - llvm::sys::path::append(IncludeDir, Version); - - SmallString<64> StringPath; - llvm::sys::path::append(StringPath, IncludeDir, "string"); - FS.Files[StringPath] = "class mock_string {};"; - - auto FooCpp = testPath("foo.cpp"); - const auto SourceContents = R"cpp( -#include <string> -mock_string x; -)cpp"; - FS.Files[FooCpp] = SourceContents; - - runAddDocument(Server, FooCpp, SourceContents); - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - - const auto SourceContentsWithError = R"cpp( -#include <string> -std::string x; -)cpp"; - runAddDocument(Server, FooCpp, SourceContentsWithError); - EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); -} -#endif // LLVM_ON_UNIX - -TEST_F(ClangdVFSTest, ForceReparseCompileCommand) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - const auto SourceContents1 = R"cpp( -template <class T> -struct foo { T x; }; -)cpp"; - const auto SourceContents2 = R"cpp( -template <class T> -struct bar { T x; }; -)cpp"; - - FS.Files[FooCpp] = ""; - - // First parse files in C mode and check they produce errors. - CDB.ExtraClangFlags = {"-xc"}; - runAddDocument(Server, FooCpp, SourceContents1); - EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); - runAddDocument(Server, FooCpp, SourceContents2); - EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); - - // Now switch to C++ mode. - CDB.ExtraClangFlags = {"-xc++"}; - runAddDocument(Server, FooCpp, SourceContents2, WantDiagnostics::Auto); - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - // Subsequent addDocument calls should finish without errors too. - runAddDocument(Server, FooCpp, SourceContents1); - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - runAddDocument(Server, FooCpp, SourceContents2); - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); -} - -TEST_F(ClangdVFSTest, ForceReparseCompileCommandDefines) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - const auto SourceContents = R"cpp( -#ifdef WITH_ERROR -this -#endif - -int main() { return 0; } -)cpp"; - FS.Files[FooCpp] = ""; - - // Parse with define, we expect to see the errors. - CDB.ExtraClangFlags = {"-DWITH_ERROR"}; - runAddDocument(Server, FooCpp, SourceContents); - EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); - - // Parse without the define, no errors should be produced. - CDB.ExtraClangFlags = {}; - runAddDocument(Server, FooCpp, SourceContents, WantDiagnostics::Auto); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - // Subsequent addDocument call should finish without errors too. - runAddDocument(Server, FooCpp, SourceContents); - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); -} - -// Test ClangdServer.reparseOpenedFiles. -TEST_F(ClangdVFSTest, ReparseOpenedFiles) { - Annotations FooSource(R"cpp( -#ifdef MACRO -static void $one[[bob]]() {} -#else -static void $two[[bob]]() {} -#endif - -int main () { bo^b (); return 0; } -)cpp"); - - Annotations BarSource(R"cpp( -#ifdef MACRO -this is an error -#endif -)cpp"); - - Annotations BazSource(R"cpp( -int hello; -)cpp"); - - MockFSProvider FS; - MockCompilationDatabase CDB; - MultipleErrorCheckingDiagConsumer DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - auto BarCpp = testPath("bar.cpp"); - auto BazCpp = testPath("baz.cpp"); - - FS.Files[FooCpp] = ""; - FS.Files[BarCpp] = ""; - FS.Files[BazCpp] = ""; - - CDB.ExtraClangFlags = {"-DMACRO=1"}; - Server.addDocument(FooCpp, FooSource.code()); - Server.addDocument(BarCpp, BarSource.code()); - Server.addDocument(BazCpp, BazSource.code()); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - - EXPECT_THAT(DiagConsumer.filesWithDiags(), - UnorderedElementsAre(Pair(FooCpp, false), Pair(BarCpp, true), - Pair(BazCpp, false))); - - auto Locations = runLocateSymbolAt(Server, FooCpp, FooSource.point()); - EXPECT_TRUE(bool(Locations)); - EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range("one")))); - - // Undefine MACRO, close baz.cpp. - CDB.ExtraClangFlags.clear(); - DiagConsumer.clear(); - Server.removeDocument(BazCpp); - Server.addDocument(FooCpp, FooSource.code(), WantDiagnostics::Auto); - Server.addDocument(BarCpp, BarSource.code(), WantDiagnostics::Auto); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - - EXPECT_THAT(DiagConsumer.filesWithDiags(), - UnorderedElementsAre(Pair(FooCpp, false), Pair(BarCpp, false))); - - Locations = runLocateSymbolAt(Server, FooCpp, FooSource.point()); - EXPECT_TRUE(bool(Locations)); - EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range("two")))); -} - -TEST_F(ClangdVFSTest, MemoryUsage) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - Path FooCpp = testPath("foo.cpp"); - const auto SourceContents = R"cpp( -struct Something { - int method(); -}; -)cpp"; - Path BarCpp = testPath("bar.cpp"); - - FS.Files[FooCpp] = ""; - FS.Files[BarCpp] = ""; - - EXPECT_THAT(Server.getUsedBytesPerFile(), IsEmpty()); - - Server.addDocument(FooCpp, SourceContents); - Server.addDocument(BarCpp, SourceContents); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - - EXPECT_THAT(Server.getUsedBytesPerFile(), - UnorderedElementsAre(Pair(FooCpp, Gt(0u)), Pair(BarCpp, Gt(0u)))); - - Server.removeDocument(FooCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_THAT(Server.getUsedBytesPerFile(), ElementsAre(Pair(BarCpp, Gt(0u)))); - - Server.removeDocument(BarCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_THAT(Server.getUsedBytesPerFile(), IsEmpty()); -} - -TEST_F(ClangdVFSTest, InvalidCompileCommand) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - // clang cannot create CompilerInvocation if we pass two files in the - // CompileCommand. We pass the file in ExtraFlags once and CDB adds another - // one in getCompileCommand(). - CDB.ExtraClangFlags.push_back(FooCpp); - - // Clang can't parse command args in that case, but we shouldn't crash. - runAddDocument(Server, FooCpp, "int main() {}"); - - EXPECT_EQ(runDumpAST(Server, FooCpp), "<no-ast>"); - EXPECT_ERROR(runLocateSymbolAt(Server, FooCpp, Position())); - EXPECT_ERROR(runFindDocumentHighlights(Server, FooCpp, Position())); - EXPECT_ERROR(runRename(Server, FooCpp, Position(), "new_name")); - // Identifier-based fallback completion. - EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Position(), - clangd::CodeCompleteOptions())) - .Completions, - ElementsAre(Field(&CodeCompletion::Name, "int"), - Field(&CodeCompletion::Name, "main"))); - auto SigHelp = runSignatureHelp(Server, FooCpp, Position()); - ASSERT_TRUE(bool(SigHelp)) << "signatureHelp returned an error"; - EXPECT_THAT(SigHelp->signatures, IsEmpty()); -} - -class ClangdThreadingTest : public ClangdVFSTest {}; - -TEST_F(ClangdThreadingTest, StressTest) { - // Without 'static' clang gives an error for a usage inside TestDiagConsumer. - static const unsigned FilesCount = 5; - const unsigned RequestsCount = 500; - // Blocking requests wait for the parsing to complete, they slow down the test - // dramatically, so they are issued rarely. Each - // BlockingRequestInterval-request will be a blocking one. - const unsigned BlockingRequestInterval = 40; - - const auto SourceContentsWithoutErrors = R"cpp( -int a; -int b; -int c; -int d; -)cpp"; - - const auto SourceContentsWithErrors = R"cpp( -int a = x; -int b; -int c; -int d; -)cpp"; - - // Giving invalid line and column number should not crash ClangdServer, but - // just to make sure we're sometimes hitting the bounds inside the file we - // limit the intervals of line and column number that are generated. - unsigned MaxLineForFileRequests = 7; - unsigned MaxColumnForFileRequests = 10; - - std::vector<std::string> FilePaths; - MockFSProvider FS; - for (unsigned I = 0; I < FilesCount; ++I) { - std::string Name = std::string("Foo") + std::to_string(I) + ".cpp"; - FS.Files[Name] = ""; - FilePaths.push_back(testPath(Name)); - } - - struct FileStat { - unsigned HitsWithoutErrors = 0; - unsigned HitsWithErrors = 0; - bool HadErrorsInLastDiags = false; - }; - - class TestDiagConsumer : public DiagnosticsConsumer { - public: - TestDiagConsumer() : Stats(FilesCount, FileStat()) {} - - void onDiagnosticsReady(PathRef File, - std::vector<Diag> Diagnostics) override { - StringRef FileIndexStr = llvm::sys::path::stem(File); - ASSERT_TRUE(FileIndexStr.consume_front("Foo")); - - unsigned long FileIndex = std::stoul(FileIndexStr.str()); - - bool HadError = diagsContainErrors(Diagnostics); - - std::lock_guard<std::mutex> Lock(Mutex); - if (HadError) - Stats[FileIndex].HitsWithErrors++; - else - Stats[FileIndex].HitsWithoutErrors++; - Stats[FileIndex].HadErrorsInLastDiags = HadError; - } - - std::vector<FileStat> takeFileStats() { - std::lock_guard<std::mutex> Lock(Mutex); - return std::move(Stats); - } - - private: - std::mutex Mutex; - std::vector<FileStat> Stats; - }; - - struct RequestStats { - unsigned RequestsWithoutErrors = 0; - unsigned RequestsWithErrors = 0; - bool LastContentsHadErrors = false; - bool FileIsRemoved = true; - }; - - std::vector<RequestStats> ReqStats; - ReqStats.reserve(FilesCount); - for (unsigned FileIndex = 0; FileIndex < FilesCount; ++FileIndex) - ReqStats.emplace_back(); - - TestDiagConsumer DiagConsumer; - { - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - // Prepare some random distributions for the test. - std::random_device RandGen; - - std::uniform_int_distribution<unsigned> FileIndexDist(0, FilesCount - 1); - // Pass a text that contains compiler errors to addDocument in about 20% of - // all requests. - std::bernoulli_distribution ShouldHaveErrorsDist(0.2); - // Line and Column numbers for requests that need them. - std::uniform_int_distribution<int> LineDist(0, MaxLineForFileRequests); - std::uniform_int_distribution<int> ColumnDist(0, MaxColumnForFileRequests); - - // Some helpers. - auto UpdateStatsOnAddDocument = [&](unsigned FileIndex, bool HadErrors) { - auto &Stats = ReqStats[FileIndex]; - - if (HadErrors) - ++Stats.RequestsWithErrors; - else - ++Stats.RequestsWithoutErrors; - Stats.LastContentsHadErrors = HadErrors; - Stats.FileIsRemoved = false; - }; - - auto UpdateStatsOnRemoveDocument = [&](unsigned FileIndex) { - auto &Stats = ReqStats[FileIndex]; - - Stats.FileIsRemoved = true; - }; - - auto AddDocument = [&](unsigned FileIndex, bool SkipCache) { - bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen); - Server.addDocument(FilePaths[FileIndex], - ShouldHaveErrors ? SourceContentsWithErrors - : SourceContentsWithoutErrors, - WantDiagnostics::Auto); - UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors); - }; - - // Various requests that we would randomly run. - auto AddDocumentRequest = [&]() { - unsigned FileIndex = FileIndexDist(RandGen); - AddDocument(FileIndex, /*SkipCache=*/false); - }; - - auto ForceReparseRequest = [&]() { - unsigned FileIndex = FileIndexDist(RandGen); - AddDocument(FileIndex, /*SkipCache=*/true); - }; - - auto RemoveDocumentRequest = [&]() { - unsigned FileIndex = FileIndexDist(RandGen); - // Make sure we don't violate the ClangdServer's contract. - if (ReqStats[FileIndex].FileIsRemoved) - AddDocument(FileIndex, /*SkipCache=*/false); - - Server.removeDocument(FilePaths[FileIndex]); - UpdateStatsOnRemoveDocument(FileIndex); - }; - - auto CodeCompletionRequest = [&]() { - unsigned FileIndex = FileIndexDist(RandGen); - // Make sure we don't violate the ClangdServer's contract. - if (ReqStats[FileIndex].FileIsRemoved) - AddDocument(FileIndex, /*SkipCache=*/false); - - Position Pos; - Pos.line = LineDist(RandGen); - Pos.character = ColumnDist(RandGen); - // FIXME(ibiryukov): Also test async completion requests. - // Simply putting CodeCompletion into async requests now would make - // tests slow, since there's no way to cancel previous completion - // requests as opposed to AddDocument/RemoveDocument, which are implicitly - // cancelled by any subsequent AddDocument/RemoveDocument request to the - // same file. - cantFail(runCodeComplete(Server, FilePaths[FileIndex], Pos, - clangd::CodeCompleteOptions())); - }; - - auto LocateSymbolRequest = [&]() { - unsigned FileIndex = FileIndexDist(RandGen); - // Make sure we don't violate the ClangdServer's contract. - if (ReqStats[FileIndex].FileIsRemoved) - AddDocument(FileIndex, /*SkipCache=*/false); - - Position Pos; - Pos.line = LineDist(RandGen); - Pos.character = ColumnDist(RandGen); - - ASSERT_TRUE(!!runLocateSymbolAt(Server, FilePaths[FileIndex], Pos)); - }; - - std::vector<std::function<void()>> AsyncRequests = { - AddDocumentRequest, ForceReparseRequest, RemoveDocumentRequest}; - std::vector<std::function<void()>> BlockingRequests = { - CodeCompletionRequest, LocateSymbolRequest}; - - // Bash requests to ClangdServer in a loop. - std::uniform_int_distribution<int> AsyncRequestIndexDist( - 0, AsyncRequests.size() - 1); - std::uniform_int_distribution<int> BlockingRequestIndexDist( - 0, BlockingRequests.size() - 1); - for (unsigned I = 1; I <= RequestsCount; ++I) { - if (I % BlockingRequestInterval != 0) { - // Issue an async request most of the time. It should be fast. - unsigned RequestIndex = AsyncRequestIndexDist(RandGen); - AsyncRequests[RequestIndex](); - } else { - // Issue a blocking request once in a while. - auto RequestIndex = BlockingRequestIndexDist(RandGen); - BlockingRequests[RequestIndex](); - } - } - ASSERT_TRUE(Server.blockUntilIdleForTest()); - } - - // Check some invariants about the state of the program. - std::vector<FileStat> Stats = DiagConsumer.takeFileStats(); - for (unsigned I = 0; I < FilesCount; ++I) { - if (!ReqStats[I].FileIsRemoved) { - ASSERT_EQ(Stats[I].HadErrorsInLastDiags, - ReqStats[I].LastContentsHadErrors); - } - - ASSERT_LE(Stats[I].HitsWithErrors, ReqStats[I].RequestsWithErrors); - ASSERT_LE(Stats[I].HitsWithoutErrors, ReqStats[I].RequestsWithoutErrors); - } -} - -TEST_F(ClangdVFSTest, CheckSourceHeaderSwitch) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto SourceContents = R"cpp( - #include "foo.h" - int b = a; - )cpp"; - - auto FooCpp = testPath("foo.cpp"); - auto FooH = testPath("foo.h"); - auto Invalid = testPath("main.cpp"); - - FS.Files[FooCpp] = SourceContents; - FS.Files[FooH] = "int a;"; - FS.Files[Invalid] = "int main() { \n return 0; \n }"; - - Optional<Path> PathResult = Server.switchSourceHeader(FooCpp); - EXPECT_TRUE(PathResult.hasValue()); - ASSERT_EQ(PathResult.getValue(), FooH); - - PathResult = Server.switchSourceHeader(FooH); - EXPECT_TRUE(PathResult.hasValue()); - ASSERT_EQ(PathResult.getValue(), FooCpp); - - SourceContents = R"c( - #include "foo.HH" - int b = a; - )c"; - - // Test with header file in capital letters and different extension, source - // file with different extension - auto FooC = testPath("bar.c"); - auto FooHH = testPath("bar.HH"); - - FS.Files[FooC] = SourceContents; - FS.Files[FooHH] = "int a;"; - - PathResult = Server.switchSourceHeader(FooC); - EXPECT_TRUE(PathResult.hasValue()); - ASSERT_EQ(PathResult.getValue(), FooHH); - - // Test with both capital letters - auto Foo2C = testPath("foo2.C"); - auto Foo2HH = testPath("foo2.HH"); - FS.Files[Foo2C] = SourceContents; - FS.Files[Foo2HH] = "int a;"; - - PathResult = Server.switchSourceHeader(Foo2C); - EXPECT_TRUE(PathResult.hasValue()); - ASSERT_EQ(PathResult.getValue(), Foo2HH); - - // Test with source file as capital letter and .hxx header file - auto Foo3C = testPath("foo3.C"); - auto Foo3HXX = testPath("foo3.hxx"); - - SourceContents = R"c( - #include "foo3.hxx" - int b = a; - )c"; - - FS.Files[Foo3C] = SourceContents; - FS.Files[Foo3HXX] = "int a;"; - - PathResult = Server.switchSourceHeader(Foo3C); - EXPECT_TRUE(PathResult.hasValue()); - ASSERT_EQ(PathResult.getValue(), Foo3HXX); - - // Test if asking for a corresponding file that doesn't exist returns an empty - // string. - PathResult = Server.switchSourceHeader(Invalid); - EXPECT_FALSE(PathResult.hasValue()); -} - -TEST_F(ClangdThreadingTest, NoConcurrentDiagnostics) { - class NoConcurrentAccessDiagConsumer : public DiagnosticsConsumer { - public: - std::atomic<int> Count = {0}; - - NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse) - : StartSecondReparse(std::move(StartSecondReparse)) {} - - void onDiagnosticsReady(PathRef, std::vector<Diag>) override { - ++Count; - std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock_t()); - ASSERT_TRUE(Lock.owns_lock()) - << "Detected concurrent onDiagnosticsReady calls for the same file."; - - // If we started the second parse immediately, it might cancel the first. - // So we don't allow it to start until the first has delivered diags... - if (FirstRequest) { - FirstRequest = false; - StartSecondReparse.set_value(); - // ... but then we wait long enough that the callbacks would overlap. - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } - } - - private: - std::mutex Mutex; - bool FirstRequest = true; - std::promise<void> StartSecondReparse; - }; - - const auto SourceContentsWithoutErrors = R"cpp( -int a; -int b; -int c; -int d; -)cpp"; - - const auto SourceContentsWithErrors = R"cpp( -int a = x; -int b; -int c; -int d; -)cpp"; - - auto FooCpp = testPath("foo.cpp"); - MockFSProvider FS; - FS.Files[FooCpp] = ""; - - std::promise<void> StartSecondPromise; - std::future<void> StartSecond = StartSecondPromise.get_future(); - - NoConcurrentAccessDiagConsumer DiagConsumer(std::move(StartSecondPromise)); - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - Server.addDocument(FooCpp, SourceContentsWithErrors); - StartSecond.wait(); - Server.addDocument(FooCpp, SourceContentsWithoutErrors); - ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - ASSERT_EQ(DiagConsumer.Count, 2); // Sanity check - we actually ran both? -} - -TEST_F(ClangdVFSTest, FormatCode) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto Path = testPath("foo.cpp"); - std::string Code = R"cpp( -#include "x.h" -#include "y.h" - -void f( ) {} -)cpp"; - std::string Expected = R"cpp( -#include "x.h" -#include "y.h" - -void f() {} -)cpp"; - FS.Files[Path] = Code; - runAddDocument(Server, Path, Code); - - auto Replaces = Server.formatFile(Code, Path); - EXPECT_TRUE(static_cast<bool>(Replaces)); - auto Changed = tooling::applyAllReplacements(Code, *Replaces); - EXPECT_TRUE(static_cast<bool>(Changed)); - EXPECT_EQ(Expected, *Changed); -} - -TEST_F(ClangdVFSTest, ChangedHeaderFromISystem) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto SourcePath = testPath("source/foo.cpp"); - auto HeaderPath = testPath("headers/foo.h"); - FS.Files[HeaderPath] = "struct X { int bar; };"; - Annotations Code(R"cpp( - #include "foo.h" - - int main() { - X().ba^ - })cpp"); - CDB.ExtraClangFlags.push_back("-xc++"); - CDB.ExtraClangFlags.push_back("-isystem" + testPath("headers")); - - runAddDocument(Server, SourcePath, Code.code()); - auto Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(), - clangd::CodeCompleteOptions())) - .Completions; - EXPECT_THAT(Completions, ElementsAre(Field(&CodeCompletion::Name, "bar"))); - // Update the header and rerun addDocument to make sure we get the updated - // files. - FS.Files[HeaderPath] = "struct X { int bar; int baz; };"; - runAddDocument(Server, SourcePath, Code.code()); - Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(), - clangd::CodeCompleteOptions())) - .Completions; - // We want to make sure we see the updated version. - EXPECT_THAT(Completions, ElementsAre(Field(&CodeCompletion::Name, "bar"), - Field(&CodeCompletion::Name, "baz"))); -} - -// FIXME(ioeric): make this work for windows again. -#ifndef _WIN32 -// Check that running code completion doesn't stat() a bunch of files from the -// preamble again. (They should be using the preamble's stat-cache) -TEST(ClangdTests, PreambleVFSStatCache) { - class ListenStatsFSProvider : public FileSystemProvider { - public: - ListenStatsFSProvider(llvm::StringMap<unsigned> &CountStats) - : CountStats(CountStats) {} - - IntrusiveRefCntPtr<llvm::vfs::FileSystem> getFileSystem() const override { - class ListenStatVFS : public llvm::vfs::ProxyFileSystem { - public: - ListenStatVFS(IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS, - llvm::StringMap<unsigned> &CountStats) - : ProxyFileSystem(std::move(FS)), CountStats(CountStats) {} - - llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> - openFileForRead(const Twine &Path) override { - ++CountStats[llvm::sys::path::filename(Path.str())]; - return ProxyFileSystem::openFileForRead(Path); - } - llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override { - ++CountStats[llvm::sys::path::filename(Path.str())]; - return ProxyFileSystem::status(Path); - } - - private: - llvm::StringMap<unsigned> &CountStats; - }; - - return IntrusiveRefCntPtr<ListenStatVFS>( - new ListenStatVFS(buildTestFS(Files), CountStats)); - } - - // If relative paths are used, they are resolved with testPath(). - llvm::StringMap<std::string> Files; - llvm::StringMap<unsigned> &CountStats; - }; - - llvm::StringMap<unsigned> CountStats; - ListenStatsFSProvider FS(CountStats); - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto SourcePath = testPath("foo.cpp"); - auto HeaderPath = testPath("foo.h"); - FS.Files[HeaderPath] = "struct TestSym {};"; - Annotations Code(R"cpp( - #include "foo.h" - - int main() { - TestSy^ - })cpp"); - - runAddDocument(Server, SourcePath, Code.code()); - - unsigned Before = CountStats["foo.h"]; - EXPECT_GT(Before, 0u); - auto Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(), - clangd::CodeCompleteOptions())) - .Completions; - EXPECT_EQ(CountStats["foo.h"], Before); - EXPECT_THAT(Completions, - ElementsAre(Field(&CodeCompletion::Name, "TestSym"))); -} -#endif - -TEST_F(ClangdVFSTest, FlagsWithPlugins) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - CDB.ExtraClangFlags = { - "-Xclang", - "-add-plugin", - "-Xclang", - "random-plugin", - }; - OverlayCDB OCDB(&CDB); - ClangdServer Server(OCDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - const auto SourceContents = "int main() { return 0; }"; - FS.Files[FooCpp] = FooCpp; - Server.addDocument(FooCpp, SourceContents); - auto Result = dumpASTWithoutMemoryLocs(Server, FooCpp); - EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_NE(Result, "<no-ast>"); -} - -TEST_F(ClangdVFSTest, FallbackWhenPreambleIsNotReady) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - Annotations Code(R"cpp( - namespace ns { int xyz; } - using namespace ns; - int main() { - xy^ - })cpp"); - FS.Files[FooCpp] = FooCpp; - - auto Opts = clangd::CodeCompleteOptions(); - Opts.AllowFallback = true; - - // This will make compile command broken and preamble absent. - CDB.ExtraClangFlags = {"yolo.cc"}; - Server.addDocument(FooCpp, Code.code()); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - auto Res = cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)); - EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery); - // Identifier-based fallback completion doesn't know about "symbol" scope. - EXPECT_THAT(Res.Completions, - ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"), - Field(&CodeCompletion::Scope, "")))); - - // Make the compile command work again. - CDB.ExtraClangFlags = {"-std=c++11"}; - Server.addDocument(FooCpp, Code.code()); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Code.point(), - clangd::CodeCompleteOptions())) - .Completions, - ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"), - Field(&CodeCompletion::Scope, "ns::")))); -} - -TEST_F(ClangdVFSTest, FallbackWhenWaitingForCompileCommand) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - // Returns compile command only when notified. - class DelayedCompilationDatabase : public GlobalCompilationDatabase { - public: - DelayedCompilationDatabase(Notification &CanReturnCommand) - : CanReturnCommand(CanReturnCommand) {} - - llvm::Optional<tooling::CompileCommand> - getCompileCommand(PathRef File, ProjectInfo * = nullptr) const override { - // FIXME: make this timeout and fail instead of waiting forever in case - // something goes wrong. - CanReturnCommand.wait(); - auto FileName = llvm::sys::path::filename(File); - std::vector<std::string> CommandLine = {"clangd", "-ffreestanding", File}; - return {tooling::CompileCommand(llvm::sys::path::parent_path(File), - FileName, std::move(CommandLine), "")}; - } - - std::vector<std::string> ExtraClangFlags; - - private: - Notification &CanReturnCommand; - }; - - Notification CanReturnCommand; - DelayedCompilationDatabase CDB(CanReturnCommand); - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - Annotations Code(R"cpp( - namespace ns { int xyz; } - using namespace ns; - int main() { - xy^ - })cpp"); - FS.Files[FooCpp] = FooCpp; - Server.addDocument(FooCpp, Code.code()); - - // Sleep for some time to make sure code completion is not run because update - // hasn't been scheduled. - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - auto Opts = clangd::CodeCompleteOptions(); - Opts.AllowFallback = true; - - auto Res = cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)); - EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery); - - CanReturnCommand.notify(); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Code.point(), - clangd::CodeCompleteOptions())) - .Completions, - ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"), - Field(&CodeCompletion::Scope, "ns::")))); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/ClangdUnitTests.cpp b/clang-tools-extra/unittests/clangd/ClangdUnitTests.cpp deleted file mode 100644 index dd3fc6dd58b..00000000000 --- a/clang-tools-extra/unittests/clangd/ClangdUnitTests.cpp +++ /dev/null @@ -1,86 +0,0 @@ -//===-- ClangdUnitTests.cpp - ClangdUnit tests ------------------*- C++ -*-===// -// -// 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 "Annotations.h" -#include "ClangdUnit.h" -#include "SourceCode.h" -#include "TestTU.h" -#include "llvm/Support/ScopedPrinter.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -using testing::ElementsAre; - -TEST(ClangdUnitTest, GetBeginningOfIdentifier) { - std::string Preamble = R"cpp( -struct Bar { int func(); }; -#define MACRO(X) void f() { X; } -Bar* bar; - )cpp"; - // First ^ is the expected beginning, last is the search position. - for (std::string Text : std::vector<std::string>{ - "int ^f^oo();", // inside identifier - "int ^foo();", // beginning of identifier - "int ^foo^();", // end of identifier - "int foo(^);", // non-identifier - "^int foo();", // beginning of file (can't back up) - "int ^f0^0();", // after a digit (lexing at N-1 is wrong) - "int ^λλ^λ();", // UTF-8 handled properly when backing up - - // identifier in macro arg - "MACRO(bar->^func())", // beginning of identifier - "MACRO(bar->^fun^c())", // inside identifier - "MACRO(bar->^func^())", // end of identifier - "MACRO(^bar->func())", // begin identifier - "MACRO(^bar^->func())", // end identifier - "^MACRO(bar->func())", // beginning of macro name - "^MAC^RO(bar->func())", // inside macro name - "^MACRO^(bar->func())", // end of macro name - }) { - std::string WithPreamble = Preamble + Text; - Annotations TestCase(WithPreamble); - auto AST = TestTU::withCode(TestCase.code()).build(); - const auto &SourceMgr = AST.getASTContext().getSourceManager(); - SourceLocation Actual = getBeginningOfIdentifier( - AST, TestCase.points().back(), SourceMgr.getMainFileID()); - Position ActualPos = offsetToPosition( - TestCase.code(), - SourceMgr.getFileOffset(SourceMgr.getSpellingLoc(Actual))); - EXPECT_EQ(TestCase.points().front(), ActualPos) << Text; - } -} - -MATCHER_P(DeclNamed, Name, "") { - if (NamedDecl *ND = dyn_cast<NamedDecl>(arg)) - if (ND->getName() == Name) - return true; - if (auto *Stream = result_listener->stream()) { - llvm::raw_os_ostream OS(*Stream); - arg->dump(OS); - } - return false; -} - -TEST(ClangdUnitTest, TopLevelDecls) { - TestTU TU; - TU.HeaderCode = R"( - int header1(); - int header2; - )"; - TU.Code = "int main();"; - auto AST = TU.build(); - EXPECT_THAT(AST.getLocalTopLevelDecls(), ElementsAre(DeclNamed("main"))); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/CodeCompleteTests.cpp b/clang-tools-extra/unittests/clangd/CodeCompleteTests.cpp deleted file mode 100644 index 97f9124e714..00000000000 --- a/clang-tools-extra/unittests/clangd/CodeCompleteTests.cpp +++ /dev/null @@ -1,2523 +0,0 @@ -//===-- CodeCompleteTests.cpp -----------------------------------*- C++ -*-===// -// -// 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 "Annotations.h" -#include "ClangdServer.h" -#include "CodeComplete.h" -#include "Compiler.h" -#include "Matchers.h" -#include "Protocol.h" -#include "Quality.h" -#include "SourceCode.h" -#include "SyncAPI.h" -#include "TestFS.h" -#include "TestIndex.h" -#include "TestTU.h" -#include "index/Index.h" -#include "index/MemIndex.h" -#include "clang/Sema/CodeCompleteConsumer.h" -#include "clang/Tooling/CompilationDatabase.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/Path.h" -#include "llvm/Testing/Support/Error.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { - -namespace { -using ::llvm::Failed; -using ::testing::AllOf; -using ::testing::Contains; -using ::testing::ElementsAre; -using ::testing::Field; -using ::testing::HasSubstr; -using ::testing::IsEmpty; -using ::testing::Not; -using ::testing::UnorderedElementsAre; - -class IgnoreDiagnostics : public DiagnosticsConsumer { - void onDiagnosticsReady(PathRef File, - std::vector<Diag> Diagnostics) override {} -}; - -// GMock helpers for matching completion items. -MATCHER_P(Named, Name, "") { return arg.Name == Name; } -MATCHER_P(Scope, S, "") { return arg.Scope == S; } -MATCHER_P(Qualifier, Q, "") { return arg.RequiredQualifier == Q; } -MATCHER_P(Labeled, Label, "") { - return arg.RequiredQualifier + arg.Name + arg.Signature == Label; -} -MATCHER_P(SigHelpLabeled, Label, "") { return arg.label == Label; } -MATCHER_P(Kind, K, "") { return arg.Kind == K; } -MATCHER_P(Doc, D, "") { return arg.Documentation == D; } -MATCHER_P(ReturnType, D, "") { return arg.ReturnType == D; } -MATCHER_P(HasInclude, IncludeHeader, "") { - return !arg.Includes.empty() && arg.Includes[0].Header == IncludeHeader; -} -MATCHER_P(InsertInclude, IncludeHeader, "") { - return !arg.Includes.empty() && arg.Includes[0].Header == IncludeHeader && - bool(arg.Includes[0].Insertion); -} -MATCHER(InsertInclude, "") { - return !arg.Includes.empty() && bool(arg.Includes[0].Insertion); -} -MATCHER_P(SnippetSuffix, Text, "") { return arg.SnippetSuffix == Text; } -MATCHER_P(Origin, OriginSet, "") { return arg.Origin == OriginSet; } -MATCHER_P(Signature, S, "") { return arg.Signature == S; } - -// Shorthand for Contains(Named(Name)). -Matcher<const std::vector<CodeCompletion> &> Has(std::string Name) { - return Contains(Named(std::move(Name))); -} -Matcher<const std::vector<CodeCompletion> &> Has(std::string Name, - CompletionItemKind K) { - return Contains(AllOf(Named(std::move(Name)), Kind(K))); -} -MATCHER(IsDocumented, "") { return !arg.Documentation.empty(); } -MATCHER(Deprecated, "") { return arg.Deprecated; } - -std::unique_ptr<SymbolIndex> memIndex(std::vector<Symbol> Symbols) { - SymbolSlab::Builder Slab; - for (const auto &Sym : Symbols) - Slab.insert(Sym); - return MemIndex::build(std::move(Slab).build(), RefSlab()); -} - -CodeCompleteResult completions(ClangdServer &Server, llvm::StringRef TestCode, - Position point, - std::vector<Symbol> IndexSymbols = {}, - clangd::CodeCompleteOptions Opts = {}) { - std::unique_ptr<SymbolIndex> OverrideIndex; - if (!IndexSymbols.empty()) { - assert(!Opts.Index && "both Index and IndexSymbols given!"); - OverrideIndex = memIndex(std::move(IndexSymbols)); - Opts.Index = OverrideIndex.get(); - } - - auto File = testPath("foo.cpp"); - runAddDocument(Server, File, TestCode); - auto CompletionList = - llvm::cantFail(runCodeComplete(Server, File, point, Opts)); - return CompletionList; -} - -CodeCompleteResult completions(ClangdServer &Server, llvm::StringRef Text, - std::vector<Symbol> IndexSymbols = {}, - clangd::CodeCompleteOptions Opts = {}, - PathRef FilePath = "foo.cpp") { - std::unique_ptr<SymbolIndex> OverrideIndex; - if (!IndexSymbols.empty()) { - assert(!Opts.Index && "both Index and IndexSymbols given!"); - OverrideIndex = memIndex(std::move(IndexSymbols)); - Opts.Index = OverrideIndex.get(); - } - - auto File = testPath(FilePath); - Annotations Test(Text); - runAddDocument(Server, File, Test.code()); - auto CompletionList = - llvm::cantFail(runCodeComplete(Server, File, Test.point(), Opts)); - return CompletionList; -} - -// Builds a server and runs code completion. -// If IndexSymbols is non-empty, an index will be built and passed to opts. -CodeCompleteResult completions(llvm::StringRef Text, - std::vector<Symbol> IndexSymbols = {}, - clangd::CodeCompleteOptions Opts = {}, - PathRef FilePath = "foo.cpp") { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - return completions(Server, Text, std::move(IndexSymbols), std::move(Opts), - FilePath); -} - -// Builds a server and runs code completion. -// If IndexSymbols is non-empty, an index will be built and passed to opts. -CodeCompleteResult completionsNoCompile(llvm::StringRef Text, - std::vector<Symbol> IndexSymbols = {}, - clangd::CodeCompleteOptions Opts = {}, - PathRef FilePath = "foo.cpp") { - std::unique_ptr<SymbolIndex> OverrideIndex; - if (!IndexSymbols.empty()) { - assert(!Opts.Index && "both Index and IndexSymbols given!"); - OverrideIndex = memIndex(std::move(IndexSymbols)); - Opts.Index = OverrideIndex.get(); - } - - MockFSProvider FS; - Annotations Test(Text); - return codeComplete(FilePath, tooling::CompileCommand(), /*Preamble=*/nullptr, - Test.code(), Test.point(), FS.getFileSystem(), Opts); -} - -Symbol withReferences(int N, Symbol S) { - S.References = N; - return S; -} - -TEST(CompletionTest, Limit) { - clangd::CodeCompleteOptions Opts; - Opts.Limit = 2; - auto Results = completions(R"cpp( -struct ClassWithMembers { - int AAA(); - int BBB(); - int CCC(); -}; -int main() { ClassWithMembers().^ } - )cpp", - /*IndexSymbols=*/{}, Opts); - - EXPECT_TRUE(Results.HasMore); - EXPECT_THAT(Results.Completions, ElementsAre(Named("AAA"), Named("BBB"))); -} - -TEST(CompletionTest, Filter) { - std::string Body = R"cpp( - #define MotorCar - int Car; - struct S { - int FooBar; - int FooBaz; - int Qux; - }; - )cpp"; - - // Only items matching the fuzzy query are returned. - EXPECT_THAT(completions(Body + "int main() { S().Foba^ }").Completions, - AllOf(Has("FooBar"), Has("FooBaz"), Not(Has("Qux")))); - - // Macros require prefix match. - EXPECT_THAT(completions(Body + "int main() { C^ }").Completions, - AllOf(Has("Car"), Not(Has("MotorCar")))); -} - -void TestAfterDotCompletion(clangd::CodeCompleteOptions Opts) { - auto Results = completions( - R"cpp( - int global_var; - - int global_func(); - - // Make sure this is not in preamble. - #define MACRO X - - struct GlobalClass {}; - - struct ClassWithMembers { - /// Doc for method. - int method(); - - int field; - private: - int private_field; - }; - - int test() { - struct LocalClass {}; - - /// Doc for local_var. - int local_var; - - ClassWithMembers().^ - } - )cpp", - {cls("IndexClass"), var("index_var"), func("index_func")}, Opts); - - // Class members. The only items that must be present in after-dot - // completion. - EXPECT_THAT(Results.Completions, - AllOf(Has("method"), Has("field"), Not(Has("ClassWithMembers")), - Not(Has("operator=")), Not(Has("~ClassWithMembers")))); - EXPECT_IFF(Opts.IncludeIneligibleResults, Results.Completions, - Has("private_field")); - // Global items. - EXPECT_THAT( - Results.Completions, - Not(AnyOf(Has("global_var"), Has("index_var"), Has("global_func"), - Has("global_func()"), Has("index_func"), Has("GlobalClass"), - Has("IndexClass"), Has("MACRO"), Has("LocalClass")))); - // There should be no code patterns (aka snippets) in after-dot - // completion. At least there aren't any we're aware of. - EXPECT_THAT(Results.Completions, - Not(Contains(Kind(CompletionItemKind::Snippet)))); - // Check documentation. - EXPECT_IFF(Opts.IncludeComments, Results.Completions, - Contains(IsDocumented())); -} - -void TestGlobalScopeCompletion(clangd::CodeCompleteOptions Opts) { - auto Results = completions( - R"cpp( - int global_var; - int global_func(); - - // Make sure this is not in preamble. - #define MACRO X - - struct GlobalClass {}; - - struct ClassWithMembers { - /// Doc for method. - int method(); - }; - - int test() { - struct LocalClass {}; - - /// Doc for local_var. - int local_var; - - ^ - } - )cpp", - {cls("IndexClass"), var("index_var"), func("index_func")}, Opts); - - // Class members. Should never be present in global completions. - EXPECT_THAT(Results.Completions, - Not(AnyOf(Has("method"), Has("method()"), Has("field")))); - // Global items. - EXPECT_THAT(Results.Completions, - AllOf(Has("global_var"), Has("index_var"), Has("global_func"), - Has("index_func" /* our fake symbol doesn't include () */), - Has("GlobalClass"), Has("IndexClass"))); - // A macro. - EXPECT_IFF(Opts.IncludeMacros, Results.Completions, Has("MACRO")); - // Local items. Must be present always. - EXPECT_THAT(Results.Completions, - AllOf(Has("local_var"), Has("LocalClass"), - Contains(Kind(CompletionItemKind::Snippet)))); - // Check documentation. - EXPECT_IFF(Opts.IncludeComments, Results.Completions, - Contains(IsDocumented())); -} - -TEST(CompletionTest, CompletionOptions) { - auto Test = [&](const clangd::CodeCompleteOptions &Opts) { - TestAfterDotCompletion(Opts); - TestGlobalScopeCompletion(Opts); - }; - // We used to test every combination of options, but that got too slow (2^N). - auto Flags = { - &clangd::CodeCompleteOptions::IncludeMacros, - &clangd::CodeCompleteOptions::IncludeComments, - &clangd::CodeCompleteOptions::IncludeCodePatterns, - &clangd::CodeCompleteOptions::IncludeIneligibleResults, - }; - // Test default options. - Test({}); - // Test with one flag flipped. - for (auto &F : Flags) { - clangd::CodeCompleteOptions O; - O.*F ^= true; - Test(O); - } -} - -TEST(CompletionTest, Priorities) { - auto Internal = completions(R"cpp( - class Foo { - public: void pub(); - protected: void prot(); - private: void priv(); - }; - void Foo::pub() { this->^ } - )cpp"); - EXPECT_THAT(Internal.Completions, - HasSubsequence(Named("priv"), Named("prot"), Named("pub"))); - - auto External = completions(R"cpp( - class Foo { - public: void pub(); - protected: void prot(); - private: void priv(); - }; - void test() { - Foo F; - F.^ - } - )cpp"); - EXPECT_THAT(External.Completions, - AllOf(Has("pub"), Not(Has("prot")), Not(Has("priv")))); -} - -TEST(CompletionTest, Qualifiers) { - auto Results = completions(R"cpp( - class Foo { - public: int foo() const; - int bar() const; - }; - class Bar : public Foo { - int foo() const; - }; - void test() { Bar().^ } - )cpp"); - EXPECT_THAT(Results.Completions, - Contains(AllOf(Qualifier(""), Named("bar")))); - // Hidden members are not shown. - EXPECT_THAT(Results.Completions, - Not(Contains(AllOf(Qualifier("Foo::"), Named("foo"))))); - // Private members are not shown. - EXPECT_THAT(Results.Completions, - Not(Contains(AllOf(Qualifier(""), Named("foo"))))); -} - -TEST(CompletionTest, InjectedTypename) { - // These are suppressed when accessed as a member... - EXPECT_THAT(completions("struct X{}; void foo(){ X().^ }").Completions, - Not(Has("X"))); - EXPECT_THAT(completions("struct X{ void foo(){ this->^ } };").Completions, - Not(Has("X"))); - // ...but accessible in other, more useful cases. - EXPECT_THAT(completions("struct X{ void foo(){ ^ } };").Completions, - Has("X")); - EXPECT_THAT( - completions("struct Y{}; struct X:Y{ void foo(){ ^ } };").Completions, - Has("Y")); - EXPECT_THAT( - completions( - "template<class> struct Y{}; struct X:Y<int>{ void foo(){ ^ } };") - .Completions, - Has("Y")); - // This case is marginal (`using X::X` is useful), we allow it for now. - EXPECT_THAT(completions("struct X{}; void foo(){ X::^ }").Completions, - Has("X")); -} - -TEST(CompletionTest, SkipInjectedWhenUnqualified) { - EXPECT_THAT(completions("struct X { void f() { X^ }};").Completions, - ElementsAre(Named("X"), Named("~X"))); -} - -TEST(CompletionTest, Snippets) { - clangd::CodeCompleteOptions Opts; - auto Results = completions( - R"cpp( - struct fake { - int a; - int f(int i, const float f) const; - }; - int main() { - fake f; - f.^ - } - )cpp", - /*IndexSymbols=*/{}, Opts); - EXPECT_THAT( - Results.Completions, - HasSubsequence(Named("a"), - SnippetSuffix("(${1:int i}, ${2:const float f})"))); -} - -TEST(CompletionTest, Kinds) { - auto Results = completions( - R"cpp( - int variable; - struct Struct {}; - int function(); - // make sure MACRO is not included in preamble. - #define MACRO 10 - int X = ^ - )cpp", - {func("indexFunction"), var("indexVariable"), cls("indexClass")}); - EXPECT_THAT(Results.Completions, - AllOf(Has("function", CompletionItemKind::Function), - Has("variable", CompletionItemKind::Variable), - Has("int", CompletionItemKind::Keyword), - Has("Struct", CompletionItemKind::Class), - Has("MACRO", CompletionItemKind::Text), - Has("indexFunction", CompletionItemKind::Function), - Has("indexVariable", CompletionItemKind::Variable), - Has("indexClass", CompletionItemKind::Class))); - - Results = completions("nam^"); - EXPECT_THAT(Results.Completions, - Has("namespace", CompletionItemKind::Snippet)); -} - -TEST(CompletionTest, NoDuplicates) { - auto Results = completions( - R"cpp( - class Adapter { - }; - - void f() { - Adapter^ - } - )cpp", - {cls("Adapter")}); - - // Make sure there are no duplicate entries of 'Adapter'. - EXPECT_THAT(Results.Completions, ElementsAre(Named("Adapter"))); -} - -TEST(CompletionTest, ScopedNoIndex) { - auto Results = completions( - R"cpp( - namespace fake { int BigBang, Babble, Box; }; - int main() { fake::ba^ } - ")cpp"); - // Babble is a better match than BigBang. Box doesn't match at all. - EXPECT_THAT(Results.Completions, - ElementsAre(Named("Babble"), Named("BigBang"))); -} - -TEST(CompletionTest, Scoped) { - auto Results = completions( - R"cpp( - namespace fake { int Babble, Box; }; - int main() { fake::ba^ } - ")cpp", - {var("fake::BigBang")}); - EXPECT_THAT(Results.Completions, - ElementsAre(Named("Babble"), Named("BigBang"))); -} - -TEST(CompletionTest, ScopedWithFilter) { - auto Results = completions( - R"cpp( - void f() { ns::x^ } - )cpp", - {cls("ns::XYZ"), func("ns::foo")}); - EXPECT_THAT(Results.Completions, UnorderedElementsAre(Named("XYZ"))); -} - -TEST(CompletionTest, ReferencesAffectRanking) { - auto Results = completions("int main() { abs^ }", {ns("absl"), func("absb")}); - EXPECT_THAT(Results.Completions, - HasSubsequence(Named("absb"), Named("absl"))); - Results = completions("int main() { abs^ }", - {withReferences(10000, ns("absl")), func("absb")}); - EXPECT_THAT(Results.Completions, - HasSubsequence(Named("absl"), Named("absb"))); -} - -TEST(CompletionTest, GlobalQualified) { - auto Results = completions( - R"cpp( - void f() { ::^ } - )cpp", - {cls("XYZ")}); - EXPECT_THAT(Results.Completions, - AllOf(Has("XYZ", CompletionItemKind::Class), - Has("f", CompletionItemKind::Function))); -} - -TEST(CompletionTest, FullyQualified) { - auto Results = completions( - R"cpp( - namespace ns { void bar(); } - void f() { ::ns::^ } - )cpp", - {cls("ns::XYZ")}); - EXPECT_THAT(Results.Completions, - AllOf(Has("XYZ", CompletionItemKind::Class), - Has("bar", CompletionItemKind::Function))); -} - -TEST(CompletionTest, SemaIndexMerge) { - auto Results = completions( - R"cpp( - namespace ns { int local; void both(); } - void f() { ::ns::^ } - )cpp", - {func("ns::both"), cls("ns::Index")}); - // We get results from both index and sema, with no duplicates. - EXPECT_THAT(Results.Completions, - UnorderedElementsAre( - AllOf(Named("local"), Origin(SymbolOrigin::AST)), - AllOf(Named("Index"), Origin(SymbolOrigin::Static)), - AllOf(Named("both"), - Origin(SymbolOrigin::AST | SymbolOrigin::Static)))); -} - -TEST(CompletionTest, SemaIndexMergeWithLimit) { - clangd::CodeCompleteOptions Opts; - Opts.Limit = 1; - auto Results = completions( - R"cpp( - namespace ns { int local; void both(); } - void f() { ::ns::^ } - )cpp", - {func("ns::both"), cls("ns::Index")}, Opts); - EXPECT_EQ(Results.Completions.size(), Opts.Limit); - EXPECT_TRUE(Results.HasMore); -} - -TEST(CompletionTest, IncludeInsertionPreprocessorIntegrationTests) { - MockFSProvider FS; - MockCompilationDatabase CDB; - std::string Subdir = testPath("sub"); - std::string SearchDirArg = (Twine("-I") + Subdir).str(); - CDB.ExtraClangFlags = {SearchDirArg.c_str()}; - std::string BarHeader = testPath("sub/bar.h"); - FS.Files[BarHeader] = ""; - - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - auto BarURI = URI::create(BarHeader).toString(); - Symbol Sym = cls("ns::X"); - Sym.CanonicalDeclaration.FileURI = BarURI.c_str(); - Sym.IncludeHeaders.emplace_back(BarURI, 1); - // Shoten include path based on search dirctory and insert. - auto Results = completions(Server, - R"cpp( - int main() { ns::^ } - )cpp", - {Sym}); - EXPECT_THAT(Results.Completions, - ElementsAre(AllOf(Named("X"), InsertInclude("\"bar.h\"")))); - // Can be disabled via option. - CodeCompleteOptions NoInsertion; - NoInsertion.InsertIncludes = CodeCompleteOptions::NeverInsert; - Results = completions(Server, - R"cpp( - int main() { ns::^ } - )cpp", - {Sym}, NoInsertion); - EXPECT_THAT(Results.Completions, - ElementsAre(AllOf(Named("X"), Not(InsertInclude())))); - // Duplicate based on inclusions in preamble. - Results = completions(Server, - R"cpp( - #include "sub/bar.h" // not shortest, so should only match resolved. - int main() { ns::^ } - )cpp", - {Sym}); - EXPECT_THAT(Results.Completions, ElementsAre(AllOf(Named("X"), Labeled("X"), - Not(InsertInclude())))); -} - -TEST(CompletionTest, NoIncludeInsertionWhenDeclFoundInFile) { - MockFSProvider FS; - MockCompilationDatabase CDB; - - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - Symbol SymX = cls("ns::X"); - Symbol SymY = cls("ns::Y"); - std::string BarHeader = testPath("bar.h"); - auto BarURI = URI::create(BarHeader).toString(); - SymX.CanonicalDeclaration.FileURI = BarURI.c_str(); - SymY.CanonicalDeclaration.FileURI = BarURI.c_str(); - SymX.IncludeHeaders.emplace_back("<bar>", 1); - SymY.IncludeHeaders.emplace_back("<bar>", 1); - // Shoten include path based on search dirctory and insert. - auto Results = completions(Server, - R"cpp( - namespace ns { - class X; - class Y {}; - } - int main() { ns::^ } - )cpp", - {SymX, SymY}); - EXPECT_THAT(Results.Completions, - ElementsAre(AllOf(Named("X"), Not(InsertInclude())), - AllOf(Named("Y"), Not(InsertInclude())))); -} - -TEST(CompletionTest, IndexSuppressesPreambleCompletions) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - FS.Files[testPath("bar.h")] = - R"cpp(namespace ns { struct preamble { int member; }; })cpp"; - auto File = testPath("foo.cpp"); - Annotations Test(R"cpp( - #include "bar.h" - namespace ns { int local; } - void f() { ns::^; } - void f2() { ns::preamble().$2^; } - )cpp"); - runAddDocument(Server, File, Test.code()); - clangd::CodeCompleteOptions Opts = {}; - - auto I = memIndex({var("ns::index")}); - Opts.Index = I.get(); - auto WithIndex = cantFail(runCodeComplete(Server, File, Test.point(), Opts)); - EXPECT_THAT(WithIndex.Completions, - UnorderedElementsAre(Named("local"), Named("index"))); - auto ClassFromPreamble = - cantFail(runCodeComplete(Server, File, Test.point("2"), Opts)); - EXPECT_THAT(ClassFromPreamble.Completions, Contains(Named("member"))); - - Opts.Index = nullptr; - auto WithoutIndex = - cantFail(runCodeComplete(Server, File, Test.point(), Opts)); - EXPECT_THAT(WithoutIndex.Completions, - UnorderedElementsAre(Named("local"), Named("preamble"))); -} - -// This verifies that we get normal preprocessor completions in the preamble. -// This is a regression test for an old bug: if we override the preamble and -// try to complete inside it, clang kicks our completion point just outside the -// preamble, resulting in always getting top-level completions. -TEST(CompletionTest, CompletionInPreamble) { - auto Results = completions(R"cpp( - #ifnd^ef FOO_H_ - #define BAR_H_ - #include <bar.h> - int foo() {} - #endif - )cpp") - .Completions; - EXPECT_THAT(Results, ElementsAre(Named("ifndef"))); -} - -TEST(CompletionTest, DynamicIndexIncludeInsertion) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer::Options Opts = ClangdServer::optsForTest(); - Opts.BuildDynamicSymbolIndex = true; - ClangdServer Server(CDB, FS, DiagConsumer, Opts); - - FS.Files[testPath("foo_header.h")] = R"cpp( - #pragma once - struct Foo { - // Member doc - int foo(); - }; - )cpp"; - const std::string FileContent(R"cpp( - #include "foo_header.h" - int Foo::foo() { - return 42; - } - )cpp"); - Server.addDocument(testPath("foo_impl.cpp"), FileContent); - // Wait for the dynamic index being built. - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_THAT(completions(Server, "Foo^ foo;").Completions, - ElementsAre(AllOf(Named("Foo"), - HasInclude('"' + - llvm::sys::path::convert_to_slash( - testPath("foo_header.h")) + - '"'), - InsertInclude()))); -} - -TEST(CompletionTest, DynamicIndexMultiFile) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - auto Opts = ClangdServer::optsForTest(); - Opts.BuildDynamicSymbolIndex = true; - ClangdServer Server(CDB, FS, DiagConsumer, Opts); - - FS.Files[testPath("foo.h")] = R"cpp( - namespace ns { class XYZ {}; void foo(int x) {} } - )cpp"; - runAddDocument(Server, testPath("foo.cpp"), R"cpp( - #include "foo.h" - )cpp"); - - auto File = testPath("bar.cpp"); - Annotations Test(R"cpp( - namespace ns { - class XXX {}; - /// Doooc - void fooooo() {} - } - void f() { ns::^ } - )cpp"); - runAddDocument(Server, File, Test.code()); - - auto Results = cantFail(runCodeComplete(Server, File, Test.point(), {})); - // "XYZ" and "foo" are not included in the file being completed but are still - // visible through the index. - EXPECT_THAT(Results.Completions, Has("XYZ", CompletionItemKind::Class)); - EXPECT_THAT(Results.Completions, Has("foo", CompletionItemKind::Function)); - EXPECT_THAT(Results.Completions, Has("XXX", CompletionItemKind::Class)); - EXPECT_THAT(Results.Completions, - Contains((Named("fooooo"), Kind(CompletionItemKind::Function), - Doc("Doooc"), ReturnType("void")))); -} - -TEST(CompletionTest, Documentation) { - auto Results = completions( - R"cpp( - // Non-doxygen comment. - int foo(); - /// Doxygen comment. - /// \param int a - int bar(int a); - /* Multi-line - block comment - */ - int baz(); - - int x = ^ - )cpp"); - EXPECT_THAT(Results.Completions, - Contains(AllOf(Named("foo"), Doc("Non-doxygen comment.")))); - EXPECT_THAT( - Results.Completions, - Contains(AllOf(Named("bar"), Doc("Doxygen comment.\n\\param int a")))); - EXPECT_THAT(Results.Completions, - Contains(AllOf(Named("baz"), Doc("Multi-line\nblock comment")))); -} - -TEST(CompletionTest, GlobalCompletionFiltering) { - - Symbol Class = cls("XYZ"); - Class.Flags = static_cast<Symbol::SymbolFlag>( - Class.Flags & ~(Symbol::IndexedForCodeCompletion)); - Symbol Func = func("XYZ::foooo"); - Func.Flags = static_cast<Symbol::SymbolFlag>( - Func.Flags & ~(Symbol::IndexedForCodeCompletion)); - - auto Results = completions(R"(// void f() { - XYZ::foooo^ - })", - {Class, Func}); - EXPECT_THAT(Results.Completions, IsEmpty()); -} - -TEST(CodeCompleteTest, DisableTypoCorrection) { - auto Results = completions(R"cpp( - namespace clang { int v; } - void f() { clangd::^ - )cpp"); - EXPECT_TRUE(Results.Completions.empty()); -} - -TEST(CodeCompleteTest, NoColonColonAtTheEnd) { - auto Results = completions(R"cpp( - namespace clang { } - void f() { - clan^ - } - )cpp"); - - EXPECT_THAT(Results.Completions, Contains(Labeled("clang"))); - EXPECT_THAT(Results.Completions, Not(Contains(Labeled("clang::")))); -} - -TEST(CompletionTest, BacktrackCrashes) { - // Sema calls code completion callbacks twice in these cases. - auto Results = completions(R"cpp( - namespace ns { - struct FooBarBaz {}; - } // namespace ns - - int foo(ns::FooBar^ - )cpp"); - - EXPECT_THAT(Results.Completions, ElementsAre(Labeled("FooBarBaz"))); - - // Check we don't crash in that case too. - completions(R"cpp( - struct FooBarBaz {}; - void test() { - if (FooBarBaz * x^) {} - } -)cpp"); -} - -TEST(CompletionTest, CompleteInMacroWithStringification) { - auto Results = completions(R"cpp( -void f(const char *, int x); -#define F(x) f(#x, x) - -namespace ns { -int X; -int Y; -} // namespace ns - -int f(int input_num) { - F(ns::^) -} -)cpp"); - - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(Named("X"), Named("Y"))); -} - -TEST(CompletionTest, CompleteInMacroAndNamespaceWithStringification) { - auto Results = completions(R"cpp( -void f(const char *, int x); -#define F(x) f(#x, x) - -namespace ns { -int X; - -int f(int input_num) { - F(^) -} -} // namespace ns -)cpp"); - - EXPECT_THAT(Results.Completions, Contains(Named("X"))); -} - -TEST(CompletionTest, IgnoreCompleteInExcludedPPBranchWithRecoveryContext) { - auto Results = completions(R"cpp( - int bar(int param_in_bar) { - } - - int foo(int param_in_foo) { -#if 0 - // In recorvery mode, "param_in_foo" will also be suggested among many other - // unrelated symbols; however, this is really a special case where this works. - // If the #if block is outside of the function, "param_in_foo" is still - // suggested, but "bar" and "foo" are missing. So the recovery mode doesn't - // really provide useful results in excluded branches. - par^ -#endif - } -)cpp"); - - EXPECT_TRUE(Results.Completions.empty()); -} -SignatureHelp signatures(llvm::StringRef Text, Position Point, - std::vector<Symbol> IndexSymbols = {}) { - std::unique_ptr<SymbolIndex> Index; - if (!IndexSymbols.empty()) - Index = memIndex(IndexSymbols); - - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer::Options Opts = ClangdServer::optsForTest(); - Opts.StaticIndex = Index.get(); - - ClangdServer Server(CDB, FS, DiagConsumer, Opts); - auto File = testPath("foo.cpp"); - runAddDocument(Server, File, Text); - return llvm::cantFail(runSignatureHelp(Server, File, Point)); -} - -SignatureHelp signatures(llvm::StringRef Text, - std::vector<Symbol> IndexSymbols = {}) { - Annotations Test(Text); - return signatures(Test.code(), Test.point(), std::move(IndexSymbols)); -} - -MATCHER_P(ParamsAre, P, "") { - if (P.size() != arg.parameters.size()) - return false; - for (unsigned I = 0; I < P.size(); ++I) - if (P[I] != arg.parameters[I].label) - return false; - return true; -} -MATCHER_P(SigDoc, Doc, "") { return arg.documentation == Doc; } - -Matcher<SignatureInformation> Sig(std::string Label, - std::vector<std::string> Params) { - return AllOf(SigHelpLabeled(Label), ParamsAre(Params)); -} - -TEST(SignatureHelpTest, Overloads) { - auto Results = signatures(R"cpp( - void foo(int x, int y); - void foo(int x, float y); - void foo(float x, int y); - void foo(float x, float y); - void bar(int x, int y = 0); - int main() { foo(^); } - )cpp"); - EXPECT_THAT(Results.signatures, - UnorderedElementsAre( - Sig("foo(float x, float y) -> void", {"float x", "float y"}), - Sig("foo(float x, int y) -> void", {"float x", "int y"}), - Sig("foo(int x, float y) -> void", {"int x", "float y"}), - Sig("foo(int x, int y) -> void", {"int x", "int y"}))); - // We always prefer the first signature. - EXPECT_EQ(0, Results.activeSignature); - EXPECT_EQ(0, Results.activeParameter); -} - -TEST(SignatureHelpTest, DefaultArgs) { - auto Results = signatures(R"cpp( - void bar(int x, int y = 0); - void bar(float x = 0, int y = 42); - int main() { bar(^ - )cpp"); - EXPECT_THAT(Results.signatures, - UnorderedElementsAre( - Sig("bar(int x, int y = 0) -> void", {"int x", "int y = 0"}), - Sig("bar(float x = 0, int y = 42) -> void", - {"float x = 0", "int y = 42"}))); - EXPECT_EQ(0, Results.activeSignature); - EXPECT_EQ(0, Results.activeParameter); -} - -TEST(SignatureHelpTest, ActiveArg) { - auto Results = signatures(R"cpp( - int baz(int a, int b, int c); - int main() { baz(baz(1,2,3), ^); } - )cpp"); - EXPECT_THAT(Results.signatures, - ElementsAre(Sig("baz(int a, int b, int c) -> int", - {"int a", "int b", "int c"}))); - EXPECT_EQ(0, Results.activeSignature); - EXPECT_EQ(1, Results.activeParameter); -} - -TEST(SignatureHelpTest, OpeningParen) { - llvm::StringLiteral Tests[] = {// Recursive function call. - R"cpp( - int foo(int a, int b, int c); - int main() { - foo(foo $p^( foo(10, 10, 10), ^ ))); - })cpp", - // Functional type cast. - R"cpp( - struct Foo { - Foo(int a, int b, int c); - }; - int main() { - Foo $p^( 10, ^ ); - })cpp", - // New expression. - R"cpp( - struct Foo { - Foo(int a, int b, int c); - }; - int main() { - new Foo $p^( 10, ^ ); - })cpp", - // Macro expansion. - R"cpp( - int foo(int a, int b, int c); - #define FOO foo( - - int main() { - // Macro expansions. - $p^FOO 10, ^ ); - })cpp", - // Macro arguments. - R"cpp( - int foo(int a, int b, int c); - int main() { - #define ID(X) X - ID(foo $p^( foo(10), ^ )) - })cpp"}; - - for (auto Test : Tests) { - Annotations Code(Test); - EXPECT_EQ(signatures(Code.code(), Code.point()).argListStart, - Code.point("p")) - << "Test source:" << Test; - } -} - -class IndexRequestCollector : public SymbolIndex { -public: - bool - fuzzyFind(const FuzzyFindRequest &Req, - llvm::function_ref<void(const Symbol &)> Callback) const override { - std::lock_guard<std::mutex> Lock(Mut); - Requests.push_back(Req); - return true; - } - - void lookup(const LookupRequest &, - llvm::function_ref<void(const Symbol &)>) const override {} - - void refs(const RefsRequest &, - llvm::function_ref<void(const Ref &)>) const override {} - - // This is incorrect, but IndexRequestCollector is not an actual index and it - // isn't used in production code. - size_t estimateMemoryUsage() const override { return 0; } - - const std::vector<FuzzyFindRequest> consumeRequests() const { - std::lock_guard<std::mutex> Lock(Mut); - auto Reqs = std::move(Requests); - Requests = {}; - return Reqs; - } - -private: - // We need a mutex to handle async fuzzy find requests. - mutable std::mutex Mut; - mutable std::vector<FuzzyFindRequest> Requests; -}; - -std::vector<FuzzyFindRequest> captureIndexRequests(llvm::StringRef Code) { - clangd::CodeCompleteOptions Opts; - IndexRequestCollector Requests; - Opts.Index = &Requests; - completions(Code, {}, Opts); - return Requests.consumeRequests(); -} - -TEST(CompletionTest, UnqualifiedIdQuery) { - auto Requests = captureIndexRequests(R"cpp( - namespace std {} - using namespace std; - namespace ns { - void f() { - vec^ - } - } - )cpp"); - - EXPECT_THAT(Requests, - ElementsAre(Field(&FuzzyFindRequest::Scopes, - UnorderedElementsAre("", "ns::", "std::")))); -} - -TEST(CompletionTest, EnclosingScopeComesFirst) { - auto Requests = captureIndexRequests(R"cpp( - namespace std {} - using namespace std; - namespace nx { - namespace ns { - namespace { - void f() { - vec^ - } - } - } - } - )cpp"); - - EXPECT_THAT(Requests, - ElementsAre(Field( - &FuzzyFindRequest::Scopes, - UnorderedElementsAre("", "std::", "nx::ns::", "nx::")))); - EXPECT_EQ(Requests[0].Scopes[0], "nx::ns::"); -} - -TEST(CompletionTest, ResolvedQualifiedIdQuery) { - auto Requests = captureIndexRequests(R"cpp( - namespace ns1 {} - namespace ns2 {} // ignore - namespace ns3 { namespace nns3 {} } - namespace foo { - using namespace ns1; - using namespace ns3::nns3; - } - namespace ns { - void f() { - foo::^ - } - } - )cpp"); - - EXPECT_THAT(Requests, - ElementsAre(Field( - &FuzzyFindRequest::Scopes, - UnorderedElementsAre("foo::", "ns1::", "ns3::nns3::")))); -} - -TEST(CompletionTest, UnresolvedQualifierIdQuery) { - auto Requests = captureIndexRequests(R"cpp( - namespace a {} - using namespace a; - namespace ns { - void f() { - bar::^ - } - } // namespace ns - )cpp"); - - EXPECT_THAT(Requests, - ElementsAre(Field( - &FuzzyFindRequest::Scopes, - UnorderedElementsAre("a::bar::", "ns::bar::", "bar::")))); -} - -TEST(CompletionTest, UnresolvedNestedQualifierIdQuery) { - auto Requests = captureIndexRequests(R"cpp( - namespace a {} - using namespace a; - namespace ns { - void f() { - ::a::bar::^ - } - } // namespace ns - )cpp"); - - EXPECT_THAT(Requests, ElementsAre(Field(&FuzzyFindRequest::Scopes, - UnorderedElementsAre("a::bar::")))); -} - -TEST(CompletionTest, EmptyQualifiedQuery) { - auto Requests = captureIndexRequests(R"cpp( - namespace ns { - void f() { - ^ - } - } // namespace ns - )cpp"); - - EXPECT_THAT(Requests, ElementsAre(Field(&FuzzyFindRequest::Scopes, - UnorderedElementsAre("", "ns::")))); -} - -TEST(CompletionTest, GlobalQualifiedQuery) { - auto Requests = captureIndexRequests(R"cpp( - namespace ns { - void f() { - ::^ - } - } // namespace ns - )cpp"); - - EXPECT_THAT(Requests, ElementsAre(Field(&FuzzyFindRequest::Scopes, - UnorderedElementsAre("")))); -} - -TEST(CompletionTest, NoDuplicatedQueryScopes) { - auto Requests = captureIndexRequests(R"cpp( - namespace {} - - namespace na { - namespace {} - namespace nb { - ^ - } // namespace nb - } // namespace na - )cpp"); - - EXPECT_THAT(Requests, - ElementsAre(Field(&FuzzyFindRequest::Scopes, - UnorderedElementsAre("na::", "na::nb::", "")))); -} - -TEST(CompletionTest, NoIndexCompletionsInsideClasses) { - auto Completions = completions( - R"cpp( - struct Foo { - int SomeNameOfField; - typedef int SomeNameOfTypedefField; - }; - - Foo::^)cpp", - {func("::SomeNameInTheIndex"), func("::Foo::SomeNameInTheIndex")}); - - EXPECT_THAT(Completions.Completions, - AllOf(Contains(Labeled("SomeNameOfField")), - Contains(Labeled("SomeNameOfTypedefField")), - Not(Contains(Labeled("SomeNameInTheIndex"))))); -} - -TEST(CompletionTest, NoIndexCompletionsInsideDependentCode) { - { - auto Completions = completions( - R"cpp( - template <class T> - void foo() { - T::^ - } - )cpp", - {func("::SomeNameInTheIndex")}); - - EXPECT_THAT(Completions.Completions, - Not(Contains(Labeled("SomeNameInTheIndex")))); - } - - { - auto Completions = completions( - R"cpp( - template <class T> - void foo() { - T::template Y<int>::^ - } - )cpp", - {func("::SomeNameInTheIndex")}); - - EXPECT_THAT(Completions.Completions, - Not(Contains(Labeled("SomeNameInTheIndex")))); - } - - { - auto Completions = completions( - R"cpp( - template <class T> - void foo() { - T::foo::^ - } - )cpp", - {func("::SomeNameInTheIndex")}); - - EXPECT_THAT(Completions.Completions, - Not(Contains(Labeled("SomeNameInTheIndex")))); - } -} - -TEST(CompletionTest, OverloadBundling) { - clangd::CodeCompleteOptions Opts; - Opts.BundleOverloads = true; - - std::string Context = R"cpp( - struct X { - // Overload with int - int a(int); - // Overload with bool - int a(bool); - int b(float); - }; - int GFuncC(int); - int GFuncD(int); - )cpp"; - - // Member completions are bundled. - EXPECT_THAT(completions(Context + "int y = X().^", {}, Opts).Completions, - UnorderedElementsAre(Labeled("a(…)"), Labeled("b(float)"))); - - // Non-member completions are bundled, including index+sema. - Symbol NoArgsGFunc = func("GFuncC"); - EXPECT_THAT( - completions(Context + "int y = GFunc^", {NoArgsGFunc}, Opts).Completions, - UnorderedElementsAre(Labeled("GFuncC(…)"), Labeled("GFuncD(int)"))); - - // Differences in header-to-insert suppress bundling. - std::string DeclFile = URI::create(testPath("foo")).toString(); - NoArgsGFunc.CanonicalDeclaration.FileURI = DeclFile.c_str(); - NoArgsGFunc.IncludeHeaders.emplace_back("<foo>", 1); - EXPECT_THAT( - completions(Context + "int y = GFunc^", {NoArgsGFunc}, Opts).Completions, - UnorderedElementsAre(AllOf(Named("GFuncC"), InsertInclude("<foo>")), - Labeled("GFuncC(int)"), Labeled("GFuncD(int)"))); - - // Examine a bundled completion in detail. - auto A = - completions(Context + "int y = X().a^", {}, Opts).Completions.front(); - EXPECT_EQ(A.Name, "a"); - EXPECT_EQ(A.Signature, "(…)"); - EXPECT_EQ(A.BundleSize, 2u); - EXPECT_EQ(A.Kind, CompletionItemKind::Method); - EXPECT_EQ(A.ReturnType, "int"); // All overloads return int. - // For now we just return one of the doc strings arbitrarily. - EXPECT_THAT(A.Documentation, AnyOf(HasSubstr("Overload with int"), - HasSubstr("Overload with bool"))); - EXPECT_EQ(A.SnippetSuffix, "($0)"); -} - -TEST(CompletionTest, DocumentationFromChangedFileCrash) { - MockFSProvider FS; - auto FooH = testPath("foo.h"); - auto FooCpp = testPath("foo.cpp"); - FS.Files[FooH] = R"cpp( - // this is my documentation comment. - int func(); - )cpp"; - FS.Files[FooCpp] = ""; - - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - Annotations Source(R"cpp( - #include "foo.h" - int func() { - // This makes sure we have func from header in the AST. - } - int a = fun^ - )cpp"); - Server.addDocument(FooCpp, Source.code(), WantDiagnostics::Yes); - // We need to wait for preamble to build. - ASSERT_TRUE(Server.blockUntilIdleForTest()); - - // Change the header file. Completion will reuse the old preamble! - FS.Files[FooH] = R"cpp( - int func(); - )cpp"; - - clangd::CodeCompleteOptions Opts; - Opts.IncludeComments = true; - CodeCompleteResult Completions = - cantFail(runCodeComplete(Server, FooCpp, Source.point(), Opts)); - // We shouldn't crash. Unfortunately, current workaround is to not produce - // comments for symbols from headers. - EXPECT_THAT(Completions.Completions, - Contains(AllOf(Not(IsDocumented()), Named("func")))); -} - -TEST(CompletionTest, NonDocComments) { - MockFSProvider FS; - auto FooCpp = testPath("foo.cpp"); - FS.Files[FooCpp] = ""; - - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - Annotations Source(R"cpp( - // We ignore namespace comments, for rationale see CodeCompletionStrings.h. - namespace comments_ns { - } - - // ------------------ - int comments_foo(); - - // A comment and a decl are separated by newlines. - // Therefore, the comment shouldn't show up as doc comment. - - int comments_bar(); - - // this comment should be in the results. - int comments_baz(); - - - template <class T> - struct Struct { - int comments_qux(); - int comments_quux(); - }; - - - // This comment should not be there. - - template <class T> - int Struct<T>::comments_qux() { - } - - // This comment **should** be in results. - template <class T> - int Struct<T>::comments_quux() { - int a = comments^; - } - )cpp"); - // FIXME: Auto-completion in a template requires disabling delayed template - // parsing. - CDB.ExtraClangFlags.push_back("-fno-delayed-template-parsing"); - runAddDocument(Server, FooCpp, Source.code(), WantDiagnostics::Yes); - CodeCompleteResult Completions = cantFail(runCodeComplete( - Server, FooCpp, Source.point(), clangd::CodeCompleteOptions())); - - // We should not get any of those comments in completion. - EXPECT_THAT( - Completions.Completions, - UnorderedElementsAre(AllOf(Not(IsDocumented()), Named("comments_foo")), - AllOf(IsDocumented(), Named("comments_baz")), - AllOf(IsDocumented(), Named("comments_quux")), - AllOf(Not(IsDocumented()), Named("comments_ns")), - // FIXME(ibiryukov): the following items should have - // empty documentation, since they are separated from - // a comment with an empty line. Unfortunately, I - // couldn't make Sema tests pass if we ignore those. - AllOf(IsDocumented(), Named("comments_bar")), - AllOf(IsDocumented(), Named("comments_qux")))); -} - -TEST(CompletionTest, CompleteOnInvalidLine) { - auto FooCpp = testPath("foo.cpp"); - - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - MockFSProvider FS; - FS.Files[FooCpp] = "// empty file"; - - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - // Run completion outside the file range. - Position Pos; - Pos.line = 100; - Pos.character = 0; - EXPECT_THAT_EXPECTED( - runCodeComplete(Server, FooCpp, Pos, clangd::CodeCompleteOptions()), - Failed()); -} - -TEST(CompletionTest, QualifiedNames) { - auto Results = completions( - R"cpp( - namespace ns { int local; void both(); } - void f() { ::ns::^ } - )cpp", - {func("ns::both"), cls("ns::Index")}); - // We get results from both index and sema, with no duplicates. - EXPECT_THAT( - Results.Completions, - UnorderedElementsAre(Scope("ns::"), Scope("ns::"), Scope("ns::"))); -} - -TEST(CompletionTest, Render) { - CodeCompletion C; - C.Name = "x"; - C.Signature = "(bool) const"; - C.SnippetSuffix = "(${0:bool})"; - C.ReturnType = "int"; - C.RequiredQualifier = "Foo::"; - C.Scope = "ns::Foo::"; - C.Documentation = "This is x()."; - C.Includes.emplace_back(); - auto &Include = C.Includes.back(); - Include.Header = "\"foo.h\""; - C.Kind = CompletionItemKind::Method; - C.Score.Total = 1.0; - C.Origin = SymbolOrigin::AST | SymbolOrigin::Static; - - CodeCompleteOptions Opts; - Opts.IncludeIndicator.Insert = "^"; - Opts.IncludeIndicator.NoInsert = ""; - Opts.EnableSnippets = false; - - auto R = C.render(Opts); - EXPECT_EQ(R.label, "Foo::x(bool) const"); - EXPECT_EQ(R.insertText, "Foo::x"); - EXPECT_EQ(R.insertTextFormat, InsertTextFormat::PlainText); - EXPECT_EQ(R.filterText, "x"); - EXPECT_EQ(R.detail, "int\n\"foo.h\""); - EXPECT_EQ(R.documentation, "This is x()."); - EXPECT_THAT(R.additionalTextEdits, IsEmpty()); - EXPECT_EQ(R.sortText, sortText(1.0, "x")); - EXPECT_FALSE(R.deprecated); - - Opts.EnableSnippets = true; - R = C.render(Opts); - EXPECT_EQ(R.insertText, "Foo::x(${0:bool})"); - EXPECT_EQ(R.insertTextFormat, InsertTextFormat::Snippet); - - Include.Insertion.emplace(); - R = C.render(Opts); - EXPECT_EQ(R.label, "^Foo::x(bool) const"); - EXPECT_THAT(R.additionalTextEdits, Not(IsEmpty())); - - Opts.ShowOrigins = true; - R = C.render(Opts); - EXPECT_EQ(R.label, "^[AS]Foo::x(bool) const"); - - C.BundleSize = 2; - R = C.render(Opts); - EXPECT_EQ(R.detail, "[2 overloads]\n\"foo.h\""); - - C.Deprecated = true; - R = C.render(Opts); - EXPECT_TRUE(R.deprecated); -} - -TEST(CompletionTest, IgnoreRecoveryResults) { - auto Results = completions( - R"cpp( - namespace ns { int NotRecovered() { return 0; } } - void f() { - // Sema enters recovery mode first and then normal mode. - if (auto x = ns::NotRecover^) - } - )cpp"); - EXPECT_THAT(Results.Completions, UnorderedElementsAre(Named("NotRecovered"))); -} - -TEST(CompletionTest, ScopeOfClassFieldInConstructorInitializer) { - auto Results = completions( - R"cpp( - namespace ns { - class X { public: X(); int x_; }; - X::X() : x_^(0) {} - } - )cpp"); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(AllOf(Scope("ns::X::"), Named("x_")))); -} - -TEST(CompletionTest, CodeCompletionContext) { - auto Results = completions( - R"cpp( - namespace ns { - class X { public: X(); int x_; }; - void f() { - X x; - x.^; - } - } - )cpp"); - - EXPECT_THAT(Results.Context, CodeCompletionContext::CCC_DotMemberAccess); -} - -TEST(CompletionTest, FixItForArrowToDot) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - CodeCompleteOptions Opts; - Opts.IncludeFixIts = true; - Annotations TestCode( - R"cpp( - class Auxilary { - public: - void AuxFunction(); - }; - class ClassWithPtr { - public: - void MemberFunction(); - Auxilary* operator->() const; - Auxilary* Aux; - }; - void f() { - ClassWithPtr x; - x[[->]]^; - } - )cpp"); - auto Results = - completions(Server, TestCode.code(), TestCode.point(), {}, Opts); - EXPECT_EQ(Results.Completions.size(), 3u); - - TextEdit ReplacementEdit; - ReplacementEdit.range = TestCode.range(); - ReplacementEdit.newText = "."; - for (const auto &C : Results.Completions) { - EXPECT_TRUE(C.FixIts.size() == 1u || C.Name == "AuxFunction"); - if (!C.FixIts.empty()) { - EXPECT_THAT(C.FixIts, ElementsAre(ReplacementEdit)); - } - } -} - -TEST(CompletionTest, FixItForDotToArrow) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - CodeCompleteOptions Opts; - Opts.IncludeFixIts = true; - Annotations TestCode( - R"cpp( - class Auxilary { - public: - void AuxFunction(); - }; - class ClassWithPtr { - public: - void MemberFunction(); - Auxilary* operator->() const; - Auxilary* Aux; - }; - void f() { - ClassWithPtr x; - x[[.]]^; - } - )cpp"); - auto Results = - completions(Server, TestCode.code(), TestCode.point(), {}, Opts); - EXPECT_EQ(Results.Completions.size(), 3u); - - TextEdit ReplacementEdit; - ReplacementEdit.range = TestCode.range(); - ReplacementEdit.newText = "->"; - for (const auto &C : Results.Completions) { - EXPECT_TRUE(C.FixIts.empty() || C.Name == "AuxFunction"); - if (!C.FixIts.empty()) { - EXPECT_THAT(C.FixIts, ElementsAre(ReplacementEdit)); - } - } -} - -TEST(CompletionTest, RenderWithFixItMerged) { - TextEdit FixIt; - FixIt.range.end.character = 5; - FixIt.newText = "->"; - - CodeCompletion C; - C.Name = "x"; - C.RequiredQualifier = "Foo::"; - C.FixIts = {FixIt}; - C.CompletionTokenRange.start.character = 5; - - CodeCompleteOptions Opts; - Opts.IncludeFixIts = true; - - auto R = C.render(Opts); - EXPECT_TRUE(R.textEdit); - EXPECT_EQ(R.textEdit->newText, "->Foo::x"); - EXPECT_TRUE(R.additionalTextEdits.empty()); -} - -TEST(CompletionTest, RenderWithFixItNonMerged) { - TextEdit FixIt; - FixIt.range.end.character = 4; - FixIt.newText = "->"; - - CodeCompletion C; - C.Name = "x"; - C.RequiredQualifier = "Foo::"; - C.FixIts = {FixIt}; - C.CompletionTokenRange.start.character = 5; - - CodeCompleteOptions Opts; - Opts.IncludeFixIts = true; - - auto R = C.render(Opts); - EXPECT_TRUE(R.textEdit); - EXPECT_EQ(R.textEdit->newText, "Foo::x"); - EXPECT_THAT(R.additionalTextEdits, UnorderedElementsAre(FixIt)); -} - -TEST(CompletionTest, CompletionTokenRange) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - constexpr const char *TestCodes[] = { - R"cpp( - class Auxilary { - public: - void AuxFunction(); - }; - void f() { - Auxilary x; - x.[[Aux]]^; - } - )cpp", - R"cpp( - class Auxilary { - public: - void AuxFunction(); - }; - void f() { - Auxilary x; - x.[[]]^; - } - )cpp"}; - for (const auto &Text : TestCodes) { - Annotations TestCode(Text); - auto Results = completions(Server, TestCode.code(), TestCode.point()); - - EXPECT_EQ(Results.Completions.size(), 1u); - EXPECT_THAT(Results.Completions.front().CompletionTokenRange, - TestCode.range()); - } -} - -TEST(SignatureHelpTest, OverloadsOrdering) { - const auto Results = signatures(R"cpp( - void foo(int x); - void foo(int x, float y); - void foo(float x, int y); - void foo(float x, float y); - void foo(int x, int y = 0); - int main() { foo(^); } - )cpp"); - EXPECT_THAT( - Results.signatures, - ElementsAre( - Sig("foo(int x) -> void", {"int x"}), - Sig("foo(int x, int y = 0) -> void", {"int x", "int y = 0"}), - Sig("foo(float x, int y) -> void", {"float x", "int y"}), - Sig("foo(int x, float y) -> void", {"int x", "float y"}), - Sig("foo(float x, float y) -> void", {"float x", "float y"}))); - // We always prefer the first signature. - EXPECT_EQ(0, Results.activeSignature); - EXPECT_EQ(0, Results.activeParameter); -} - -TEST(SignatureHelpTest, InstantiatedSignatures) { - StringRef Sig0 = R"cpp( - template <class T> - void foo(T, T, T); - - int main() { - foo<int>(^); - } - )cpp"; - - EXPECT_THAT(signatures(Sig0).signatures, - ElementsAre(Sig("foo(T, T, T) -> void", {"T", "T", "T"}))); - - StringRef Sig1 = R"cpp( - template <class T> - void foo(T, T, T); - - int main() { - foo(10, ^); - })cpp"; - - EXPECT_THAT(signatures(Sig1).signatures, - ElementsAre(Sig("foo(T, T, T) -> void", {"T", "T", "T"}))); - - StringRef Sig2 = R"cpp( - template <class ...T> - void foo(T...); - - int main() { - foo<int>(^); - } - )cpp"; - - EXPECT_THAT(signatures(Sig2).signatures, - ElementsAre(Sig("foo(T...) -> void", {"T..."}))); - - // It is debatable whether we should substitute the outer template parameter - // ('T') in that case. Currently we don't substitute it in signature help, but - // do substitute in code complete. - // FIXME: make code complete and signature help consistent, figure out which - // way is better. - StringRef Sig3 = R"cpp( - template <class T> - struct X { - template <class U> - void foo(T, U); - }; - - int main() { - X<int>().foo<double>(^) - } - )cpp"; - - EXPECT_THAT(signatures(Sig3).signatures, - ElementsAre(Sig("foo(T, U) -> void", {"T", "U"}))); -} - -TEST(SignatureHelpTest, IndexDocumentation) { - Symbol Foo0 = sym("foo", index::SymbolKind::Function, "@F@\\0#"); - Foo0.Documentation = "Doc from the index"; - Symbol Foo1 = sym("foo", index::SymbolKind::Function, "@F@\\0#I#"); - Foo1.Documentation = "Doc from the index"; - Symbol Foo2 = sym("foo", index::SymbolKind::Function, "@F@\\0#I#I#"); - - StringRef Sig0 = R"cpp( - int foo(); - int foo(double); - - void test() { - foo(^); - } - )cpp"; - - EXPECT_THAT( - signatures(Sig0, {Foo0}).signatures, - ElementsAre(AllOf(Sig("foo() -> int", {}), SigDoc("Doc from the index")), - AllOf(Sig("foo(double) -> int", {"double"}), SigDoc("")))); - - StringRef Sig1 = R"cpp( - int foo(); - // Overriden doc from sema - int foo(int); - // Doc from sema - int foo(int, int); - - void test() { - foo(^); - } - )cpp"; - - EXPECT_THAT( - signatures(Sig1, {Foo0, Foo1, Foo2}).signatures, - ElementsAre(AllOf(Sig("foo() -> int", {}), SigDoc("Doc from the index")), - AllOf(Sig("foo(int) -> int", {"int"}), - SigDoc("Overriden doc from sema")), - AllOf(Sig("foo(int, int) -> int", {"int", "int"}), - SigDoc("Doc from sema")))); -} - -TEST(SignatureHelpTest, DynamicIndexDocumentation) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer::Options Opts = ClangdServer::optsForTest(); - Opts.BuildDynamicSymbolIndex = true; - ClangdServer Server(CDB, FS, DiagConsumer, Opts); - - FS.Files[testPath("foo.h")] = R"cpp( - struct Foo { - // Member doc - int foo(); - }; - )cpp"; - Annotations FileContent(R"cpp( - #include "foo.h" - void test() { - Foo f; - f.foo(^); - } - )cpp"); - auto File = testPath("test.cpp"); - Server.addDocument(File, FileContent.code()); - // Wait for the dynamic index being built. - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_THAT( - llvm::cantFail(runSignatureHelp(Server, File, FileContent.point())) - .signatures, - ElementsAre(AllOf(Sig("foo() -> int", {}), SigDoc("Member doc")))); -} - -TEST(CompletionTest, CompletionFunctionArgsDisabled) { - CodeCompleteOptions Opts; - Opts.EnableSnippets = true; - Opts.EnableFunctionArgSnippets = false; - - { - auto Results = completions( - R"cpp( - void xfoo(); - void xfoo(int x, int y); - void f() { xfo^ })cpp", - {}, Opts); - EXPECT_THAT( - Results.Completions, - UnorderedElementsAre(AllOf(Named("xfoo"), SnippetSuffix("()")), - AllOf(Named("xfoo"), SnippetSuffix("($0)")))); - } - { - auto Results = completions( - R"cpp( - void xbar(); - void f() { xba^ })cpp", - {}, Opts); - EXPECT_THAT(Results.Completions, UnorderedElementsAre(AllOf( - Named("xbar"), SnippetSuffix("()")))); - } - { - Opts.BundleOverloads = true; - auto Results = completions( - R"cpp( - void xfoo(); - void xfoo(int x, int y); - void f() { xfo^ })cpp", - {}, Opts); - EXPECT_THAT( - Results.Completions, - UnorderedElementsAre(AllOf(Named("xfoo"), SnippetSuffix("($0)")))); - } - { - auto Results = completions( - R"cpp( - template <class T, class U> - void xfoo(int a, U b); - void f() { xfo^ })cpp", - {}, Opts); - EXPECT_THAT( - Results.Completions, - UnorderedElementsAre(AllOf(Named("xfoo"), SnippetSuffix("<$1>($0)")))); - } - { - auto Results = completions( - R"cpp( - template <class T> - class foo_class{}; - template <class T> - using foo_alias = T**; - void f() { foo_^ })cpp", - {}, Opts); - EXPECT_THAT( - Results.Completions, - UnorderedElementsAre(AllOf(Named("foo_class"), SnippetSuffix("<$0>")), - AllOf(Named("foo_alias"), SnippetSuffix("<$0>")))); - } -} - -TEST(CompletionTest, SuggestOverrides) { - constexpr const char *const Text(R"cpp( - class A { - public: - virtual void vfunc(bool param); - virtual void vfunc(bool param, int p); - void func(bool param); - }; - class B : public A { - virtual void ttt(bool param) const; - void vfunc(bool param, int p) override; - }; - class C : public B { - public: - void vfunc(bool param) override; - ^ - }; - )cpp"); - const auto Results = completions(Text); - EXPECT_THAT(Results.Completions, - AllOf(Contains(Labeled("void vfunc(bool param, int p) override")), - Contains(Labeled("void ttt(bool param) const override")), - Not(Contains(Labeled("void vfunc(bool param) override"))))); -} - -TEST(CompletionTest, OverridesNonIdentName) { - // Check the completions call does not crash. - completions(R"cpp( - struct Base { - virtual ~Base() = 0; - virtual operator int() = 0; - virtual Base& operator+(Base&) = 0; - }; - - struct Derived : Base { - ^ - }; - )cpp"); -} - -TEST(GuessCompletionPrefix, Filters) { - for (llvm::StringRef Case : { - "[[scope::]][[ident]]^", - "[[]][[]]^", - "\n[[]][[]]^", - "[[]][[ab]]^", - "x.[[]][[ab]]^", - "x.[[]][[]]^", - "[[x::]][[ab]]^", - "[[x::]][[]]^", - "[[::x::]][[ab]]^", - "some text [[scope::more::]][[identif]]^ier", - "some text [[scope::]][[mor]]^e::identifier", - "weird case foo::[[::bar::]][[baz]]^", - }) { - Annotations F(Case); - auto Offset = cantFail(positionToOffset(F.code(), F.point())); - auto ToStringRef = [&](Range R) { - return F.code().slice(cantFail(positionToOffset(F.code(), R.start)), - cantFail(positionToOffset(F.code(), R.end))); - }; - auto WantQualifier = ToStringRef(F.ranges()[0]), - WantName = ToStringRef(F.ranges()[1]); - - auto Prefix = guessCompletionPrefix(F.code(), Offset); - // Even when components are empty, check their offsets are correct. - EXPECT_EQ(WantQualifier, Prefix.Qualifier) << Case; - EXPECT_EQ(WantQualifier.begin(), Prefix.Qualifier.begin()) << Case; - EXPECT_EQ(WantName, Prefix.Name) << Case; - EXPECT_EQ(WantName.begin(), Prefix.Name.begin()) << Case; - } -} - -TEST(CompletionTest, EnableSpeculativeIndexRequest) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto File = testPath("foo.cpp"); - Annotations Test(R"cpp( - namespace ns1 { int abc; } - namespace ns2 { int abc; } - void f() { ns1::ab$1^; ns1::ab$2^; } - void f2() { ns2::ab$3^; } - )cpp"); - runAddDocument(Server, File, Test.code()); - clangd::CodeCompleteOptions Opts = {}; - - IndexRequestCollector Requests; - Opts.Index = &Requests; - Opts.SpeculativeIndexRequest = true; - - auto CompleteAtPoint = [&](StringRef P) { - cantFail(runCodeComplete(Server, File, Test.point(P), Opts)); - // Sleep for a while to make sure asynchronous call (if applicable) is also - // triggered before callback is invoked. - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - }; - - CompleteAtPoint("1"); - auto Reqs1 = Requests.consumeRequests(); - ASSERT_EQ(Reqs1.size(), 1u); - EXPECT_THAT(Reqs1[0].Scopes, UnorderedElementsAre("ns1::")); - - CompleteAtPoint("2"); - auto Reqs2 = Requests.consumeRequests(); - // Speculation succeeded. Used speculative index result. - ASSERT_EQ(Reqs2.size(), 1u); - EXPECT_EQ(Reqs2[0], Reqs1[0]); - - CompleteAtPoint("3"); - // Speculation failed. Sent speculative index request and the new index - // request after sema. - auto Reqs3 = Requests.consumeRequests(); - ASSERT_EQ(Reqs3.size(), 2u); -} - -TEST(CompletionTest, InsertTheMostPopularHeader) { - std::string DeclFile = URI::create(testPath("foo")).toString(); - Symbol sym = func("Func"); - sym.CanonicalDeclaration.FileURI = DeclFile.c_str(); - sym.IncludeHeaders.emplace_back("\"foo.h\"", 2); - sym.IncludeHeaders.emplace_back("\"bar.h\"", 1000); - - auto Results = completions("Fun^", {sym}).Completions; - assert(!Results.empty()); - EXPECT_THAT(Results[0], AllOf(Named("Func"), InsertInclude("\"bar.h\""))); - EXPECT_EQ(Results[0].Includes.size(), 2u); -} - -TEST(CompletionTest, NoInsertIncludeIfOnePresent) { - MockFSProvider FS; - MockCompilationDatabase CDB; - - std::string FooHeader = testPath("foo.h"); - FS.Files[FooHeader] = ""; - - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - std::string DeclFile = URI::create(testPath("foo")).toString(); - Symbol sym = func("Func"); - sym.CanonicalDeclaration.FileURI = DeclFile.c_str(); - sym.IncludeHeaders.emplace_back("\"foo.h\"", 2); - sym.IncludeHeaders.emplace_back("\"bar.h\"", 1000); - - EXPECT_THAT( - completions(Server, "#include \"foo.h\"\nFun^", {sym}).Completions, - UnorderedElementsAre( - AllOf(Named("Func"), HasInclude("\"foo.h\""), Not(InsertInclude())))); -} - -TEST(CompletionTest, MergeMacrosFromIndexAndSema) { - Symbol Sym; - Sym.Name = "Clangd_Macro_Test"; - Sym.ID = SymbolID("c:foo.cpp@8@macro@Clangd_Macro_Test"); - Sym.SymInfo.Kind = index::SymbolKind::Macro; - Sym.Flags |= Symbol::IndexedForCodeCompletion; - EXPECT_THAT(completions("#define Clangd_Macro_Test\nClangd_Macro_T^", {Sym}) - .Completions, - UnorderedElementsAre(Named("Clangd_Macro_Test"))); -} - -TEST(CompletionTest, NoMacroFromPreambleIfIndexIsSet) { - auto Results = completions( - R"cpp(#define CLANGD_PREAMBLE x - - int x = 0; - #define CLANGD_MAIN x - void f() { CLANGD_^ } - )cpp", - {func("CLANGD_INDEX")}); - // Index is overriden in code completion options, so the preamble symbol is - // not seen. - EXPECT_THAT(Results.Completions, UnorderedElementsAre(Named("CLANGD_MAIN"), - Named("CLANGD_INDEX"))); -} - -TEST(CompletionTest, DeprecatedResults) { - std::string Body = R"cpp( - void TestClangd(); - void TestClangc() __attribute__((deprecated("", ""))); - )cpp"; - - EXPECT_THAT( - completions(Body + "int main() { TestClang^ }").Completions, - UnorderedElementsAre(AllOf(Named("TestClangd"), Not(Deprecated())), - AllOf(Named("TestClangc"), Deprecated()))); -} - -TEST(SignatureHelpTest, InsideArgument) { - { - const auto Results = signatures(R"cpp( - void foo(int x); - void foo(int x, int y); - int main() { foo(1+^); } - )cpp"); - EXPECT_THAT( - Results.signatures, - ElementsAre(Sig("foo(int x) -> void", {"int x"}), - Sig("foo(int x, int y) -> void", {"int x", "int y"}))); - EXPECT_EQ(0, Results.activeParameter); - } - { - const auto Results = signatures(R"cpp( - void foo(int x); - void foo(int x, int y); - int main() { foo(1^); } - )cpp"); - EXPECT_THAT( - Results.signatures, - ElementsAre(Sig("foo(int x) -> void", {"int x"}), - Sig("foo(int x, int y) -> void", {"int x", "int y"}))); - EXPECT_EQ(0, Results.activeParameter); - } - { - const auto Results = signatures(R"cpp( - void foo(int x); - void foo(int x, int y); - int main() { foo(1^0); } - )cpp"); - EXPECT_THAT( - Results.signatures, - ElementsAre(Sig("foo(int x) -> void", {"int x"}), - Sig("foo(int x, int y) -> void", {"int x", "int y"}))); - EXPECT_EQ(0, Results.activeParameter); - } - { - const auto Results = signatures(R"cpp( - void foo(int x); - void foo(int x, int y); - int bar(int x, int y); - int main() { bar(foo(2, 3^)); } - )cpp"); - EXPECT_THAT(Results.signatures, ElementsAre(Sig("foo(int x, int y) -> void", - {"int x", "int y"}))); - EXPECT_EQ(1, Results.activeParameter); - } -} - -TEST(SignatureHelpTest, ConstructorInitializeFields) { - { - const auto Results = signatures(R"cpp( - struct A { - A(int); - }; - struct B { - B() : a_elem(^) {} - A a_elem; - }; - )cpp"); - EXPECT_THAT(Results.signatures, - UnorderedElementsAre(Sig("A(int)", {"int"}), - Sig("A(A &&)", {"A &&"}), - Sig("A(const A &)", {"const A &"}))); - } - { - const auto Results = signatures(R"cpp( - struct A { - A(int); - }; - struct C { - C(int); - C(A); - }; - struct B { - B() : c_elem(A(1^)) {} - C c_elem; - }; - )cpp"); - EXPECT_THAT(Results.signatures, - UnorderedElementsAre(Sig("A(int)", {"int"}), - Sig("A(A &&)", {"A &&"}), - Sig("A(const A &)", {"const A &"}))); - } -} - -TEST(CompletionTest, IncludedCompletionKinds) { - MockFSProvider FS; - MockCompilationDatabase CDB; - std::string Subdir = testPath("sub"); - std::string SearchDirArg = (Twine("-I") + Subdir).str(); - CDB.ExtraClangFlags = {SearchDirArg.c_str()}; - std::string BarHeader = testPath("sub/bar.h"); - FS.Files[BarHeader] = ""; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - auto Results = completions(Server, - R"cpp( - #include "^" - )cpp"); - EXPECT_THAT(Results.Completions, - AllOf(Has("sub/", CompletionItemKind::Folder), - Has("bar.h\"", CompletionItemKind::File))); -} - -TEST(CompletionTest, NoCrashAtNonAlphaIncludeHeader) { - auto Results = completions( - R"cpp( - #include "./^" - )cpp"); - EXPECT_TRUE(Results.Completions.empty()); -} - -TEST(CompletionTest, NoAllScopesCompletionWhenQualified) { - clangd::CodeCompleteOptions Opts = {}; - Opts.AllScopes = true; - - auto Results = completions( - R"cpp( - void f() { na::Clangd^ } - )cpp", - {cls("na::ClangdA"), cls("nx::ClangdX"), cls("Clangd3")}, Opts); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre( - AllOf(Qualifier(""), Scope("na::"), Named("ClangdA")))); -} - -TEST(CompletionTest, AllScopesCompletion) { - clangd::CodeCompleteOptions Opts = {}; - Opts.AllScopes = true; - - auto Results = completions( - R"cpp( - namespace na { - void f() { Clangd^ } - } - )cpp", - {cls("nx::Clangd1"), cls("ny::Clangd2"), cls("Clangd3"), - cls("na::nb::Clangd4")}, - Opts); - EXPECT_THAT( - Results.Completions, - UnorderedElementsAre(AllOf(Qualifier("nx::"), Named("Clangd1")), - AllOf(Qualifier("ny::"), Named("Clangd2")), - AllOf(Qualifier(""), Scope(""), Named("Clangd3")), - AllOf(Qualifier("nb::"), Named("Clangd4")))); -} - -TEST(CompletionTest, NoQualifierIfShadowed) { - clangd::CodeCompleteOptions Opts = {}; - Opts.AllScopes = true; - - auto Results = completions(R"cpp( - namespace nx { class Clangd1 {}; } - using nx::Clangd1; - void f() { Clangd^ } - )cpp", - {cls("nx::Clangd1"), cls("nx::Clangd2")}, Opts); - // Although Clangd1 is from another namespace, Sema tells us it's in-scope and - // needs no qualifier. - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(AllOf(Qualifier(""), Named("Clangd1")), - AllOf(Qualifier("nx::"), Named("Clangd2")))); -} - -TEST(CompletionTest, NoCompletionsForNewNames) { - clangd::CodeCompleteOptions Opts; - Opts.AllScopes = true; - auto Results = completions(R"cpp( - void f() { int n^ } - )cpp", - {cls("naber"), cls("nx::naber")}, Opts); - EXPECT_THAT(Results.Completions, UnorderedElementsAre()); -} - -TEST(CompletionTest, ObjectiveCMethodNoArguments) { - auto Results = completions(R"objc( - @interface Foo - @property(nonatomic, setter=setXToIgnoreComplete:) int value; - @end - Foo *foo = [Foo new]; int y = [foo v^] - )objc", - /*IndexSymbols=*/{}, - /*Opts=*/{}, "Foo.m"); - - auto C = Results.Completions; - EXPECT_THAT(C, ElementsAre(Named("value"))); - EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method))); - EXPECT_THAT(C, ElementsAre(ReturnType("int"))); - EXPECT_THAT(C, ElementsAre(Signature(""))); - EXPECT_THAT(C, ElementsAre(SnippetSuffix(""))); -} - -TEST(CompletionTest, ObjectiveCMethodOneArgument) { - auto Results = completions(R"objc( - @interface Foo - - (int)valueForCharacter:(char)c; - @end - Foo *foo = [Foo new]; int y = [foo v^] - )objc", - /*IndexSymbols=*/{}, - /*Opts=*/{}, "Foo.m"); - - auto C = Results.Completions; - EXPECT_THAT(C, ElementsAre(Named("valueForCharacter:"))); - EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method))); - EXPECT_THAT(C, ElementsAre(ReturnType("int"))); - EXPECT_THAT(C, ElementsAre(Signature("(char)"))); - EXPECT_THAT(C, ElementsAre(SnippetSuffix("${1:(char)}"))); -} - -TEST(CompletionTest, ObjectiveCMethodTwoArgumentsFromBeginning) { - auto Results = completions(R"objc( - @interface Foo - + (id)fooWithValue:(int)value fooey:(unsigned int)fooey; - @end - id val = [Foo foo^] - )objc", - /*IndexSymbols=*/{}, - /*Opts=*/{}, "Foo.m"); - - auto C = Results.Completions; - EXPECT_THAT(C, ElementsAre(Named("fooWithValue:"))); - EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method))); - EXPECT_THAT(C, ElementsAre(ReturnType("id"))); - EXPECT_THAT(C, ElementsAre(Signature("(int) fooey:(unsigned int)"))); - EXPECT_THAT( - C, ElementsAre(SnippetSuffix("${1:(int)} fooey:${2:(unsigned int)}"))); -} - -TEST(CompletionTest, ObjectiveCMethodTwoArgumentsFromMiddle) { - auto Results = completions(R"objc( - @interface Foo - + (id)fooWithValue:(int)value fooey:(unsigned int)fooey; - @end - id val = [Foo fooWithValue:10 f^] - )objc", - /*IndexSymbols=*/{}, - /*Opts=*/{}, "Foo.m"); - - auto C = Results.Completions; - EXPECT_THAT(C, ElementsAre(Named("fooey:"))); - EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method))); - EXPECT_THAT(C, ElementsAre(ReturnType("id"))); - EXPECT_THAT(C, ElementsAre(Signature("(unsigned int)"))); - EXPECT_THAT(C, ElementsAre(SnippetSuffix("${1:(unsigned int)}"))); -} - -TEST(CompletionTest, WorksWithNullType) { - auto R = completions(R"cpp( - int main() { - for (auto [loopVar] : y ) { // y has to be unresolved. - int z = loopV^; - } - } - )cpp"); - EXPECT_THAT(R.Completions, ElementsAre(Named("loopVar"))); -} - -TEST(CompletionTest, UsingDecl) { - const char *Header(R"cpp( - void foo(int); - namespace std { - using ::foo; - })cpp"); - const char *Source(R"cpp( - void bar() { - std::^; - })cpp"); - auto Index = TestTU::withHeaderCode(Header).index(); - clangd::CodeCompleteOptions Opts; - Opts.Index = Index.get(); - Opts.AllScopes = true; - auto R = completions(Source, {}, Opts); - EXPECT_THAT(R.Completions, - ElementsAre(AllOf(Scope("std::"), Named("foo"), - Kind(CompletionItemKind::Reference)))); -} - -TEST(CompletionTest, ScopeIsUnresolved) { - clangd::CodeCompleteOptions Opts = {}; - Opts.AllScopes = true; - - auto Results = completions(R"cpp( - namespace a { - void f() { b::X^ } - } - )cpp", - {cls("a::b::XYZ")}, Opts); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(AllOf(Qualifier(""), Named("XYZ")))); -} - -TEST(CompletionTest, NestedScopeIsUnresolved) { - clangd::CodeCompleteOptions Opts = {}; - Opts.AllScopes = true; - - auto Results = completions(R"cpp( - namespace a { - namespace b {} - void f() { b::c::X^ } - } - )cpp", - {cls("a::b::c::XYZ")}, Opts); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(AllOf(Qualifier(""), Named("XYZ")))); -} - -// Clang parser gets confused here and doesn't report the ns:: prefix. -// Naive behavior is to insert it again. We examine the source and recover. -TEST(CompletionTest, NamespaceDoubleInsertion) { - clangd::CodeCompleteOptions Opts = {}; - - auto Results = completions(R"cpp( - namespace foo { - namespace ns {} - #define M(X) < X - M(ns::ABC^ - } - )cpp", - {cls("foo::ns::ABCDE")}, Opts); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(AllOf(Qualifier(""), Named("ABCDE")))); -} - -TEST(NoCompileCompletionTest, Basic) { - auto Results = completionsNoCompile(R"cpp( - void func() { - int xyz; - int abc; - ^ - } - )cpp"); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(Named("void"), Named("func"), Named("int"), - Named("xyz"), Named("abc"))); -} - -TEST(NoCompileCompletionTest, WithFilter) { - auto Results = completionsNoCompile(R"cpp( - void func() { - int sym1; - int sym2; - int xyz1; - int xyz2; - sy^ - } - )cpp"); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(Named("sym1"), Named("sym2"))); -} - -TEST(NoCompileCompletionTest, WithIndex) { - std::vector<Symbol> Syms = {func("xxx"), func("a::xxx"), func("ns::b::xxx"), - func("c::xxx"), func("ns::d::xxx")}; - auto Results = completionsNoCompile( - R"cpp( - // Current-scopes, unqualified completion. - using namespace a; - namespace ns { - using namespace b; - void foo() { - xx^ - } - )cpp", - Syms); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(AllOf(Qualifier(""), Scope("")), - AllOf(Qualifier(""), Scope("a::")), - AllOf(Qualifier(""), Scope("ns::b::")))); - CodeCompleteOptions Opts; - Opts.AllScopes = true; - Results = completionsNoCompile( - R"cpp( - // All-scopes unqualified completion. - using namespace a; - namespace ns { - using namespace b; - void foo() { - xx^ - } - )cpp", - Syms, Opts); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(AllOf(Qualifier(""), Scope("")), - AllOf(Qualifier(""), Scope("a::")), - AllOf(Qualifier(""), Scope("ns::b::")), - AllOf(Qualifier("c::"), Scope("c::")), - AllOf(Qualifier("d::"), Scope("ns::d::")))); - Results = completionsNoCompile( - R"cpp( - // Qualified completion. - using namespace a; - namespace ns { - using namespace b; - void foo() { - b::xx^ - } - )cpp", - Syms, Opts); - EXPECT_THAT(Results.Completions, - ElementsAre(AllOf(Qualifier(""), Scope("ns::b::")))); - Results = completionsNoCompile( - R"cpp( - // Absolutely qualified completion. - using namespace a; - namespace ns { - using namespace b; - void foo() { - ::a::xx^ - } - )cpp", - Syms, Opts); - EXPECT_THAT(Results.Completions, - ElementsAre(AllOf(Qualifier(""), Scope("a::")))); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/CodeCompletionStringsTests.cpp b/clang-tools-extra/unittests/clangd/CodeCompletionStringsTests.cpp deleted file mode 100644 index 43429c86465..00000000000 --- a/clang-tools-extra/unittests/clangd/CodeCompletionStringsTests.cpp +++ /dev/null @@ -1,160 +0,0 @@ -//===-- CodeCompletionStringsTests.cpp --------------------------*- C++ -*-===// -// -// 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 "CodeCompletionStrings.h" -#include "clang/Sema/CodeCompleteConsumer.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -class CompletionStringTest : public ::testing::Test { -public: - CompletionStringTest() - : Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()), - CCTUInfo(Allocator), Builder(*Allocator, CCTUInfo) {} - -protected: - void computeSignature(const CodeCompletionString &CCS) { - Signature.clear(); - Snippet.clear(); - getSignature(CCS, &Signature, &Snippet); - } - - std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator; - CodeCompletionTUInfo CCTUInfo; - CodeCompletionBuilder Builder; - std::string Signature; - std::string Snippet; -}; - -TEST_F(CompletionStringTest, ReturnType) { - Builder.AddResultTypeChunk("result"); - Builder.AddResultTypeChunk("redundant result no no"); - EXPECT_EQ(getReturnType(*Builder.TakeString()), "result"); -} - -TEST_F(CompletionStringTest, Documentation) { - Builder.addBriefComment("This is ignored"); - EXPECT_EQ(formatDocumentation(*Builder.TakeString(), "Is this brief?"), - "Is this brief?"); -} - -TEST_F(CompletionStringTest, DocumentationWithAnnotation) { - Builder.addBriefComment("This is ignored"); - Builder.AddAnnotation("Ano"); - EXPECT_EQ(formatDocumentation(*Builder.TakeString(), "Is this brief?"), - "Annotation: Ano\n\nIs this brief?"); -} - -TEST_F(CompletionStringTest, MultipleAnnotations) { - Builder.AddAnnotation("Ano1"); - Builder.AddAnnotation("Ano2"); - Builder.AddAnnotation("Ano3"); - - EXPECT_EQ(formatDocumentation(*Builder.TakeString(), ""), - "Annotations: Ano1 Ano2 Ano3\n"); -} - -TEST_F(CompletionStringTest, EmptySignature) { - Builder.AddTypedTextChunk("X"); - Builder.AddResultTypeChunk("result no no"); - computeSignature(*Builder.TakeString()); - EXPECT_EQ(Signature, ""); - EXPECT_EQ(Snippet, ""); -} - -TEST_F(CompletionStringTest, Function) { - Builder.AddResultTypeChunk("result no no"); - Builder.addBriefComment("This comment is ignored"); - Builder.AddTypedTextChunk("Foo"); - Builder.AddChunk(CodeCompletionString::CK_LeftParen); - Builder.AddPlaceholderChunk("p1"); - Builder.AddChunk(CodeCompletionString::CK_Comma); - Builder.AddPlaceholderChunk("p2"); - Builder.AddChunk(CodeCompletionString::CK_RightParen); - - auto *CCS = Builder.TakeString(); - computeSignature(*CCS); - EXPECT_EQ(Signature, "(p1, p2)"); - EXPECT_EQ(Snippet, "(${1:p1}, ${2:p2})"); - EXPECT_EQ(formatDocumentation(*CCS, "Foo's comment"), "Foo's comment"); -} - -TEST_F(CompletionStringTest, EscapeSnippet) { - Builder.AddTypedTextChunk("Foo"); - Builder.AddChunk(CodeCompletionString::CK_LeftParen); - Builder.AddPlaceholderChunk("$p}1\\"); - Builder.AddChunk(CodeCompletionString::CK_RightParen); - - computeSignature(*Builder.TakeString()); - EXPECT_EQ(Signature, "($p}1\\)"); - EXPECT_EQ(Snippet, "(${1:\\$p\\}1\\\\})"); -} - -TEST_F(CompletionStringTest, IgnoreInformativeQualifier) { - Builder.AddTypedTextChunk("X"); - Builder.AddInformativeChunk("info ok"); - Builder.AddInformativeChunk("info no no::"); - computeSignature(*Builder.TakeString()); - EXPECT_EQ(Signature, "info ok"); - EXPECT_EQ(Snippet, ""); -} - -TEST_F(CompletionStringTest, ObjectiveCMethodNoArguments) { - Builder.AddResultTypeChunk("void"); - Builder.AddTypedTextChunk("methodName"); - - auto *CCS = Builder.TakeString(); - computeSignature(*CCS); - EXPECT_EQ(Signature, ""); - EXPECT_EQ(Snippet, ""); -} - -TEST_F(CompletionStringTest, ObjectiveCMethodOneArgument) { - Builder.AddResultTypeChunk("void"); - Builder.AddTypedTextChunk("methodWithArg:"); - Builder.AddPlaceholderChunk("(type)"); - - auto *CCS = Builder.TakeString(); - computeSignature(*CCS); - EXPECT_EQ(Signature, "(type)"); - EXPECT_EQ(Snippet, "${1:(type)}"); -} - -TEST_F(CompletionStringTest, ObjectiveCMethodTwoArgumentsFromBeginning) { - Builder.AddResultTypeChunk("int"); - Builder.AddTypedTextChunk("withFoo:"); - Builder.AddPlaceholderChunk("(type)"); - Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace); - Builder.AddTypedTextChunk("bar:"); - Builder.AddPlaceholderChunk("(type2)"); - - auto *CCS = Builder.TakeString(); - computeSignature(*CCS); - EXPECT_EQ(Signature, "(type) bar:(type2)"); - EXPECT_EQ(Snippet, "${1:(type)} bar:${2:(type2)}"); -} - -TEST_F(CompletionStringTest, ObjectiveCMethodTwoArgumentsFromMiddle) { - Builder.AddResultTypeChunk("int"); - Builder.AddInformativeChunk("withFoo:"); - Builder.AddTypedTextChunk("bar:"); - Builder.AddPlaceholderChunk("(type2)"); - - auto *CCS = Builder.TakeString(); - computeSignature(*CCS); - EXPECT_EQ(Signature, "(type2)"); - EXPECT_EQ(Snippet, "${1:(type2)}"); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/ContextTests.cpp b/clang-tools-extra/unittests/clangd/ContextTests.cpp deleted file mode 100644 index d760f4eb987..00000000000 --- a/clang-tools-extra/unittests/clangd/ContextTests.cpp +++ /dev/null @@ -1,56 +0,0 @@ -//===-- ContextTests.cpp - Context tests ------------------------*- C++ -*-===// -// -// 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 "Context.h" - -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { - -TEST(ContextTests, Simple) { - Key<int> IntParam; - Key<int> ExtraIntParam; - - Context Ctx = Context::empty().derive(IntParam, 10).derive(ExtraIntParam, 20); - - EXPECT_EQ(*Ctx.get(IntParam), 10); - EXPECT_EQ(*Ctx.get(ExtraIntParam), 20); -} - -TEST(ContextTests, MoveOps) { - Key<std::unique_ptr<int>> Param; - - Context Ctx = Context::empty().derive(Param, llvm::make_unique<int>(10)); - EXPECT_EQ(**Ctx.get(Param), 10); - - Context NewCtx = std::move(Ctx); - EXPECT_EQ(**NewCtx.get(Param), 10); -} - -TEST(ContextTests, Builders) { - Key<int> ParentParam; - Key<int> ParentAndChildParam; - Key<int> ChildParam; - - Context ParentCtx = - Context::empty().derive(ParentParam, 10).derive(ParentAndChildParam, 20); - Context ChildCtx = - ParentCtx.derive(ParentAndChildParam, 30).derive(ChildParam, 40); - - EXPECT_EQ(*ParentCtx.get(ParentParam), 10); - EXPECT_EQ(*ParentCtx.get(ParentAndChildParam), 20); - EXPECT_EQ(ParentCtx.get(ChildParam), nullptr); - - EXPECT_EQ(*ChildCtx.get(ParentParam), 10); - EXPECT_EQ(*ChildCtx.get(ParentAndChildParam), 30); - EXPECT_EQ(*ChildCtx.get(ChildParam), 40); -} - -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/DexTests.cpp b/clang-tools-extra/unittests/clangd/DexTests.cpp deleted file mode 100644 index da744f11d1e..00000000000 --- a/clang-tools-extra/unittests/clangd/DexTests.cpp +++ /dev/null @@ -1,753 +0,0 @@ -//===-- DexTests.cpp ---------------------------------*- C++ -*-----------===// -// -// 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 "FuzzyMatch.h" -#include "TestFS.h" -#include "TestIndex.h" -#include "index/Index.h" -#include "index/Merge.h" -#include "index/SymbolID.h" -#include "index/dex/Dex.h" -#include "index/dex/Iterator.h" -#include "index/dex/Token.h" -#include "index/dex/Trigram.h" -#include "llvm/Support/ScopedPrinter.h" -#include "llvm/Support/raw_ostream.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include <string> -#include <vector> - -using ::testing::AnyOf; -using ::testing::ElementsAre; -using ::testing::IsEmpty; -using ::testing::UnorderedElementsAre; - -namespace clang { -namespace clangd { -namespace dex { -namespace { - -//===----------------------------------------------------------------------===// -// Query iterator tests. -//===----------------------------------------------------------------------===// - -std::vector<DocID> consumeIDs(Iterator &It) { - auto IDAndScore = consume(It); - std::vector<DocID> IDs(IDAndScore.size()); - for (size_t I = 0; I < IDAndScore.size(); ++I) - IDs[I] = IDAndScore[I].first; - return IDs; -} - -TEST(DexIterators, DocumentIterator) { - const PostingList L({4, 7, 8, 20, 42, 100}); - auto DocIterator = L.iterator(); - - EXPECT_EQ(DocIterator->peek(), 4U); - EXPECT_FALSE(DocIterator->reachedEnd()); - - DocIterator->advance(); - EXPECT_EQ(DocIterator->peek(), 7U); - EXPECT_FALSE(DocIterator->reachedEnd()); - - DocIterator->advanceTo(20); - EXPECT_EQ(DocIterator->peek(), 20U); - EXPECT_FALSE(DocIterator->reachedEnd()); - - DocIterator->advanceTo(65); - EXPECT_EQ(DocIterator->peek(), 100U); - EXPECT_FALSE(DocIterator->reachedEnd()); - - DocIterator->advanceTo(420); - EXPECT_TRUE(DocIterator->reachedEnd()); -} - -TEST(DexIterators, AndTwoLists) { - Corpus C{10000}; - const PostingList L0({0, 5, 7, 10, 42, 320, 9000}); - const PostingList L1({0, 4, 7, 10, 30, 60, 320, 9000}); - - auto And = C.intersect(L1.iterator(), L0.iterator()); - - EXPECT_FALSE(And->reachedEnd()); - EXPECT_THAT(consumeIDs(*And), ElementsAre(0U, 7U, 10U, 320U, 9000U)); - - And = C.intersect(L0.iterator(), L1.iterator()); - - And->advanceTo(0); - EXPECT_EQ(And->peek(), 0U); - And->advanceTo(5); - EXPECT_EQ(And->peek(), 7U); - And->advanceTo(10); - EXPECT_EQ(And->peek(), 10U); - And->advanceTo(42); - EXPECT_EQ(And->peek(), 320U); - And->advanceTo(8999); - EXPECT_EQ(And->peek(), 9000U); - And->advanceTo(9001); -} - -TEST(DexIterators, AndThreeLists) { - Corpus C{10000}; - const PostingList L0({0, 5, 7, 10, 42, 320, 9000}); - const PostingList L1({0, 4, 7, 10, 30, 60, 320, 9000}); - const PostingList L2({1, 4, 7, 11, 30, 60, 320, 9000}); - - auto And = C.intersect(L0.iterator(), L1.iterator(), L2.iterator()); - EXPECT_EQ(And->peek(), 7U); - And->advanceTo(300); - EXPECT_EQ(And->peek(), 320U); - And->advanceTo(100000); - - EXPECT_TRUE(And->reachedEnd()); -} - -TEST(DexIterators, AndEmpty) { - Corpus C{10000}; - const PostingList L1{1}; - const PostingList L2{2}; - // These iterators are empty, but the optimizer can't tell. - auto Empty1 = C.intersect(L1.iterator(), L2.iterator()); - auto Empty2 = C.intersect(L1.iterator(), L2.iterator()); - // And syncs iterators on construction, and used to fail on empty children. - auto And = C.intersect(std::move(Empty1), std::move(Empty2)); - EXPECT_TRUE(And->reachedEnd()); -} - -TEST(DexIterators, OrTwoLists) { - Corpus C{10000}; - const PostingList L0({0, 5, 7, 10, 42, 320, 9000}); - const PostingList L1({0, 4, 7, 10, 30, 60, 320, 9000}); - - auto Or = C.unionOf(L0.iterator(), L1.iterator()); - - EXPECT_FALSE(Or->reachedEnd()); - EXPECT_EQ(Or->peek(), 0U); - Or->advance(); - EXPECT_EQ(Or->peek(), 4U); - Or->advance(); - EXPECT_EQ(Or->peek(), 5U); - Or->advance(); - EXPECT_EQ(Or->peek(), 7U); - Or->advance(); - EXPECT_EQ(Or->peek(), 10U); - Or->advance(); - EXPECT_EQ(Or->peek(), 30U); - Or->advanceTo(42); - EXPECT_EQ(Or->peek(), 42U); - Or->advanceTo(300); - EXPECT_EQ(Or->peek(), 320U); - Or->advanceTo(9000); - EXPECT_EQ(Or->peek(), 9000U); - Or->advanceTo(9001); - EXPECT_TRUE(Or->reachedEnd()); - - Or = C.unionOf(L0.iterator(), L1.iterator()); - - EXPECT_THAT(consumeIDs(*Or), - ElementsAre(0U, 4U, 5U, 7U, 10U, 30U, 42U, 60U, 320U, 9000U)); -} - -TEST(DexIterators, OrThreeLists) { - Corpus C{10000}; - const PostingList L0({0, 5, 7, 10, 42, 320, 9000}); - const PostingList L1({0, 4, 7, 10, 30, 60, 320, 9000}); - const PostingList L2({1, 4, 7, 11, 30, 60, 320, 9000}); - - auto Or = C.unionOf(L0.iterator(), L1.iterator(), L2.iterator()); - - EXPECT_FALSE(Or->reachedEnd()); - EXPECT_EQ(Or->peek(), 0U); - - Or->advance(); - EXPECT_EQ(Or->peek(), 1U); - - Or->advance(); - EXPECT_EQ(Or->peek(), 4U); - - Or->advanceTo(7); - - Or->advanceTo(59); - EXPECT_EQ(Or->peek(), 60U); - - Or->advanceTo(9001); - EXPECT_TRUE(Or->reachedEnd()); -} - -// FIXME(kbobyrev): The testcase below is similar to what is expected in real -// queries. It should be updated once new iterators (such as boosting, limiting, -// etc iterators) appear. However, it is not exhaustive and it would be -// beneficial to implement automatic generation (e.g. fuzzing) of query trees -// for more comprehensive testing. -TEST(DexIterators, QueryTree) { - // - // +-----------------+ - // |And Iterator:1, 5| - // +--------+--------+ - // | - // | - // +-------------+----------------------+ - // | | - // | | - // +----------v----------+ +----------v------------+ - // |And Iterator: 1, 5, 9| |Or Iterator: 0, 1, 3, 5| - // +----------+----------+ +----------+------------+ - // | | - // +------+-----+ ------------+ - // | | | | - // +-------v-----+ +----+---+ +---v----+ +----v---+ - // |1, 3, 5, 8, 9| |Boost: 2| |Boost: 3| |Boost: 4| - // +-------------+ +----+---+ +---+----+ +----+---+ - // | | | - // +----v-----+ +-v--+ +---v---+ - // |1, 5, 7, 9| |1, 5| |0, 3, 5| - // +----------+ +----+ +-------+ - // - Corpus C{10}; - const PostingList L0({1, 3, 5, 8, 9}); - const PostingList L1({1, 5, 7, 9}); - const PostingList L2({1, 5}); - const PostingList L3({0, 3, 5}); - - // Root of the query tree: [1, 5] - auto Root = C.intersect( - // Lower And Iterator: [1, 5, 9] - C.intersect(L0.iterator(), C.boost(L1.iterator(), 2U)), - // Lower Or Iterator: [0, 1, 5] - C.unionOf(C.boost(L2.iterator(), 3U), C.boost(L3.iterator(), 4U))); - - EXPECT_FALSE(Root->reachedEnd()); - EXPECT_EQ(Root->peek(), 1U); - Root->advanceTo(0); - // Advance multiple times. Shouldn't do anything. - Root->advanceTo(1); - Root->advanceTo(0); - EXPECT_EQ(Root->peek(), 1U); - auto ElementBoost = Root->consume(); - EXPECT_THAT(ElementBoost, 6); - Root->advance(); - EXPECT_EQ(Root->peek(), 5U); - Root->advanceTo(5); - EXPECT_EQ(Root->peek(), 5U); - ElementBoost = Root->consume(); - EXPECT_THAT(ElementBoost, 8); - Root->advanceTo(9000); - EXPECT_TRUE(Root->reachedEnd()); -} - -TEST(DexIterators, StringRepresentation) { - Corpus C{10}; - const PostingList L1({1, 3, 5}); - const PostingList L2({1, 7, 9}); - - // No token given, prints full posting list. - auto I1 = L1.iterator(); - EXPECT_EQ(llvm::to_string(*I1), "[1 3 5]"); - - // Token given, uses token's string representation. - Token Tok(Token::Kind::Trigram, "L2"); - auto I2 = L1.iterator(&Tok); - EXPECT_EQ(llvm::to_string(*I2), "T=L2"); - - auto Tree = C.limit(C.intersect(move(I1), move(I2)), 10); - // AND reorders its children, we don't care which order it prints. - EXPECT_THAT(llvm::to_string(*Tree), AnyOf("(LIMIT 10 (& [1 3 5] T=L2))", - "(LIMIT 10 (& T=L2 [1 3 5]))")); -} - -TEST(DexIterators, Limit) { - Corpus C{10000}; - const PostingList L0({3, 6, 7, 20, 42, 100}); - const PostingList L1({1, 3, 5, 6, 7, 30, 100}); - const PostingList L2({0, 3, 5, 7, 8, 100}); - - auto DocIterator = C.limit(L0.iterator(), 42); - EXPECT_THAT(consumeIDs(*DocIterator), ElementsAre(3, 6, 7, 20, 42, 100)); - - DocIterator = C.limit(L0.iterator(), 3); - EXPECT_THAT(consumeIDs(*DocIterator), ElementsAre(3, 6, 7)); - - DocIterator = C.limit(L0.iterator(), 0); - EXPECT_THAT(consumeIDs(*DocIterator), ElementsAre()); - - auto AndIterator = - C.intersect(C.limit(C.all(), 343), C.limit(L0.iterator(), 2), - C.limit(L1.iterator(), 3), C.limit(L2.iterator(), 42)); - EXPECT_THAT(consumeIDs(*AndIterator), ElementsAre(3, 7)); -} - -TEST(DexIterators, True) { - EXPECT_TRUE(Corpus{0}.all()->reachedEnd()); - EXPECT_THAT(consumeIDs(*Corpus{4}.all()), ElementsAre(0, 1, 2, 3)); -} - -TEST(DexIterators, Boost) { - Corpus C{5}; - auto BoostIterator = C.boost(C.all(), 42U); - EXPECT_FALSE(BoostIterator->reachedEnd()); - auto ElementBoost = BoostIterator->consume(); - EXPECT_THAT(ElementBoost, 42U); - - const PostingList L0({2, 4}); - const PostingList L1({1, 4}); - auto Root = C.unionOf(C.all(), C.boost(L0.iterator(), 2U), - C.boost(L1.iterator(), 3U)); - - ElementBoost = Root->consume(); - EXPECT_THAT(ElementBoost, 1); - Root->advance(); - EXPECT_THAT(Root->peek(), 1U); - ElementBoost = Root->consume(); - EXPECT_THAT(ElementBoost, 3); - - Root->advance(); - EXPECT_THAT(Root->peek(), 2U); - ElementBoost = Root->consume(); - EXPECT_THAT(ElementBoost, 2); - - Root->advanceTo(4); - ElementBoost = Root->consume(); - EXPECT_THAT(ElementBoost, 3); -} - -TEST(DexIterators, Optimizations) { - Corpus C{5}; - const PostingList L1{1}; - const PostingList L2{2}; - const PostingList L3{3}; - - // empty and/or yield true/false - EXPECT_EQ(llvm::to_string(*C.intersect()), "true"); - EXPECT_EQ(llvm::to_string(*C.unionOf()), "false"); - - // true/false inside and/or short-circuit - EXPECT_EQ(llvm::to_string(*C.intersect(L1.iterator(), C.all())), "[1]"); - EXPECT_EQ(llvm::to_string(*C.intersect(L1.iterator(), C.none())), "false"); - // Not optimized to avoid breaking boosts. - EXPECT_EQ(llvm::to_string(*C.unionOf(L1.iterator(), C.all())), - "(| [1] true)"); - EXPECT_EQ(llvm::to_string(*C.unionOf(L1.iterator(), C.none())), "[1]"); - - // and/or nested inside and/or are flattened - EXPECT_EQ(llvm::to_string(*C.intersect( - L1.iterator(), C.intersect(L1.iterator(), L1.iterator()))), - "(& [1] [1] [1])"); - EXPECT_EQ(llvm::to_string(*C.unionOf( - L1.iterator(), C.unionOf(L2.iterator(), L3.iterator()))), - "(| [1] [2] [3])"); - - // optimizations combine over multiple levels - EXPECT_EQ(llvm::to_string(*C.intersect( - C.intersect(L1.iterator(), C.intersect()), C.unionOf(C.all()))), - "[1]"); -} - -//===----------------------------------------------------------------------===// -// Search token tests. -//===----------------------------------------------------------------------===// - -testing::Matcher<std::vector<Token>> -tokensAre(std::initializer_list<std::string> Strings, Token::Kind Kind) { - std::vector<Token> Tokens; - for (const auto &TokenData : Strings) { - Tokens.push_back(Token(Kind, TokenData)); - } - return testing::UnorderedElementsAreArray(Tokens); -} - -testing::Matcher<std::vector<Token>> -trigramsAre(std::initializer_list<std::string> Trigrams) { - return tokensAre(Trigrams, Token::Kind::Trigram); -} - -TEST(DexTrigrams, IdentifierTrigrams) { - EXPECT_THAT(generateIdentifierTrigrams("X86"), - trigramsAre({"x86", "x", "x8"})); - - EXPECT_THAT(generateIdentifierTrigrams("nl"), trigramsAre({"nl", "n"})); - - EXPECT_THAT(generateIdentifierTrigrams("n"), trigramsAre({"n"})); - - EXPECT_THAT(generateIdentifierTrigrams("clangd"), - trigramsAre({"c", "cl", "cla", "lan", "ang", "ngd"})); - - EXPECT_THAT(generateIdentifierTrigrams("abc_def"), - trigramsAre({"a", "ab", "ad", "abc", "abd", "ade", "bcd", "bde", - "cde", "def"})); - - EXPECT_THAT(generateIdentifierTrigrams("a_b_c_d_e_"), - trigramsAre({"a", "a_", "ab", "abc", "bcd", "cde"})); - - EXPECT_THAT(generateIdentifierTrigrams("unique_ptr"), - trigramsAre({"u", "un", "up", "uni", "unp", "upt", "niq", "nip", - "npt", "iqu", "iqp", "ipt", "que", "qup", "qpt", - "uep", "ept", "ptr"})); - - EXPECT_THAT( - generateIdentifierTrigrams("TUDecl"), - trigramsAre({"t", "tu", "td", "tud", "tde", "ude", "dec", "ecl"})); - - EXPECT_THAT(generateIdentifierTrigrams("IsOK"), - trigramsAre({"i", "is", "io", "iso", "iok", "sok"})); - - EXPECT_THAT( - generateIdentifierTrigrams("abc_defGhij__klm"), - trigramsAre({"a", "ab", "ad", "abc", "abd", "ade", "adg", "bcd", - "bde", "bdg", "cde", "cdg", "def", "deg", "dgh", "dgk", - "efg", "egh", "egk", "fgh", "fgk", "ghi", "ghk", "gkl", - "hij", "hik", "hkl", "ijk", "ikl", "jkl", "klm"})); -} - -TEST(DexTrigrams, QueryTrigrams) { - EXPECT_THAT(generateQueryTrigrams("c"), trigramsAre({"c"})); - EXPECT_THAT(generateQueryTrigrams("cl"), trigramsAre({"cl"})); - EXPECT_THAT(generateQueryTrigrams("cla"), trigramsAre({"cla"})); - - EXPECT_THAT(generateQueryTrigrams(""), trigramsAre({})); - EXPECT_THAT(generateQueryTrigrams("_"), trigramsAre({"_"})); - EXPECT_THAT(generateQueryTrigrams("__"), trigramsAre({"__"})); - EXPECT_THAT(generateQueryTrigrams("___"), trigramsAre({})); - - EXPECT_THAT(generateQueryTrigrams("X86"), trigramsAre({"x86"})); - - EXPECT_THAT(generateQueryTrigrams("clangd"), - trigramsAre({"cla", "lan", "ang", "ngd"})); - - EXPECT_THAT(generateQueryTrigrams("abc_def"), - trigramsAre({"abc", "bcd", "cde", "def"})); - - EXPECT_THAT(generateQueryTrigrams("a_b_c_d_e_"), - trigramsAre({"abc", "bcd", "cde"})); - - EXPECT_THAT(generateQueryTrigrams("unique_ptr"), - trigramsAre({"uni", "niq", "iqu", "que", "uep", "ept", "ptr"})); - - EXPECT_THAT(generateQueryTrigrams("TUDecl"), - trigramsAre({"tud", "ude", "dec", "ecl"})); - - EXPECT_THAT(generateQueryTrigrams("IsOK"), trigramsAre({"iso", "sok"})); - - EXPECT_THAT(generateQueryTrigrams("abc_defGhij__klm"), - trigramsAre({"abc", "bcd", "cde", "def", "efg", "fgh", "ghi", - "hij", "ijk", "jkl", "klm"})); -} - -TEST(DexSearchTokens, SymbolPath) { - EXPECT_THAT(generateProximityURIs( - "unittest:///clang-tools-extra/clangd/index/Token.h"), - ElementsAre("unittest:///clang-tools-extra/clangd/index/Token.h", - "unittest:///clang-tools-extra/clangd/index", - "unittest:///clang-tools-extra/clangd", - "unittest:///clang-tools-extra", "unittest:///")); - - EXPECT_THAT(generateProximityURIs("unittest:///a/b/c.h"), - ElementsAre("unittest:///a/b/c.h", "unittest:///a/b", - "unittest:///a", "unittest:///")); -} - -//===----------------------------------------------------------------------===// -// Index tests. -//===----------------------------------------------------------------------===// - -TEST(Dex, Lookup) { - auto I = Dex::build(generateSymbols({"ns::abc", "ns::xyz"}), RefSlab()); - EXPECT_THAT(lookup(*I, SymbolID("ns::abc")), UnorderedElementsAre("ns::abc")); - EXPECT_THAT(lookup(*I, {SymbolID("ns::abc"), SymbolID("ns::xyz")}), - UnorderedElementsAre("ns::abc", "ns::xyz")); - EXPECT_THAT(lookup(*I, {SymbolID("ns::nonono"), SymbolID("ns::xyz")}), - UnorderedElementsAre("ns::xyz")); - EXPECT_THAT(lookup(*I, SymbolID("ns::nonono")), UnorderedElementsAre()); -} - -TEST(Dex, FuzzyFind) { - auto Index = - Dex::build(generateSymbols({"ns::ABC", "ns::BCD", "::ABC", - "ns::nested::ABC", "other::ABC", "other::A"}), - RefSlab()); - FuzzyFindRequest Req; - Req.Query = "ABC"; - Req.Scopes = {"ns::"}; - EXPECT_THAT(match(*Index, Req), UnorderedElementsAre("ns::ABC")); - Req.Scopes = {"ns::", "ns::nested::"}; - EXPECT_THAT(match(*Index, Req), - UnorderedElementsAre("ns::ABC", "ns::nested::ABC")); - Req.Query = "A"; - Req.Scopes = {"other::"}; - EXPECT_THAT(match(*Index, Req), - UnorderedElementsAre("other::A", "other::ABC")); - Req.Query = ""; - Req.Scopes = {}; - Req.AnyScope = true; - EXPECT_THAT(match(*Index, Req), - UnorderedElementsAre("ns::ABC", "ns::BCD", "::ABC", - "ns::nested::ABC", "other::ABC", - "other::A")); -} - -TEST(DexTest, DexLimitedNumMatches) { - auto I = Dex::build(generateNumSymbols(0, 100), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "5"; - Req.AnyScope = true; - Req.Limit = 3; - bool Incomplete; - auto Matches = match(*I, Req, &Incomplete); - EXPECT_TRUE(Req.Limit); - EXPECT_EQ(Matches.size(), *Req.Limit); - EXPECT_TRUE(Incomplete); -} - -TEST(DexTest, FuzzyMatch) { - auto I = Dex::build( - generateSymbols({"LaughingOutLoud", "LionPopulation", "LittleOldLady"}), - RefSlab()); - FuzzyFindRequest Req; - Req.Query = "lol"; - Req.AnyScope = true; - Req.Limit = 2; - EXPECT_THAT(match(*I, Req), - UnorderedElementsAre("LaughingOutLoud", "LittleOldLady")); -} - -TEST(DexTest, ShortQuery) { - auto I = Dex::build(generateSymbols({"OneTwoThreeFour"}), RefSlab()); - FuzzyFindRequest Req; - Req.AnyScope = true; - bool Incomplete; - - EXPECT_THAT(match(*I, Req, &Incomplete), ElementsAre("OneTwoThreeFour")); - EXPECT_FALSE(Incomplete) << "Empty string is not a short query"; - - Req.Query = "t"; - EXPECT_THAT(match(*I, Req, &Incomplete), ElementsAre()); - EXPECT_TRUE(Incomplete) << "Short queries have different semantics"; - - Req.Query = "tt"; - EXPECT_THAT(match(*I, Req, &Incomplete), ElementsAre()); - EXPECT_TRUE(Incomplete) << "Short queries have different semantics"; - - Req.Query = "ttf"; - EXPECT_THAT(match(*I, Req, &Incomplete), ElementsAre("OneTwoThreeFour")); - EXPECT_FALSE(Incomplete) << "3-char string is not a short query"; -} - -TEST(DexTest, MatchQualifiedNamesWithoutSpecificScope) { - auto I = Dex::build(generateSymbols({"a::y1", "b::y2", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.AnyScope = true; - Req.Query = "y"; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "b::y2", "y3")); -} - -TEST(DexTest, MatchQualifiedNamesWithGlobalScope) { - auto I = Dex::build(generateSymbols({"a::y1", "b::y2", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {""}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("y3")); -} - -TEST(DexTest, MatchQualifiedNamesWithOneScope) { - auto I = Dex::build( - generateSymbols({"a::y1", "a::y2", "a::x", "b::y2", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {"a::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "a::y2")); -} - -TEST(DexTest, MatchQualifiedNamesWithMultipleScopes) { - auto I = Dex::build( - generateSymbols({"a::y1", "a::y2", "a::x", "b::y3", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {"a::", "b::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "a::y2", "b::y3")); -} - -TEST(DexTest, NoMatchNestedScopes) { - auto I = Dex::build(generateSymbols({"a::y1", "a::b::y2"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {"a::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1")); -} - -TEST(DexTest, WildcardScope) { - auto I = - Dex::build(generateSymbols({"a::y1", "a::b::y2", "c::y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.AnyScope = true; - Req.Query = "y"; - Req.Scopes = {"a::"}; - EXPECT_THAT(match(*I, Req), - UnorderedElementsAre("a::y1", "a::b::y2", "c::y3")); -} - -TEST(DexTest, IgnoreCases) { - auto I = Dex::build(generateSymbols({"ns::ABC", "ns::abc"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "AB"; - Req.Scopes = {"ns::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("ns::ABC", "ns::abc")); -} - -TEST(DexTest, UnknownPostingList) { - // Regression test: we used to ignore unknown scopes and accept any symbol. - auto I = Dex::build(generateSymbols({"ns::ABC", "ns::abc"}), RefSlab()); - FuzzyFindRequest Req; - Req.Scopes = {"ns2::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre()); -} - -TEST(DexTest, Lookup) { - auto I = Dex::build(generateSymbols({"ns::abc", "ns::xyz"}), RefSlab()); - EXPECT_THAT(lookup(*I, SymbolID("ns::abc")), UnorderedElementsAre("ns::abc")); - EXPECT_THAT(lookup(*I, {SymbolID("ns::abc"), SymbolID("ns::xyz")}), - UnorderedElementsAre("ns::abc", "ns::xyz")); - EXPECT_THAT(lookup(*I, {SymbolID("ns::nonono"), SymbolID("ns::xyz")}), - UnorderedElementsAre("ns::xyz")); - EXPECT_THAT(lookup(*I, SymbolID("ns::nonono")), UnorderedElementsAre()); -} - -TEST(DexTest, SymbolIndexOptionsFilter) { - auto CodeCompletionSymbol = symbol("Completion"); - auto NonCodeCompletionSymbol = symbol("NoCompletion"); - CodeCompletionSymbol.Flags = Symbol::SymbolFlag::IndexedForCodeCompletion; - NonCodeCompletionSymbol.Flags = Symbol::SymbolFlag::None; - std::vector<Symbol> Symbols{CodeCompletionSymbol, NonCodeCompletionSymbol}; - Dex I(Symbols, RefSlab()); - FuzzyFindRequest Req; - Req.AnyScope = true; - Req.RestrictForCodeCompletion = false; - EXPECT_THAT(match(I, Req), ElementsAre("Completion", "NoCompletion")); - Req.RestrictForCodeCompletion = true; - EXPECT_THAT(match(I, Req), ElementsAre("Completion")); -} - -TEST(DexTest, ProximityPathsBoosting) { - auto RootSymbol = symbol("root::abc"); - RootSymbol.CanonicalDeclaration.FileURI = "unittest:///file.h"; - auto CloseSymbol = symbol("close::abc"); - CloseSymbol.CanonicalDeclaration.FileURI = "unittest:///a/b/c/d/e/f/file.h"; - - std::vector<Symbol> Symbols{CloseSymbol, RootSymbol}; - Dex I(Symbols, RefSlab()); - - FuzzyFindRequest Req; - Req.AnyScope = true; - Req.Query = "abc"; - // The best candidate can change depending on the proximity paths. - Req.Limit = 1; - - // FuzzyFind request comes from the file which is far from the root: expect - // CloseSymbol to come out. - Req.ProximityPaths = {testPath("a/b/c/d/e/f/file.h")}; - EXPECT_THAT(match(I, Req), ElementsAre("close::abc")); - - // FuzzyFind request comes from the file which is close to the root: expect - // RootSymbol to come out. - Req.ProximityPaths = {testPath("file.h")}; - EXPECT_THAT(match(I, Req), ElementsAre("root::abc")); -} - -TEST(DexTests, Refs) { - llvm::DenseMap<SymbolID, std::vector<Ref>> Refs; - auto AddRef = [&](const Symbol &Sym, const char *Filename, RefKind Kind) { - auto &SymbolRefs = Refs[Sym.ID]; - SymbolRefs.emplace_back(); - SymbolRefs.back().Kind = Kind; - SymbolRefs.back().Location.FileURI = Filename; - }; - auto Foo = symbol("foo"); - auto Bar = symbol("bar"); - AddRef(Foo, "foo.h", RefKind::Declaration); - AddRef(Foo, "foo.cc", RefKind::Definition); - AddRef(Foo, "reffoo.h", RefKind::Reference); - AddRef(Bar, "bar.h", RefKind::Declaration); - - RefsRequest Req; - Req.IDs.insert(Foo.ID); - Req.Filter = RefKind::Declaration | RefKind::Definition; - - std::vector<std::string> Files; - Dex(std::vector<Symbol>{Foo, Bar}, Refs).refs(Req, [&](const Ref &R) { - Files.push_back(R.Location.FileURI); - }); - EXPECT_THAT(Files, UnorderedElementsAre("foo.h", "foo.cc")); - - Req.Limit = 1; - Files.clear(); - Dex(std::vector<Symbol>{Foo, Bar}, Refs).refs(Req, [&](const Ref &R) { - Files.push_back(R.Location.FileURI); - }); - EXPECT_THAT(Files, ElementsAre(AnyOf("foo.h", "foo.cc"))); -} - -TEST(DexTest, PreferredTypesBoosting) { - auto Sym1 = symbol("t1"); - Sym1.Type = "T1"; - auto Sym2 = symbol("t2"); - Sym2.Type = "T2"; - - std::vector<Symbol> Symbols{Sym1, Sym2}; - Dex I(Symbols, RefSlab()); - - FuzzyFindRequest Req; - Req.AnyScope = true; - Req.Query = "t"; - // The best candidate can change depending on the preferred type. - Req.Limit = 1; - - Req.PreferredTypes = {Sym1.Type}; - EXPECT_THAT(match(I, Req), ElementsAre("t1")); - - Req.PreferredTypes = {Sym2.Type}; - EXPECT_THAT(match(I, Req), ElementsAre("t2")); -} - -TEST(DexTest, TemplateSpecialization) { - SymbolSlab::Builder B; - - Symbol S = symbol("TempSpec"); - S.ID = SymbolID("0"); - B.insert(S); - - S = symbol("TempSpec"); - S.ID = SymbolID("1"); - S.TemplateSpecializationArgs = "<int, bool>"; - S.SymInfo.Properties = static_cast<index::SymbolPropertySet>( - index::SymbolProperty::TemplateSpecialization); - B.insert(S); - - S = symbol("TempSpec"); - S.ID = SymbolID("2"); - S.TemplateSpecializationArgs = "<int, U>"; - S.SymInfo.Properties = static_cast<index::SymbolPropertySet>( - index::SymbolProperty::TemplatePartialSpecialization); - B.insert(S); - - auto I = dex::Dex::build(std::move(B).build(), RefSlab()); - FuzzyFindRequest Req; - Req.AnyScope = true; - - Req.Query = "TempSpec"; - EXPECT_THAT(match(*I, Req), - UnorderedElementsAre("TempSpec", "TempSpec<int, bool>", - "TempSpec<int, U>")); - - // FIXME: Add filtering for template argument list. - Req.Query = "TempSpec<int"; - EXPECT_THAT(match(*I, Req), IsEmpty()); -} - -} // namespace -} // namespace dex -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/DiagnosticsTests.cpp b/clang-tools-extra/unittests/clangd/DiagnosticsTests.cpp deleted file mode 100644 index a742c9d6206..00000000000 --- a/clang-tools-extra/unittests/clangd/DiagnosticsTests.cpp +++ /dev/null @@ -1,652 +0,0 @@ -//===--- DiagnosticsTests.cpp ------------------------------------*- C++-*-===// -// -// 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 "Annotations.h" -#include "ClangdUnit.h" -#include "Diagnostics.h" -#include "Protocol.h" -#include "SourceCode.h" -#include "TestIndex.h" -#include "TestFS.h" -#include "TestTU.h" -#include "index/MemIndex.h" -#include "clang/Basic/Diagnostic.h" -#include "clang/Basic/DiagnosticSema.h" -#include "llvm/Support/ScopedPrinter.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -using testing::_; -using testing::ElementsAre; -using testing::Field; -using testing::IsEmpty; -using testing::Pair; -using testing::UnorderedElementsAre; - -testing::Matcher<const Diag &> WithFix(testing::Matcher<Fix> FixMatcher) { - return Field(&Diag::Fixes, ElementsAre(FixMatcher)); -} - -testing::Matcher<const Diag &> WithFix(testing::Matcher<Fix> FixMatcher1, - testing::Matcher<Fix> FixMatcher2) { - return Field(&Diag::Fixes, UnorderedElementsAre(FixMatcher1, FixMatcher2)); -} - -testing::Matcher<const Diag &> WithNote(testing::Matcher<Note> NoteMatcher) { - return Field(&Diag::Notes, ElementsAre(NoteMatcher)); -} - -MATCHER_P2(Diag, Range, Message, - "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") { - return arg.Range == Range && arg.Message == Message; -} - -MATCHER_P3(Fix, Range, Replacement, Message, - "Fix " + llvm::to_string(Range) + " => " + - testing::PrintToString(Replacement) + " = [" + Message + "]") { - return arg.Message == Message && arg.Edits.size() == 1 && - arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement; -} - -MATCHER_P(EqualToLSPDiag, LSPDiag, - "LSP diagnostic " + llvm::to_string(LSPDiag)) { - if (toJSON(arg) != toJSON(LSPDiag)) { - *result_listener << llvm::formatv("expected:\n{0:2}\ngot\n{1:2}", - toJSON(LSPDiag), toJSON(arg)).str(); - return false; - } - return true; -} - -MATCHER_P(DiagSource, S, "") { return arg.Source == S; } -MATCHER_P(DiagName, N, "") { return arg.Name == N; } - -MATCHER_P(EqualToFix, Fix, "LSP fix " + llvm::to_string(Fix)) { - if (arg.Message != Fix.Message) - return false; - if (arg.Edits.size() != Fix.Edits.size()) - return false; - for (std::size_t I = 0; I < arg.Edits.size(); ++I) { - if (arg.Edits[I].range != Fix.Edits[I].range || - arg.Edits[I].newText != Fix.Edits[I].newText) - return false; - } - return true; -} - - -// Helper function to make tests shorter. -Position pos(int line, int character) { - Position Res; - Res.line = line; - Res.character = character; - return Res; -} - -TEST(DiagnosticsTest, DiagnosticRanges) { - // Check we report correct ranges, including various edge-cases. - Annotations Test(R"cpp( - namespace test{}; - void $decl[[foo]](); - int main() { - $typo[[go\ -o]](); - foo()$semicolon[[]]//with comments - $unk[[unknown]](); - double $type[[bar]] = "foo"; - struct Foo { int x; }; Foo a; - a.$nomember[[y]]; - test::$nomembernamespace[[test]]; - } - )cpp"); - EXPECT_THAT( - TestTU::withCode(Test.code()).build().getDiagnostics(), - ElementsAre( - // This range spans lines. - AllOf(Diag(Test.range("typo"), - "use of undeclared identifier 'goo'; did you mean 'foo'?"), - DiagSource(Diag::Clang), - DiagName("undeclared_var_use_suggest"), - WithFix( - Fix(Test.range("typo"), "foo", "change 'go\\ o' to 'foo'")), - // This is a pretty normal range. - WithNote(Diag(Test.range("decl"), "'foo' declared here"))), - // This range is zero-width and insertion. Therefore make sure we are - // not expanding it into other tokens. Since we are not going to - // replace those. - AllOf(Diag(Test.range("semicolon"), "expected ';' after expression"), - WithFix(Fix(Test.range("semicolon"), ";", "insert ';'"))), - // This range isn't provided by clang, we expand to the token. - Diag(Test.range("unk"), "use of undeclared identifier 'unknown'"), - Diag(Test.range("type"), - "cannot initialize a variable of type 'double' with an lvalue " - "of type 'const char [4]'"), - Diag(Test.range("nomember"), "no member named 'y' in 'Foo'"), - Diag(Test.range("nomembernamespace"), - "no member named 'test' in namespace 'test'"))); -} - -TEST(DiagnosticsTest, FlagsMatter) { - Annotations Test("[[void]] main() {}"); - auto TU = TestTU::withCode(Test.code()); - EXPECT_THAT(TU.build().getDiagnostics(), - ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"), - WithFix(Fix(Test.range(), "int", - "change 'void' to 'int'"))))); - // Same code built as C gets different diagnostics. - TU.Filename = "Plain.c"; - EXPECT_THAT( - TU.build().getDiagnostics(), - ElementsAre(AllOf( - Diag(Test.range(), "return type of 'main' is not 'int'"), - WithFix(Fix(Test.range(), "int", "change return type to 'int'"))))); -} - -TEST(DiagnosticsTest, DiagnosticPreamble) { - Annotations Test(R"cpp( - #include $[["not-found.h"]] - )cpp"); - - auto TU = TestTU::withCode(Test.code()); - EXPECT_THAT(TU.build().getDiagnostics(), - ElementsAre(testing::AllOf( - Diag(Test.range(), "'not-found.h' file not found"), - DiagSource(Diag::Clang), DiagName("pp_file_not_found")))); -} - -TEST(DiagnosticsTest, ClangTidy) { - Annotations Test(R"cpp( - #include $deprecated[["assert.h"]] - - #define $macrodef[[SQUARE]](X) (X)*(X) - int main() { - return $doubled[[sizeof]](sizeof(int)); - int y = 4; - return SQUARE($macroarg[[++]]y); - } - )cpp"); - auto TU = TestTU::withCode(Test.code()); - TU.HeaderFilename = "assert.h"; // Suppress "not found" error. - TU.ClangTidyChecks = - "-*, bugprone-sizeof-expression, bugprone-macro-repeated-side-effects, " - "modernize-deprecated-headers"; - EXPECT_THAT( - TU.build().getDiagnostics(), - UnorderedElementsAre( - AllOf(Diag(Test.range("deprecated"), - "inclusion of deprecated C++ header 'assert.h'; consider " - "using 'cassert' instead"), - DiagSource(Diag::ClangTidy), - DiagName("modernize-deprecated-headers"), - WithFix(Fix(Test.range("deprecated"), "<cassert>", - "change '\"assert.h\"' to '<cassert>'"))), - Diag(Test.range("doubled"), - "suspicious usage of 'sizeof(sizeof(...))'"), - AllOf( - Diag(Test.range("macroarg"), - "side effects in the 1st macro argument 'X' are repeated in " - "macro expansion"), - DiagSource(Diag::ClangTidy), - DiagName("bugprone-macro-repeated-side-effects"), - WithNote( - Diag(Test.range("macrodef"), "macro 'SQUARE' defined here"))), - Diag(Test.range("macroarg"), - "multiple unsequenced modifications to 'y'"))); -} - -TEST(DiagnosticsTest, Preprocessor) { - // This looks like a preamble, but there's an #else in the middle! - // Check that: - // - the #else doesn't generate diagnostics (we had this bug) - // - we get diagnostics from the taken branch - // - we get no diagnostics from the not taken branch - Annotations Test(R"cpp( - #ifndef FOO - #define FOO - int a = [[b]]; - #else - int x = y; - #endif - )cpp"); - EXPECT_THAT( - TestTU::withCode(Test.code()).build().getDiagnostics(), - ElementsAre(Diag(Test.range(), "use of undeclared identifier 'b'"))); -} - -TEST(DiagnosticsTest, InsideMacros) { - Annotations Test(R"cpp( - #define TEN 10 - #define RET(x) return x + 10 - - int* foo() { - RET($foo[[0]]); - } - int* bar() { - return $bar[[TEN]]; - } - )cpp"); - EXPECT_THAT(TestTU::withCode(Test.code()).build().getDiagnostics(), - ElementsAre(Diag(Test.range("foo"), - "cannot initialize return object of type " - "'int *' with an rvalue of type 'int'"), - Diag(Test.range("bar"), - "cannot initialize return object of type " - "'int *' with an rvalue of type 'int'"))); -} - -TEST(DiagnosticsTest, NoFixItInMacro) { - Annotations Test(R"cpp( - #define Define(name) void name() {} - - [[Define]](main) - )cpp"); - auto TU = TestTU::withCode(Test.code()); - EXPECT_THAT(TU.build().getDiagnostics(), - ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"), - Not(WithFix(_))))); -} - -TEST(DiagnosticsTest, ToLSP) { - URIForFile MainFile = - URIForFile::canonicalize(testPath("foo/bar/main.cpp"), ""); - URIForFile HeaderFile = - URIForFile::canonicalize(testPath("foo/bar/header.h"), ""); - - clangd::Diag D; - D.ID = clang::diag::err_enum_class_reference; - D.Name = "enum_class_reference"; - D.Source = clangd::Diag::Clang; - D.Message = "something terrible happened"; - D.Range = {pos(1, 2), pos(3, 4)}; - D.InsideMainFile = true; - D.Severity = DiagnosticsEngine::Error; - D.File = "foo/bar/main.cpp"; - D.AbsFile = MainFile.file(); - - clangd::Note NoteInMain; - NoteInMain.Message = "declared somewhere in the main file"; - NoteInMain.Range = {pos(5, 6), pos(7, 8)}; - NoteInMain.Severity = DiagnosticsEngine::Remark; - NoteInMain.File = "../foo/bar/main.cpp"; - NoteInMain.InsideMainFile = true; - NoteInMain.AbsFile = MainFile.file(); - - D.Notes.push_back(NoteInMain); - - clangd::Note NoteInHeader; - NoteInHeader.Message = "declared somewhere in the header file"; - NoteInHeader.Range = {pos(9, 10), pos(11, 12)}; - NoteInHeader.Severity = DiagnosticsEngine::Note; - NoteInHeader.File = "../foo/baz/header.h"; - NoteInHeader.InsideMainFile = false; - NoteInHeader.AbsFile = HeaderFile.file(); - D.Notes.push_back(NoteInHeader); - - clangd::Fix F; - F.Message = "do something"; - D.Fixes.push_back(F); - - // Diagnostics should turn into these: - clangd::Diagnostic MainLSP; - MainLSP.range = D.Range; - MainLSP.severity = getSeverity(DiagnosticsEngine::Error); - MainLSP.code = "enum_class_reference"; - MainLSP.source = "clang"; - MainLSP.message = R"(Something terrible happened (fix available) - -main.cpp:6:7: remark: declared somewhere in the main file - -../foo/baz/header.h:10:11: -note: declared somewhere in the header file)"; - - clangd::Diagnostic NoteInMainLSP; - NoteInMainLSP.range = NoteInMain.Range; - NoteInMainLSP.severity = getSeverity(DiagnosticsEngine::Remark); - NoteInMainLSP.message = R"(Declared somewhere in the main file - -main.cpp:2:3: error: something terrible happened)"; - - ClangdDiagnosticOptions Opts; - // Transform diagnostics and check the results. - std::vector<std::pair<clangd::Diagnostic, std::vector<clangd::Fix>>> LSPDiags; - toLSPDiags(D, MainFile, Opts, - [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) { - LSPDiags.push_back( - {std::move(LSPDiag), - std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())}); - }); - - EXPECT_THAT( - LSPDiags, - ElementsAre(Pair(EqualToLSPDiag(MainLSP), ElementsAre(EqualToFix(F))), - Pair(EqualToLSPDiag(NoteInMainLSP), IsEmpty()))); - EXPECT_EQ(LSPDiags[0].first.code, "enum_class_reference"); - EXPECT_EQ(LSPDiags[0].first.source, "clang"); - EXPECT_EQ(LSPDiags[1].first.code, ""); - EXPECT_EQ(LSPDiags[1].first.source, ""); - - // Same thing, but don't flatten notes into the main list. - LSPDiags.clear(); - Opts.EmitRelatedLocations = true; - toLSPDiags(D, MainFile, Opts, - [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) { - LSPDiags.push_back( - {std::move(LSPDiag), - std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())}); - }); - MainLSP.message = "Something terrible happened (fix available)"; - DiagnosticRelatedInformation NoteInMainDRI; - NoteInMainDRI.message = "Declared somewhere in the main file"; - NoteInMainDRI.location.range = NoteInMain.Range; - NoteInMainDRI.location.uri = MainFile; - MainLSP.relatedInformation = {NoteInMainDRI}; - DiagnosticRelatedInformation NoteInHeaderDRI; - NoteInHeaderDRI.message = "Declared somewhere in the header file"; - NoteInHeaderDRI.location.range = NoteInHeader.Range; - NoteInHeaderDRI.location.uri = HeaderFile; - MainLSP.relatedInformation = {NoteInMainDRI, NoteInHeaderDRI}; - EXPECT_THAT( - LSPDiags, - ElementsAre(Pair(EqualToLSPDiag(MainLSP), ElementsAre(EqualToFix(F))))); -} - -struct SymbolWithHeader { - std::string QName; - std::string DeclaringFile; - std::string IncludeHeader; -}; - -std::unique_ptr<SymbolIndex> -buildIndexWithSymbol(llvm::ArrayRef<SymbolWithHeader> Syms) { - SymbolSlab::Builder Slab; - for (const auto &S : Syms) { - Symbol Sym = cls(S.QName); - Sym.Flags |= Symbol::IndexedForCodeCompletion; - Sym.CanonicalDeclaration.FileURI = S.DeclaringFile.c_str(); - Sym.Definition.FileURI = S.DeclaringFile.c_str(); - Sym.IncludeHeaders.emplace_back(S.IncludeHeader, 1); - Slab.insert(Sym); - } - return MemIndex::build(std::move(Slab).build(), RefSlab()); -} - -TEST(IncludeFixerTest, IncompleteType) { - Annotations Test(R"cpp( -$insert[[]]namespace ns { - class X; - $nested[[X::]]Nested n; -} -class Y : $base[[public ns::X]] {}; -int main() { - ns::X *x; - x$access[[->]]f(); -} - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = buildIndexWithSymbol( - {SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""}}); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT( - TU.build().getDiagnostics(), - UnorderedElementsAre( - AllOf(Diag(Test.range("nested"), - "incomplete type 'ns::X' named in nested name specifier"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol ns::X"))), - AllOf(Diag(Test.range("base"), "base class has incomplete type"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol ns::X"))), - AllOf(Diag(Test.range("access"), - "member access into incomplete type 'ns::X'"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol ns::X"))))); -} - -TEST(IncludeFixerTest, NoSuggestIncludeWhenNoDefinitionInHeader) { - Annotations Test(R"cpp( -$insert[[]]namespace ns { - class X; -} -class Y : $base[[public ns::X]] {}; -int main() { - ns::X *x; - x$access[[->]]f(); -} - )cpp"); - auto TU = TestTU::withCode(Test.code()); - Symbol Sym = cls("ns::X"); - Sym.Flags |= Symbol::IndexedForCodeCompletion; - Sym.CanonicalDeclaration.FileURI = "unittest:///x.h"; - Sym.Definition.FileURI = "unittest:///x.cc"; - Sym.IncludeHeaders.emplace_back("\"x.h\"", 1); - - SymbolSlab::Builder Slab; - Slab.insert(Sym); - auto Index = MemIndex::build(std::move(Slab).build(), RefSlab()); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT(TU.build().getDiagnostics(), - UnorderedElementsAre( - Diag(Test.range("base"), "base class has incomplete type"), - Diag(Test.range("access"), - "member access into incomplete type 'ns::X'"))); -} - -TEST(IncludeFixerTest, Typo) { - Annotations Test(R"cpp( -$insert[[]]namespace ns { -void foo() { - $unqualified1[[X]] x; - // No fix if the unresolved type is used as specifier. (ns::)X::Nested will be - // considered the unresolved type. - $unqualified2[[X]]::Nested n; -} -} -void bar() { - ns::$qualified1[[X]] x; // ns:: is valid. - ns::$qualified2[[X]](); // Error: no member in namespace - - ::$global[[Global]] glob; -} - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = buildIndexWithSymbol( - {SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""}, - SymbolWithHeader{"Global", "unittest:///global.h", "\"global.h\""}}); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT( - TU.build().getDiagnostics(), - UnorderedElementsAre( - AllOf(Diag(Test.range("unqualified1"), "unknown type name 'X'"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol ns::X"))), - Diag(Test.range("unqualified2"), "use of undeclared identifier 'X'"), - AllOf(Diag(Test.range("qualified1"), - "no type named 'X' in namespace 'ns'"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol ns::X"))), - AllOf(Diag(Test.range("qualified2"), - "no member named 'X' in namespace 'ns'"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol ns::X"))), - AllOf(Diag(Test.range("global"), - "no type named 'Global' in the global namespace"), - WithFix(Fix(Test.range("insert"), "#include \"global.h\"\n", - "Add include \"global.h\" for symbol Global"))))); -} - -TEST(IncludeFixerTest, MultipleMatchedSymbols) { - Annotations Test(R"cpp( -$insert[[]]namespace na { -namespace nb { -void foo() { - $unqualified[[X]] x; -} -} -} - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = buildIndexWithSymbol( - {SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""}, - SymbolWithHeader{"na::nb::X", "unittest:///b.h", "\"b.h\""}}); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT(TU.build().getDiagnostics(), - UnorderedElementsAre(AllOf( - Diag(Test.range("unqualified"), "unknown type name 'X'"), - WithFix(Fix(Test.range("insert"), "#include \"a.h\"\n", - "Add include \"a.h\" for symbol na::X"), - Fix(Test.range("insert"), "#include \"b.h\"\n", - "Add include \"b.h\" for symbol na::nb::X"))))); -} - -TEST(IncludeFixerTest, NoCrashMemebrAccess) { - Annotations Test(R"cpp( - struct X { int xyz; }; - void g() { X x; x.$[[xy]] } - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = buildIndexWithSymbol( - SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""}); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT( - TU.build().getDiagnostics(), - UnorderedElementsAre(Diag(Test.range(), "no member named 'xy' in 'X'"))); -} - -TEST(IncludeFixerTest, UseCachedIndexResults) { - // As index results for the identical request are cached, more than 5 fixes - // are generated. - Annotations Test(R"cpp( -$insert[[]]void foo() { - $x1[[X]] x; - $x2[[X]] x; - $x3[[X]] x; - $x4[[X]] x; - $x5[[X]] x; - $x6[[X]] x; - $x7[[X]] x; -} - -class X; -void bar(X *x) { - x$a1[[->]]f(); - x$a2[[->]]f(); - x$a3[[->]]f(); - x$a4[[->]]f(); - x$a5[[->]]f(); - x$a6[[->]]f(); - x$a7[[->]]f(); -} - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = - buildIndexWithSymbol(SymbolWithHeader{"X", "unittest:///a.h", "\"a.h\""}); - TU.ExternalIndex = Index.get(); - - auto Parsed = TU.build(); - for (const auto &D : Parsed.getDiagnostics()) { - EXPECT_EQ(D.Fixes.size(), 1u); - EXPECT_EQ(D.Fixes[0].Message, - std::string("Add include \"a.h\" for symbol X")); - } -} - -TEST(IncludeFixerTest, UnresolvedNameAsSpecifier) { - Annotations Test(R"cpp( -$insert[[]]namespace ns { -} -void g() { ns::$[[scope]]::X_Y(); } - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = buildIndexWithSymbol( - SymbolWithHeader{"ns::scope::X_Y", "unittest:///x.h", "\"x.h\""}); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT( - TU.build().getDiagnostics(), - UnorderedElementsAre(AllOf( - Diag(Test.range(), "no member named 'scope' in namespace 'ns'"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol ns::scope::X_Y"))))); -} - -TEST(IncludeFixerTest, UnresolvedSpecifierWithSemaCorrection) { - Annotations Test(R"cpp( -$insert[[]]namespace clang { -void f() { - // "clangd::" will be corrected to "clang::" by Sema. - $q1[[clangd]]::$x[[X]] x; - $q2[[clangd]]::$ns[[ns]]::Y y; -} -} - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = buildIndexWithSymbol( - {SymbolWithHeader{"clang::clangd::X", "unittest:///x.h", "\"x.h\""}, - SymbolWithHeader{"clang::clangd::ns::Y", "unittest:///y.h", "\"y.h\""}}); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT( - TU.build().getDiagnostics(), - UnorderedElementsAre( - AllOf( - Diag(Test.range("q1"), "use of undeclared identifier 'clangd'; " - "did you mean 'clang'?"), - WithFix(_, // change clangd to clang - Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol clang::clangd::X"))), - AllOf( - Diag(Test.range("x"), "no type named 'X' in namespace 'clang'"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol clang::clangd::X"))), - AllOf( - Diag(Test.range("q2"), "use of undeclared identifier 'clangd'; " - "did you mean 'clang'?"), - WithFix( - _, // change clangd to clangd - Fix(Test.range("insert"), "#include \"y.h\"\n", - "Add include \"y.h\" for symbol clang::clangd::ns::Y"))), - AllOf(Diag(Test.range("ns"), - "no member named 'ns' in namespace 'clang'"), - WithFix(Fix( - Test.range("insert"), "#include \"y.h\"\n", - "Add include \"y.h\" for symbol clang::clangd::ns::Y"))))); -} - -TEST(IncludeFixerTest, SpecifiedScopeIsNamespaceAlias) { - Annotations Test(R"cpp( -$insert[[]]namespace a {} -namespace b = a; -namespace c { - b::$[[X]] x; -} - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = buildIndexWithSymbol( - SymbolWithHeader{"a::X", "unittest:///x.h", "\"x.h\""}); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT(TU.build().getDiagnostics(), - UnorderedElementsAre(AllOf( - Diag(Test.range(), "no type named 'X' in namespace 'a'"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol a::X"))))); -} - -} // namespace -} // namespace clangd -} // namespace clang - diff --git a/clang-tools-extra/unittests/clangd/DraftStoreTests.cpp b/clang-tools-extra/unittests/clangd/DraftStoreTests.cpp deleted file mode 100644 index 1840892cd5e..00000000000 --- a/clang-tools-extra/unittests/clangd/DraftStoreTests.cpp +++ /dev/null @@ -1,347 +0,0 @@ -//===-- DraftStoreTests.cpp -------------------------------------*- C++ -*-===// -// -// 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 "Annotations.h" -#include "DraftStore.h" -#include "SourceCode.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -struct IncrementalTestStep { - llvm::StringRef Src; - llvm::StringRef Contents; -}; - -int rangeLength(llvm::StringRef Code, const Range &Rng) { - llvm::Expected<size_t> Start = positionToOffset(Code, Rng.start); - llvm::Expected<size_t> End = positionToOffset(Code, Rng.end); - assert(Start); - assert(End); - return *End - *Start; -} - -/// Send the changes one by one to updateDraft, verify the intermediate results. -void stepByStep(llvm::ArrayRef<IncrementalTestStep> Steps) { - DraftStore DS; - Annotations InitialSrc(Steps.front().Src); - constexpr llvm::StringLiteral Path("/hello.cpp"); - - // Set the initial content. - DS.addDraft(Path, InitialSrc.code()); - - for (size_t i = 1; i < Steps.size(); i++) { - Annotations SrcBefore(Steps[i - 1].Src); - Annotations SrcAfter(Steps[i].Src); - llvm::StringRef Contents = Steps[i - 1].Contents; - TextDocumentContentChangeEvent Event{ - SrcBefore.range(), - rangeLength(SrcBefore.code(), SrcBefore.range()), - Contents.str(), - }; - - llvm::Expected<std::string> Result = DS.updateDraft(Path, {Event}); - ASSERT_TRUE(!!Result); - EXPECT_EQ(*Result, SrcAfter.code()); - EXPECT_EQ(*DS.getDraft(Path), SrcAfter.code()); - } -} - -/// Send all the changes at once to updateDraft, check only the final result. -void allAtOnce(llvm::ArrayRef<IncrementalTestStep> Steps) { - DraftStore DS; - Annotations InitialSrc(Steps.front().Src); - Annotations FinalSrc(Steps.back().Src); - constexpr llvm::StringLiteral Path("/hello.cpp"); - std::vector<TextDocumentContentChangeEvent> Changes; - - for (size_t i = 0; i < Steps.size() - 1; i++) { - Annotations Src(Steps[i].Src); - llvm::StringRef Contents = Steps[i].Contents; - - Changes.push_back({ - Src.range(), - rangeLength(Src.code(), Src.range()), - Contents.str(), - }); - } - - // Set the initial content. - DS.addDraft(Path, InitialSrc.code()); - - llvm::Expected<std::string> Result = DS.updateDraft(Path, Changes); - - ASSERT_TRUE(!!Result) << llvm::toString(Result.takeError()); - EXPECT_EQ(*Result, FinalSrc.code()); - EXPECT_EQ(*DS.getDraft(Path), FinalSrc.code()); -} - -TEST(DraftStoreIncrementalUpdateTest, Simple) { - // clang-format off - IncrementalTestStep Steps[] = - { - // Replace a range - { -R"cpp(static int -hello[[World]]() -{})cpp", - "Universe" - }, - // Delete a range - { -R"cpp(static int -hello[[Universe]]() -{})cpp", - "" - }, - // Add a range - { -R"cpp(static int -hello[[]]() -{})cpp", - "Monde" - }, - { -R"cpp(static int -helloMonde() -{})cpp", - "" - } - }; - // clang-format on - - stepByStep(Steps); - allAtOnce(Steps); -} - -TEST(DraftStoreIncrementalUpdateTest, MultiLine) { - // clang-format off - IncrementalTestStep Steps[] = - { - // Replace a range - { -R"cpp(static [[int -helloWorld]]() -{})cpp", -R"cpp(char -welcome)cpp" - }, - // Delete a range - { -R"cpp(static char[[ -welcome]]() -{})cpp", - "" - }, - // Add a range - { -R"cpp(static char[[]]() -{})cpp", - R"cpp( -cookies)cpp" - }, - // Replace the whole file - { -R"cpp([[static char -cookies() -{}]])cpp", - R"cpp(#include <stdio.h> -)cpp" - }, - // Delete the whole file - { - R"cpp([[#include <stdio.h> -]])cpp", - "", - }, - // Add something to an empty file - { - "[[]]", - R"cpp(int main() { -)cpp", - }, - { - R"cpp(int main() { -)cpp", - "" - } - }; - // clang-format on - - stepByStep(Steps); - allAtOnce(Steps); -} - -TEST(DraftStoreIncrementalUpdateTest, WrongRangeLength) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 0; - Change.range->end.line = 0; - Change.range->end.character = 2; - Change.rangeLength = 10; - - Expected<std::string> Result = DS.updateDraft(File, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ( - toString(Result.takeError()), - "Change's rangeLength (10) doesn't match the computed range length (2)."); -} - -TEST(DraftStoreIncrementalUpdateTest, EndBeforeStart) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 5; - Change.range->end.line = 0; - Change.range->end.character = 3; - - Expected<std::string> Result = DS.updateDraft(File, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), - "Range's end position (0:3) is before start position (0:5)"); -} - -TEST(DraftStoreIncrementalUpdateTest, StartCharOutOfRange) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 100; - Change.range->end.line = 0; - Change.range->end.character = 100; - Change.text = "foo"; - - Expected<std::string> Result = DS.updateDraft(File, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), - "utf-16 offset 100 is invalid for line 0"); -} - -TEST(DraftStoreIncrementalUpdateTest, EndCharOutOfRange) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 0; - Change.range->end.line = 0; - Change.range->end.character = 100; - Change.text = "foo"; - - Expected<std::string> Result = DS.updateDraft(File, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), - "utf-16 offset 100 is invalid for line 0"); -} - -TEST(DraftStoreIncrementalUpdateTest, StartLineOutOfRange) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 100; - Change.range->start.character = 0; - Change.range->end.line = 100; - Change.range->end.character = 0; - Change.text = "foo"; - - Expected<std::string> Result = DS.updateDraft(File, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), "Line value is out of range (100)"); -} - -TEST(DraftStoreIncrementalUpdateTest, EndLineOutOfRange) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 0; - Change.range->end.line = 100; - Change.range->end.character = 0; - Change.text = "foo"; - - Expected<std::string> Result = DS.updateDraft(File, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), "Line value is out of range (100)"); -} - -/// Check that if a valid change is followed by an invalid change, the original -/// version of the document (prior to all changes) is kept. -TEST(DraftStoreIncrementalUpdateTest, InvalidRangeInASequence) { - DraftStore DS; - Path File = "foo.cpp"; - - StringRef OriginalContents = "int main() {}\n"; - DS.addDraft(File, OriginalContents); - - // The valid change - TextDocumentContentChangeEvent Change1; - Change1.range.emplace(); - Change1.range->start.line = 0; - Change1.range->start.character = 0; - Change1.range->end.line = 0; - Change1.range->end.character = 0; - Change1.text = "Hello "; - - // The invalid change - TextDocumentContentChangeEvent Change2; - Change2.range.emplace(); - Change2.range->start.line = 0; - Change2.range->start.character = 5; - Change2.range->end.line = 0; - Change2.range->end.character = 100; - Change2.text = "something"; - - Expected<std::string> Result = DS.updateDraft(File, {Change1, Change2}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), - "utf-16 offset 100 is invalid for line 0"); - - Optional<std::string> Contents = DS.getDraft(File); - EXPECT_TRUE(Contents); - EXPECT_EQ(*Contents, OriginalContents); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/ExpectedTypeTest.cpp b/clang-tools-extra/unittests/clangd/ExpectedTypeTest.cpp deleted file mode 100644 index 8d2d60ebe55..00000000000 --- a/clang-tools-extra/unittests/clangd/ExpectedTypeTest.cpp +++ /dev/null @@ -1,153 +0,0 @@ -//===-- ExpectedTypeTest.cpp -----------------------------------*- C++ -*-===// -// -// 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 "ClangdUnit.h" -#include "ExpectedTypes.h" -#include "TestTU.h" -#include "clang/AST/ASTContext.h" -#include "clang/AST/Decl.h" -#include "llvm/ADT/StringRef.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -using ::testing::Field; -using ::testing::Matcher; -using ::testing::SizeIs; -using ::testing::UnorderedElementsAreArray; - -class ExpectedTypeConversionTest : public ::testing::Test { -protected: - void build(llvm::StringRef Code) { - assert(!AST && "AST built twice"); - AST = TestTU::withCode(Code).build(); - } - - const ValueDecl *decl(llvm::StringRef Name) { - return &cast<ValueDecl>(findDecl(*AST, Name)); - } - - QualType typeOf(llvm::StringRef Name) { - return decl(Name)->getType().getCanonicalType(); - } - - /// An overload for convenience. - llvm::Optional<OpaqueType> fromCompletionResult(const ValueDecl *D) { - return OpaqueType::fromCompletionResult( - ASTCtx(), CodeCompletionResult(D, CCP_Declaration)); - } - - /// A set of DeclNames whose type match each other computed by - /// OpaqueType::fromCompletionResult. - using EquivClass = std::set<std::string>; - - Matcher<std::map<std::string, EquivClass>> - ClassesAre(llvm::ArrayRef<EquivClass> Classes) { - using MapEntry = std::map<std::string, EquivClass>::value_type; - - std::vector<Matcher<MapEntry>> Elements; - Elements.reserve(Classes.size()); - for (auto &Cls : Classes) - Elements.push_back(Field(&MapEntry::second, Cls)); - return UnorderedElementsAreArray(Elements); - } - - // Groups \p Decls into equivalence classes based on the result of - // 'OpaqueType::fromCompletionResult'. - std::map<std::string, EquivClass> - buildEquivClasses(llvm::ArrayRef<llvm::StringRef> DeclNames) { - std::map<std::string, EquivClass> Classes; - for (llvm::StringRef Name : DeclNames) { - auto Type = OpaqueType::fromType(ASTCtx(), typeOf(Name)); - Classes[Type->raw()].insert(Name); - } - return Classes; - } - - ASTContext &ASTCtx() { return AST->getASTContext(); } - -private: - // Set after calling build(). - llvm::Optional<ParsedAST> AST; -}; - -TEST_F(ExpectedTypeConversionTest, BasicTypes) { - build(R"cpp( - // ints. - bool b; - int i; - unsigned int ui; - long long ll; - - // floats. - float f; - double d; - - // pointers - int* iptr; - bool* bptr; - - // user-defined types. - struct X {}; - X user_type; - )cpp"); - - EXPECT_THAT(buildEquivClasses({"b", "i", "ui", "ll", "f", "d", "iptr", "bptr", - "user_type"}), - ClassesAre({{"b"}, - {"i", "ui", "ll"}, - {"f", "d"}, - {"iptr"}, - {"bptr"}, - {"user_type"}})); -} - -TEST_F(ExpectedTypeConversionTest, ReferencesDontMatter) { - build(R"cpp( - int noref; - int & ref = noref; - const int & const_ref = noref; - int && rv_ref = 10; - )cpp"); - - EXPECT_THAT(buildEquivClasses({"noref", "ref", "const_ref", "rv_ref"}), - SizeIs(1)); -} - -TEST_F(ExpectedTypeConversionTest, ArraysDecay) { - build(R"cpp( - int arr[2]; - int (&arr_ref)[2] = arr; - int *ptr; - )cpp"); - - EXPECT_THAT(buildEquivClasses({"arr", "arr_ref", "ptr"}), SizeIs(1)); -} - -TEST_F(ExpectedTypeConversionTest, FunctionReturns) { - build(R"cpp( - int returns_int(); - int* returns_ptr(); - - int int_; - int* int_ptr; - )cpp"); - - OpaqueType IntTy = *OpaqueType::fromType(ASTCtx(), typeOf("int_")); - EXPECT_EQ(fromCompletionResult(decl("returns_int")), IntTy); - - OpaqueType IntPtrTy = *OpaqueType::fromType(ASTCtx(), typeOf("int_ptr")); - EXPECT_EQ(fromCompletionResult(decl("returns_ptr")), IntPtrTy); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/FSTests.cpp b/clang-tools-extra/unittests/clangd/FSTests.cpp deleted file mode 100644 index 044452cae1d..00000000000 --- a/clang-tools-extra/unittests/clangd/FSTests.cpp +++ /dev/null @@ -1,50 +0,0 @@ -//===-- FSTests.cpp - File system related tests -----------------*- C++ -*-===// -// -// 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 "FS.h" -#include "TestFS.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -TEST(FSTests, PreambleStatusCache) { - llvm::StringMap<std::string> Files; - Files["x"] = ""; - Files["y"] = ""; - Files["main"] = ""; - auto FS = buildTestFS(Files); - FS->setCurrentWorkingDirectory(testRoot()); - - PreambleFileStatusCache StatCache(testPath("main")); - auto ProduceFS = StatCache.getProducingFS(FS); - EXPECT_TRUE(ProduceFS->openFileForRead("x")); - EXPECT_TRUE(ProduceFS->status("y")); - EXPECT_TRUE(ProduceFS->status("main")); - - EXPECT_TRUE(StatCache.lookup(testPath("x")).hasValue()); - EXPECT_TRUE(StatCache.lookup(testPath("y")).hasValue()); - // Main file is not cached. - EXPECT_FALSE(StatCache.lookup(testPath("main")).hasValue()); - - llvm::vfs::Status S("fake", llvm::sys::fs::UniqueID(0, 0), - std::chrono::system_clock::now(), 0, 0, 1024, - llvm::sys::fs::file_type::regular_file, - llvm::sys::fs::all_all); - StatCache.update(*FS, S); - auto ConsumeFS = StatCache.getConsumingFS(FS); - auto Cached = ConsumeFS->status(testPath("fake")); - EXPECT_TRUE(Cached); - EXPECT_EQ(Cached->getName(), S.getName()); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/FileDistanceTests.cpp b/clang-tools-extra/unittests/clangd/FileDistanceTests.cpp deleted file mode 100644 index 3003582959a..00000000000 --- a/clang-tools-extra/unittests/clangd/FileDistanceTests.cpp +++ /dev/null @@ -1,123 +0,0 @@ -//===-- FileDistanceTests.cpp ------------------------*- C++ -*-----------===// -// -// 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 "FileDistance.h" -#include "TestFS.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -TEST(FileDistanceTests, Distance) { - FileDistanceOptions Opts; - Opts.UpCost = 5; - Opts.DownCost = 3; - SourceParams CostTwo; - CostTwo.Cost = 2; - FileDistance D( - {{"tools/clang/lib/Format/FormatToken.cpp", SourceParams()}, - {"tools/clang/include/clang/Format/FormatToken.h", SourceParams()}, - {"include/llvm/ADT/StringRef.h", CostTwo}}, - Opts); - - // Source - EXPECT_EQ(D.distance("tools/clang/lib/Format/FormatToken.cpp"), 0u); - EXPECT_EQ(D.distance("include/llvm/ADT/StringRef.h"), 2u); - // Parent - EXPECT_EQ(D.distance("tools/clang/lib/Format/"), 5u); - // Child - EXPECT_EQ(D.distance("tools/clang/lib/Format/FormatToken.cpp/Oops"), 3u); - // Ancestor (up+up+up+up) - EXPECT_EQ(D.distance("/"), 22u); - // Sibling (up+down) - EXPECT_EQ(D.distance("tools/clang/lib/Format/AnotherFile.cpp"), 8u); - // Cousin (up+up+down+down) - EXPECT_EQ(D.distance("include/llvm/Support/Allocator.h"), 18u); - // First cousin, once removed (up+up+up+down+down) - EXPECT_EQ(D.distance("include/llvm-c/Core.h"), 23u); -} - -TEST(FileDistanceTests, BadSource) { - // We mustn't assume that paths above sources are best reached via them. - FileDistanceOptions Opts; - Opts.UpCost = 5; - Opts.DownCost = 3; - SourceParams CostLots; - CostLots.Cost = 100; - FileDistance D({{"a", SourceParams()}, {"b/b/b", CostLots}}, Opts); - EXPECT_EQ(D.distance("b"), 8u); // a+up+down, not b+up+up - EXPECT_EQ(D.distance("b/b/b"), 14u); // a+up+down+down+down, not b - EXPECT_EQ(D.distance("b/b/b/c"), 17u); // a+up+down+down+down+down, not b+down -} - -// Force the unittest URI scheme to be linked, -static int LLVM_ATTRIBUTE_UNUSED UseUnittestScheme = UnittestSchemeAnchorSource; - -TEST(FileDistanceTests, URI) { - FileDistanceOptions Opts; - Opts.UpCost = 5; - Opts.DownCost = 3; - SourceParams CostLots; - CostLots.Cost = 1000; - - URIDistance D({{testPath("foo"), CostLots}, - {"/not/a/testpath", SourceParams()}, - {"C:\\not\\a\\testpath", SourceParams()}}, - Opts); -#ifdef _WIN32 - EXPECT_EQ(D.distance("file:///C%3a/not/a/testpath/either"), 3u); -#else - EXPECT_EQ(D.distance("file:///not/a/testpath/either"), 3u); -#endif - EXPECT_EQ(D.distance("unittest:///foo"), 1000u); - EXPECT_EQ(D.distance("unittest:///bar"), 1008u); -} - -TEST(FileDistance, LimitUpTraversals) { - FileDistanceOptions Opts; - Opts.UpCost = Opts.DownCost = 1; - SourceParams CheapButLimited, CostLots; - CheapButLimited.MaxUpTraversals = 1; - CostLots.Cost = 100; - - FileDistance D({{"/", CostLots}, {"/a/b/c", CheapButLimited}}, Opts); - EXPECT_EQ(D.distance("/a"), 101u); - EXPECT_EQ(D.distance("/a/z"), 102u); - EXPECT_EQ(D.distance("/a/b"), 1u); - EXPECT_EQ(D.distance("/a/b/z"), 2u); -} - -TEST(FileDistance, DisallowDownTraversalsFromRoot) { - FileDistanceOptions Opts; - Opts.UpCost = Opts.DownCost = 1; - Opts.AllowDownTraversalFromRoot = false; - SourceParams CostLots; - CostLots.Cost = 100; - - FileDistance D({{"/", SourceParams()}, {"/a/b/c", CostLots}}, Opts); - EXPECT_EQ(D.distance("/"), 0u); - EXPECT_EQ(D.distance("/a"), 102u); - EXPECT_EQ(D.distance("/a/b"), 101u); - EXPECT_EQ(D.distance("/x"), FileDistance::Unreachable); -} - -TEST(ScopeDistance, Smoke) { - ScopeDistance D({"x::y::z", "x::", "", "a::"}); - EXPECT_EQ(D.distance("x::y::z::"), 0u); - EXPECT_GT(D.distance("x::y::"), D.distance("x::y::z::")); - EXPECT_GT(D.distance("x::"), D.distance("x::y::")); - EXPECT_GT(D.distance("x::y::z::down::"), D.distance("x::y::")); - EXPECT_GT(D.distance(""), D.distance("a::")); - EXPECT_GT(D.distance("x::"), D.distance("a::")); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/FileIndexTests.cpp b/clang-tools-extra/unittests/clangd/FileIndexTests.cpp deleted file mode 100644 index 4cf589c7d8b..00000000000 --- a/clang-tools-extra/unittests/clangd/FileIndexTests.cpp +++ /dev/null @@ -1,371 +0,0 @@ -//===-- FileIndexTests.cpp ---------------------------*- C++ -*-----------===// -// -// 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 "AST.h" -#include "Annotations.h" -#include "ClangdUnit.h" -#include "SyncAPI.h" -#include "TestFS.h" -#include "TestTU.h" -#include "index/CanonicalIncludes.h" -#include "index/FileIndex.h" -#include "index/Index.h" -#include "clang/Frontend/CompilerInvocation.h" -#include "clang/Frontend/Utils.h" -#include "clang/Index/IndexSymbol.h" -#include "clang/Lex/Preprocessor.h" -#include "clang/Tooling/CompilationDatabase.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -using testing::_; -using testing::AllOf; -using testing::Contains; -using testing::ElementsAre; -using testing::IsEmpty; -using testing::Pair; -using testing::UnorderedElementsAre; - -MATCHER_P(RefRange, Range, "") { - return std::make_tuple(arg.Location.Start.line(), arg.Location.Start.column(), - arg.Location.End.line(), arg.Location.End.column()) == - std::make_tuple(Range.start.line, Range.start.character, - Range.end.line, Range.end.character); -} -MATCHER_P(FileURI, F, "") { return llvm::StringRef(arg.Location.FileURI) == F; } -MATCHER_P(DeclURI, U, "") { - return llvm::StringRef(arg.CanonicalDeclaration.FileURI) == U; -} -MATCHER_P(DefURI, U, "") { - return llvm::StringRef(arg.Definition.FileURI) == U; -} -MATCHER_P(QName, N, "") { return (arg.Scope + arg.Name).str() == N; } - -namespace clang { -namespace clangd { -namespace { -testing::Matcher<const RefSlab &> -RefsAre(std::vector<testing::Matcher<Ref>> Matchers) { - return ElementsAre(testing::Pair(_, UnorderedElementsAreArray(Matchers))); -} - -Symbol symbol(llvm::StringRef ID) { - Symbol Sym; - Sym.ID = SymbolID(ID); - Sym.Name = ID; - return Sym; -} - -std::unique_ptr<SymbolSlab> numSlab(int Begin, int End) { - SymbolSlab::Builder Slab; - for (int i = Begin; i <= End; i++) - Slab.insert(symbol(std::to_string(i))); - return llvm::make_unique<SymbolSlab>(std::move(Slab).build()); -} - -std::unique_ptr<RefSlab> refSlab(const SymbolID &ID, const char *Path) { - RefSlab::Builder Slab; - Ref R; - R.Location.FileURI = Path; - R.Kind = RefKind::Reference; - Slab.insert(ID, R); - return llvm::make_unique<RefSlab>(std::move(Slab).build()); -} - -TEST(FileSymbolsTest, UpdateAndGet) { - FileSymbols FS; - EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""), IsEmpty()); - - FS.update("f1", numSlab(1, 3), refSlab(SymbolID("1"), "f1.cc")); - EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""), - UnorderedElementsAre(QName("1"), QName("2"), QName("3"))); - EXPECT_THAT(getRefs(*FS.buildIndex(IndexType::Light), SymbolID("1")), - RefsAre({FileURI("f1.cc")})); -} - -TEST(FileSymbolsTest, Overlap) { - FileSymbols FS; - FS.update("f1", numSlab(1, 3), nullptr); - FS.update("f2", numSlab(3, 5), nullptr); - for (auto Type : {IndexType::Light, IndexType::Heavy}) - EXPECT_THAT(runFuzzyFind(*FS.buildIndex(Type), ""), - UnorderedElementsAre(QName("1"), QName("2"), QName("3"), - QName("4"), QName("5"))); -} - -TEST(FileSymbolsTest, MergeOverlap) { - FileSymbols FS; - auto OneSymboSlab = [](Symbol Sym) { - SymbolSlab::Builder S; - S.insert(Sym); - return llvm::make_unique<SymbolSlab>(std::move(S).build()); - }; - auto X1 = symbol("x"); - X1.CanonicalDeclaration.FileURI = "file:///x1"; - auto X2 = symbol("x"); - X2.Definition.FileURI = "file:///x2"; - - FS.update("f1", OneSymboSlab(X1), nullptr); - FS.update("f2", OneSymboSlab(X2), nullptr); - for (auto Type : {IndexType::Light, IndexType::Heavy}) - EXPECT_THAT( - runFuzzyFind(*FS.buildIndex(Type, DuplicateHandling::Merge), "x"), - UnorderedElementsAre( - AllOf(QName("x"), DeclURI("file:///x1"), DefURI("file:///x2")))); -} - -TEST(FileSymbolsTest, SnapshotAliveAfterRemove) { - FileSymbols FS; - - SymbolID ID("1"); - FS.update("f1", numSlab(1, 3), refSlab(ID, "f1.cc")); - - auto Symbols = FS.buildIndex(IndexType::Light); - EXPECT_THAT(runFuzzyFind(*Symbols, ""), - UnorderedElementsAre(QName("1"), QName("2"), QName("3"))); - EXPECT_THAT(getRefs(*Symbols, ID), RefsAre({FileURI("f1.cc")})); - - FS.update("f1", nullptr, nullptr); - auto Empty = FS.buildIndex(IndexType::Light); - EXPECT_THAT(runFuzzyFind(*Empty, ""), IsEmpty()); - EXPECT_THAT(getRefs(*Empty, ID), ElementsAre()); - - EXPECT_THAT(runFuzzyFind(*Symbols, ""), - UnorderedElementsAre(QName("1"), QName("2"), QName("3"))); - EXPECT_THAT(getRefs(*Symbols, ID), RefsAre({FileURI("f1.cc")})); -} - -// Adds Basename.cpp, which includes Basename.h, which contains Code. -void update(FileIndex &M, llvm::StringRef Basename, llvm::StringRef Code) { - TestTU File; - File.Filename = (Basename + ".cpp").str(); - File.HeaderFilename = (Basename + ".h").str(); - File.HeaderCode = Code; - auto AST = File.build(); - M.updatePreamble(File.Filename, AST.getASTContext(), AST.getPreprocessorPtr(), - AST.getCanonicalIncludes()); -} - -TEST(FileIndexTest, CustomizedURIScheme) { - FileIndex M; - update(M, "f", "class string {};"); - - EXPECT_THAT(runFuzzyFind(M, ""), ElementsAre(DeclURI("unittest:///f.h"))); -} - -TEST(FileIndexTest, IndexAST) { - FileIndex M; - update(M, "f1", "namespace ns { void f() {} class X {}; }"); - - FuzzyFindRequest Req; - Req.Query = ""; - Req.Scopes = {"ns::"}; - EXPECT_THAT(runFuzzyFind(M, Req), - UnorderedElementsAre(QName("ns::f"), QName("ns::X"))); -} - -TEST(FileIndexTest, NoLocal) { - FileIndex M; - update(M, "f1", "namespace ns { void f() { int local = 0; } class X {}; }"); - - EXPECT_THAT( - runFuzzyFind(M, ""), - UnorderedElementsAre(QName("ns"), QName("ns::f"), QName("ns::X"))); -} - -TEST(FileIndexTest, IndexMultiASTAndDeduplicate) { - FileIndex M; - update(M, "f1", "namespace ns { void f() {} class X {}; }"); - update(M, "f2", "namespace ns { void ff() {} class X {}; }"); - - FuzzyFindRequest Req; - Req.Scopes = {"ns::"}; - EXPECT_THAT( - runFuzzyFind(M, Req), - UnorderedElementsAre(QName("ns::f"), QName("ns::X"), QName("ns::ff"))); -} - -TEST(FileIndexTest, ClassMembers) { - FileIndex M; - update(M, "f1", "class X { static int m1; int m2; static void f(); };"); - - EXPECT_THAT(runFuzzyFind(M, ""), - UnorderedElementsAre(QName("X"), QName("X::m1"), QName("X::m2"), - QName("X::f"))); -} - -TEST(FileIndexTest, IncludeCollected) { - FileIndex M; - update( - M, "f", - "// IWYU pragma: private, include <the/good/header.h>\nclass string {};"); - - auto Symbols = runFuzzyFind(M, ""); - EXPECT_THAT(Symbols, ElementsAre(_)); - EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader, - "<the/good/header.h>"); -} - -TEST(FileIndexTest, HasSystemHeaderMappingsInPreamble) { - TestTU TU; - TU.HeaderCode = "class Foo{};"; - TU.HeaderFilename = "algorithm"; - - auto Symbols = runFuzzyFind(*TU.index(), ""); - EXPECT_THAT(Symbols, ElementsAre(_)); - EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader, - "<algorithm>"); -} - -TEST(FileIndexTest, TemplateParamsInLabel) { - auto Source = R"cpp( -template <class Ty> -class vector { -}; - -template <class Ty, class Arg> -vector<Ty> make_vector(Arg A) {} -)cpp"; - - FileIndex M; - update(M, "f", Source); - - auto Symbols = runFuzzyFind(M, ""); - EXPECT_THAT(Symbols, - UnorderedElementsAre(QName("vector"), QName("make_vector"))); - auto It = Symbols.begin(); - Symbol Vector = *It++; - Symbol MakeVector = *It++; - if (MakeVector.Name == "vector") - std::swap(MakeVector, Vector); - - EXPECT_EQ(Vector.Signature, "<class Ty>"); - EXPECT_EQ(Vector.CompletionSnippetSuffix, "<${1:class Ty}>"); - - EXPECT_EQ(MakeVector.Signature, "<class Ty>(Arg A)"); - EXPECT_EQ(MakeVector.CompletionSnippetSuffix, "<${1:class Ty}>(${2:Arg A})"); -} - -TEST(FileIndexTest, RebuildWithPreamble) { - auto FooCpp = testPath("foo.cpp"); - auto FooH = testPath("foo.h"); - // Preparse ParseInputs. - ParseInputs PI; - PI.CompileCommand.Directory = testRoot(); - PI.CompileCommand.Filename = FooCpp; - PI.CompileCommand.CommandLine = {"clang", "-xc++", FooCpp}; - - llvm::StringMap<std::string> Files; - Files[FooCpp] = ""; - Files[FooH] = R"cpp( - namespace ns_in_header { - int func_in_header(); - } - )cpp"; - PI.FS = buildTestFS(std::move(Files)); - - PI.Contents = R"cpp( - #include "foo.h" - namespace ns_in_source { - int func_in_source(); - } - )cpp"; - - // Rebuild the file. - auto CI = buildCompilerInvocation(PI); - - FileIndex Index; - bool IndexUpdated = false; - buildPreamble( - FooCpp, *CI, /*OldPreamble=*/nullptr, tooling::CompileCommand(), PI, - /*StoreInMemory=*/true, - [&](ASTContext &Ctx, std::shared_ptr<Preprocessor> PP, - const CanonicalIncludes &CanonIncludes) { - EXPECT_FALSE(IndexUpdated) << "Expected only a single index update"; - IndexUpdated = true; - Index.updatePreamble(FooCpp, Ctx, std::move(PP), CanonIncludes); - }); - ASSERT_TRUE(IndexUpdated); - - // Check the index contains symbols from the preamble, but not from the main - // file. - FuzzyFindRequest Req; - Req.Query = ""; - Req.Scopes = {"", "ns_in_header::"}; - - EXPECT_THAT(runFuzzyFind(Index, Req), - UnorderedElementsAre(QName("ns_in_header"), - QName("ns_in_header::func_in_header"))); -} - -TEST(FileIndexTest, Refs) { - const char *HeaderCode = "class Foo {};"; - Annotations MainCode(R"cpp( - void f() { - $foo[[Foo]] foo; - } - )cpp"); - - auto Foo = - findSymbol(TestTU::withHeaderCode(HeaderCode).headerSymbols(), "Foo"); - - RefsRequest Request; - Request.IDs = {Foo.ID}; - - FileIndex Index; - // Add test.cc - TestTU Test; - Test.HeaderCode = HeaderCode; - Test.Code = MainCode.code(); - Test.Filename = "test.cc"; - auto AST = Test.build(); - Index.updateMain(Test.Filename, AST); - // Add test2.cc - TestTU Test2; - Test2.HeaderCode = HeaderCode; - Test2.Code = MainCode.code(); - Test2.Filename = "test2.cc"; - AST = Test2.build(); - Index.updateMain(Test2.Filename, AST); - - EXPECT_THAT(getRefs(Index, Foo.ID), - RefsAre({AllOf(RefRange(MainCode.range("foo")), - FileURI("unittest:///test.cc")), - AllOf(RefRange(MainCode.range("foo")), - FileURI("unittest:///test2.cc"))})); -} - -TEST(FileIndexTest, CollectMacros) { - FileIndex M; - update(M, "f", "#define CLANGD 1"); - EXPECT_THAT(runFuzzyFind(M, ""), Contains(QName("CLANGD"))); -} - -TEST(FileIndexTest, ReferencesInMainFileWithPreamble) { - TestTU TU; - TU.HeaderCode = "class Foo{};"; - Annotations Main(R"cpp( - #include "foo.h" - void f() { - [[Foo]] foo; - } - )cpp"); - TU.Code = Main.code(); - auto AST = TU.build(); - FileIndex Index; - Index.updateMain(testPath(TU.Filename), AST); - - // Expect to see references in main file, references in headers are excluded - // because we only index main AST. - EXPECT_THAT(getRefs(Index, findSymbol(TU.headerSymbols(), "Foo").ID), - RefsAre({RefRange(Main.range())})); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/FindSymbolsTests.cpp b/clang-tools-extra/unittests/clangd/FindSymbolsTests.cpp deleted file mode 100644 index edb6248979e..00000000000 --- a/clang-tools-extra/unittests/clangd/FindSymbolsTests.cpp +++ /dev/null @@ -1,688 +0,0 @@ -//===-- FindSymbolsTests.cpp -------------------------*- C++ -*------------===// -// -// 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 "Annotations.h" -#include "ClangdServer.h" -#include "FindSymbols.h" -#include "SyncAPI.h" -#include "TestFS.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { - -namespace { - -using ::testing::AllOf; -using ::testing::AnyOf; -using ::testing::ElementsAre; -using ::testing::ElementsAreArray; -using ::testing::Field; -using ::testing::IsEmpty; -using ::testing::UnorderedElementsAre; - -class IgnoreDiagnostics : public DiagnosticsConsumer { - void onDiagnosticsReady(PathRef File, - std::vector<Diag> Diagnostics) override {} -}; - -// GMock helpers for matching SymbolInfos items. -MATCHER_P(QName, Name, "") { - if (arg.containerName.empty()) - return arg.name == Name; - return (arg.containerName + "::" + arg.name) == Name; -} -MATCHER_P(WithName, N, "") { return arg.name == N; } -MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; } -MATCHER_P(SymRange, Range, "") { return arg.location.range == Range; } - -// GMock helpers for matching DocumentSymbol. -MATCHER_P(SymNameRange, Range, "") { return arg.selectionRange == Range; } -template <class... ChildMatchers> -testing::Matcher<DocumentSymbol> Children(ChildMatchers... ChildrenM) { - return Field(&DocumentSymbol::children, ElementsAre(ChildrenM...)); -} - -ClangdServer::Options optsForTests() { - auto ServerOpts = ClangdServer::optsForTest(); - ServerOpts.WorkspaceRoot = testRoot(); - ServerOpts.BuildDynamicSymbolIndex = true; - return ServerOpts; -} - -class WorkspaceSymbolsTest : public ::testing::Test { -public: - WorkspaceSymbolsTest() - : Server(CDB, FSProvider, DiagConsumer, optsForTests()) { - // Make sure the test root directory is created. - FSProvider.Files[testPath("unused")] = ""; - CDB.ExtraClangFlags = {"-xc++"}; - } - -protected: - MockFSProvider FSProvider; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server; - int Limit = 0; - - std::vector<SymbolInformation> getSymbols(llvm::StringRef Query) { - EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; - auto SymbolInfos = runWorkspaceSymbols(Server, Query, Limit); - EXPECT_TRUE(bool(SymbolInfos)) << "workspaceSymbols returned an error"; - return *SymbolInfos; - } - - void addFile(llvm::StringRef FileName, llvm::StringRef Contents) { - auto Path = testPath(FileName); - FSProvider.Files[Path] = Contents; - Server.addDocument(Path, Contents); - } -}; - -} // namespace - -TEST_F(WorkspaceSymbolsTest, Macros) { - addFile("foo.cpp", R"cpp( - #define MACRO X - )cpp"); - - // LSP's SymbolKind doesn't have a "Macro" kind, and - // indexSymbolKindToSymbolKind() currently maps macros - // to SymbolKind::String. - EXPECT_THAT(getSymbols("macro"), - ElementsAre(AllOf(QName("MACRO"), WithKind(SymbolKind::String)))); -} - -TEST_F(WorkspaceSymbolsTest, NoLocals) { - addFile("foo.cpp", R"cpp( - void test(int FirstParam, int SecondParam) { - struct LocalClass {}; - int local_var; - })cpp"); - EXPECT_THAT(getSymbols("l"), IsEmpty()); - EXPECT_THAT(getSymbols("p"), IsEmpty()); -} - -TEST_F(WorkspaceSymbolsTest, Globals) { - addFile("foo.h", R"cpp( - int global_var; - - int global_func(); - - struct GlobalStruct {};)cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols("global"), - UnorderedElementsAre( - AllOf(QName("GlobalStruct"), WithKind(SymbolKind::Struct)), - AllOf(QName("global_func"), WithKind(SymbolKind::Function)), - AllOf(QName("global_var"), WithKind(SymbolKind::Variable)))); -} - -TEST_F(WorkspaceSymbolsTest, Unnamed) { - addFile("foo.h", R"cpp( - struct { - int InUnnamed; - } UnnamedStruct;)cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols("UnnamedStruct"), - ElementsAre(AllOf(QName("UnnamedStruct"), - WithKind(SymbolKind::Variable)))); - EXPECT_THAT(getSymbols("InUnnamed"), - ElementsAre(AllOf(QName("(anonymous struct)::InUnnamed"), - WithKind(SymbolKind::Field)))); -} - -TEST_F(WorkspaceSymbolsTest, InMainFile) { - addFile("foo.cpp", R"cpp( - int test() {} - static test2() {} - )cpp"); - EXPECT_THAT(getSymbols("test"), ElementsAre(QName("test"), QName("test2"))); -} - -TEST_F(WorkspaceSymbolsTest, Namespaces) { - addFile("foo.h", R"cpp( - namespace ans1 { - int ai1; - namespace ans2 { - int ai2; - } - } - )cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols("a"), - UnorderedElementsAre(QName("ans1"), QName("ans1::ai1"), - QName("ans1::ans2"), - QName("ans1::ans2::ai2"))); - EXPECT_THAT(getSymbols("::"), ElementsAre(QName("ans1"))); - EXPECT_THAT(getSymbols("::a"), ElementsAre(QName("ans1"))); - EXPECT_THAT(getSymbols("ans1::"), - UnorderedElementsAre(QName("ans1::ai1"), QName("ans1::ans2"))); - EXPECT_THAT(getSymbols("::ans1"), ElementsAre(QName("ans1"))); - EXPECT_THAT(getSymbols("::ans1::"), - UnorderedElementsAre(QName("ans1::ai1"), QName("ans1::ans2"))); - EXPECT_THAT(getSymbols("::ans1::ans2"), ElementsAre(QName("ans1::ans2"))); - EXPECT_THAT(getSymbols("::ans1::ans2::"), - ElementsAre(QName("ans1::ans2::ai2"))); -} - -TEST_F(WorkspaceSymbolsTest, AnonymousNamespace) { - addFile("foo.h", R"cpp( - namespace { - void test() {} - } - )cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols("test"), ElementsAre(QName("test"))); -} - -TEST_F(WorkspaceSymbolsTest, MultiFile) { - addFile("foo.h", R"cpp( - int foo() { - } - )cpp"); - addFile("foo2.h", R"cpp( - int foo2() { - } - )cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - #include "foo2.h" - )cpp"); - EXPECT_THAT(getSymbols("foo"), - UnorderedElementsAre(QName("foo"), QName("foo2"))); -} - -TEST_F(WorkspaceSymbolsTest, GlobalNamespaceQueries) { - addFile("foo.h", R"cpp( - int foo() { - } - class Foo { - int a; - }; - namespace ns { - int foo2() { - } - } - )cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols("::"), - UnorderedElementsAre( - AllOf(QName("Foo"), WithKind(SymbolKind::Class)), - AllOf(QName("foo"), WithKind(SymbolKind::Function)), - AllOf(QName("ns"), WithKind(SymbolKind::Namespace)))); - EXPECT_THAT(getSymbols(":"), IsEmpty()); - EXPECT_THAT(getSymbols(""), IsEmpty()); -} - -TEST_F(WorkspaceSymbolsTest, Enums) { - addFile("foo.h", R"cpp( - enum { - Red - }; - enum Color { - Green - }; - enum class Color2 { - Yellow - }; - namespace ns { - enum { - Black - }; - enum Color3 { - Blue - }; - enum class Color4 { - White - }; - } - )cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols("Red"), ElementsAre(QName("Red"))); - EXPECT_THAT(getSymbols("::Red"), ElementsAre(QName("Red"))); - EXPECT_THAT(getSymbols("Green"), ElementsAre(QName("Green"))); - EXPECT_THAT(getSymbols("Green"), ElementsAre(QName("Green"))); - EXPECT_THAT(getSymbols("Color2::Yellow"), - ElementsAre(QName("Color2::Yellow"))); - EXPECT_THAT(getSymbols("Yellow"), ElementsAre(QName("Color2::Yellow"))); - - EXPECT_THAT(getSymbols("ns::Black"), ElementsAre(QName("ns::Black"))); - EXPECT_THAT(getSymbols("ns::Blue"), ElementsAre(QName("ns::Blue"))); - EXPECT_THAT(getSymbols("ns::Color4::White"), - ElementsAre(QName("ns::Color4::White"))); -} - -TEST_F(WorkspaceSymbolsTest, Ranking) { - addFile("foo.h", R"cpp( - namespace ns{} - void func(); - )cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols("::"), ElementsAre(QName("func"), QName("ns"))); -} - -TEST_F(WorkspaceSymbolsTest, WithLimit) { - addFile("foo.h", R"cpp( - int foo; - int foo2; - )cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - // Foo is higher ranked because of exact name match. - EXPECT_THAT(getSymbols("foo"), - UnorderedElementsAre( - AllOf(QName("foo"), WithKind(SymbolKind::Variable)), - AllOf(QName("foo2"), WithKind(SymbolKind::Variable)))); - - Limit = 1; - EXPECT_THAT(getSymbols("foo"), ElementsAre(QName("foo"))); -} - -TEST_F(WorkspaceSymbolsTest, TempSpecs) { - addFile("foo.h", R"cpp( - template <typename T, typename U, int X = 5> class Foo {}; - template <typename T> class Foo<int, T> {}; - template <> class Foo<bool, int> {}; - template <> class Foo<bool, int, 3> {}; - )cpp"); - // Foo is higher ranked because of exact name match. - EXPECT_THAT( - getSymbols("Foo"), - UnorderedElementsAre( - AllOf(QName("Foo"), WithKind(SymbolKind::Class)), - AllOf(QName("Foo<int, T>"), WithKind(SymbolKind::Class)), - AllOf(QName("Foo<bool, int>"), WithKind(SymbolKind::Class)), - AllOf(QName("Foo<bool, int, 3>"), WithKind(SymbolKind::Class)))); -} - -namespace { -class DocumentSymbolsTest : public ::testing::Test { -public: - DocumentSymbolsTest() - : Server(CDB, FSProvider, DiagConsumer, optsForTests()) {} - -protected: - MockFSProvider FSProvider; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server; - - std::vector<DocumentSymbol> getSymbols(PathRef File) { - EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; - auto SymbolInfos = runDocumentSymbols(Server, File); - EXPECT_TRUE(bool(SymbolInfos)) << "documentSymbols returned an error"; - return *SymbolInfos; - } - - void addFile(llvm::StringRef FilePath, llvm::StringRef Contents) { - FSProvider.Files[FilePath] = Contents; - Server.addDocument(FilePath, Contents); - } -}; -} // namespace - -TEST_F(DocumentSymbolsTest, BasicSymbols) { - std::string FilePath = testPath("foo.cpp"); - Annotations Main(R"( - class Foo; - class Foo { - Foo() {} - Foo(int a) {} - void $decl[[f]](); - friend void f1(); - friend class Friend; - Foo& operator=(const Foo&); - ~Foo(); - class Nested { - void f(); - }; - }; - class Friend { - }; - - void f1(); - inline void f2() {} - static const int KInt = 2; - const char* kStr = "123"; - - void f1() {} - - namespace foo { - // Type alias - typedef int int32; - using int32_t = int32; - - // Variable - int v1; - - // Namespace - namespace bar { - int v2; - } - // Namespace alias - namespace baz = bar; - - using bar::v2; - } // namespace foo - )"); - - addFile(FilePath, Main.code()); - EXPECT_THAT( - getSymbols(FilePath), - ElementsAreArray( - {AllOf(WithName("Foo"), WithKind(SymbolKind::Class), Children()), - AllOf(WithName("Foo"), WithKind(SymbolKind::Class), - Children(AllOf(WithName("Foo"), WithKind(SymbolKind::Method), - Children()), - AllOf(WithName("Foo"), WithKind(SymbolKind::Method), - Children()), - AllOf(WithName("f"), WithKind(SymbolKind::Method), - Children()), - AllOf(WithName("operator="), - WithKind(SymbolKind::Method), Children()), - AllOf(WithName("~Foo"), WithKind(SymbolKind::Method), - Children()), - AllOf(WithName("Nested"), WithKind(SymbolKind::Class), - Children(AllOf(WithName("f"), - WithKind(SymbolKind::Method), - Children()))))), - AllOf(WithName("Friend"), WithKind(SymbolKind::Class), Children()), - AllOf(WithName("f1"), WithKind(SymbolKind::Function), Children()), - AllOf(WithName("f2"), WithKind(SymbolKind::Function), Children()), - AllOf(WithName("KInt"), WithKind(SymbolKind::Variable), Children()), - AllOf(WithName("kStr"), WithKind(SymbolKind::Variable), Children()), - AllOf(WithName("f1"), WithKind(SymbolKind::Function), Children()), - AllOf(WithName("foo"), WithKind(SymbolKind::Namespace), - Children( - AllOf(WithName("int32"), WithKind(SymbolKind::Class), - Children()), - AllOf(WithName("int32_t"), WithKind(SymbolKind::Class), - Children()), - AllOf(WithName("v1"), WithKind(SymbolKind::Variable), - Children()), - AllOf(WithName("bar"), WithKind(SymbolKind::Namespace), - Children(AllOf(WithName("v2"), - WithKind(SymbolKind::Variable), - Children()))), - AllOf(WithName("baz"), WithKind(SymbolKind::Namespace), - Children()), - AllOf(WithName("v2"), WithKind(SymbolKind::Namespace))))})); -} - -TEST_F(DocumentSymbolsTest, DeclarationDefinition) { - std::string FilePath = testPath("foo.cpp"); - Annotations Main(R"( - class Foo { - void $decl[[f]](); - }; - void Foo::$def[[f]]() { - } - )"); - - addFile(FilePath, Main.code()); - EXPECT_THAT(getSymbols(FilePath), - ElementsAre(AllOf(WithName("Foo"), WithKind(SymbolKind::Class), - Children(AllOf( - WithName("f"), WithKind(SymbolKind::Method), - SymNameRange(Main.range("decl"))))), - AllOf(WithName("f"), WithKind(SymbolKind::Method), - SymNameRange(Main.range("def"))))); -} - -TEST_F(DocumentSymbolsTest, ExternSymbol) { - std::string FilePath = testPath("foo.cpp"); - addFile(testPath("foo.h"), R"cpp( - extern int var; - )cpp"); - addFile(FilePath, R"cpp( - #include "foo.h" - )cpp"); - - EXPECT_THAT(getSymbols(FilePath), IsEmpty()); -} - -TEST_F(DocumentSymbolsTest, NoLocals) { - std::string FilePath = testPath("foo.cpp"); - addFile(FilePath, - R"cpp( - void test(int FirstParam, int SecondParam) { - struct LocalClass {}; - int local_var; - })cpp"); - EXPECT_THAT(getSymbols(FilePath), ElementsAre(WithName("test"))); -} - -TEST_F(DocumentSymbolsTest, Unnamed) { - std::string FilePath = testPath("foo.h"); - addFile(FilePath, - R"cpp( - struct { - int InUnnamed; - } UnnamedStruct; - )cpp"); - EXPECT_THAT( - getSymbols(FilePath), - ElementsAre( - AllOf(WithName("(anonymous struct)"), WithKind(SymbolKind::Struct), - Children(AllOf(WithName("InUnnamed"), - WithKind(SymbolKind::Field), Children()))), - AllOf(WithName("UnnamedStruct"), WithKind(SymbolKind::Variable), - Children()))); -} - -TEST_F(DocumentSymbolsTest, InHeaderFile) { - addFile(testPath("bar.h"), R"cpp( - int foo() { - } - )cpp"); - std::string FilePath = testPath("foo.h"); - addFile(FilePath, R"cpp( - #include "bar.h" - int test() { - } - )cpp"); - addFile(testPath("foo.cpp"), R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols(FilePath), ElementsAre(WithName("test"))); -} - -TEST_F(DocumentSymbolsTest, Template) { - std::string FilePath = testPath("foo.cpp"); - addFile(FilePath, R"( - template <class T> struct Tmpl {T x = 0;}; - template <> struct Tmpl<int> { - int y = 0; - }; - extern template struct Tmpl<float>; - template struct Tmpl<double>; - - template <class T, class U, class Z = float> - int funcTmpl(U a); - template <> - int funcTmpl<int>(double a); - - template <class T, class U = double> - int varTmpl = T(); - template <> - double varTmpl<int> = 10.0; - )"); - EXPECT_THAT( - getSymbols(FilePath), - ElementsAre( - AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct), - Children(AllOf(WithName("x"), WithKind(SymbolKind::Field)))), - AllOf(WithName("Tmpl<int>"), WithKind(SymbolKind::Struct), - Children(WithName("y"))), - AllOf(WithName("Tmpl<float>"), WithKind(SymbolKind::Struct), - Children()), - AllOf(WithName("Tmpl<double>"), WithKind(SymbolKind::Struct), - Children()), - AllOf(WithName("funcTmpl"), Children()), - AllOf(WithName("funcTmpl<int>"), Children()), - AllOf(WithName("varTmpl"), Children()), - AllOf(WithName("varTmpl<int>"), Children()))); -} - -TEST_F(DocumentSymbolsTest, Namespaces) { - std::string FilePath = testPath("foo.cpp"); - addFile(FilePath, R"cpp( - namespace ans1 { - int ai1; - namespace ans2 { - int ai2; - } - } - namespace { - void test() {} - } - - namespace na { - inline namespace nb { - class Foo {}; - } - } - namespace na { - // This is still inlined. - namespace nb { - class Bar {}; - } - } - )cpp"); - EXPECT_THAT( - getSymbols(FilePath), - ElementsAreArray<testing::Matcher<DocumentSymbol>>( - {AllOf(WithName("ans1"), - Children(AllOf(WithName("ai1"), Children()), - AllOf(WithName("ans2"), Children(WithName("ai2"))))), - AllOf(WithName("(anonymous namespace)"), Children(WithName("test"))), - AllOf(WithName("na"), - Children(AllOf(WithName("nb"), Children(WithName("Foo"))))), - AllOf(WithName("na"), - Children(AllOf(WithName("nb"), Children(WithName("Bar")))))})); -} - -TEST_F(DocumentSymbolsTest, Enums) { - std::string FilePath = testPath("foo.cpp"); - addFile(FilePath, R"( - enum { - Red - }; - enum Color { - Green - }; - enum class Color2 { - Yellow - }; - namespace ns { - enum { - Black - }; - } - )"); - EXPECT_THAT( - getSymbols(FilePath), - ElementsAre( - AllOf(WithName("(anonymous enum)"), Children(WithName("Red"))), - AllOf(WithName("Color"), Children(WithName("Green"))), - AllOf(WithName("Color2"), Children(WithName("Yellow"))), - AllOf(WithName("ns"), Children(AllOf(WithName("(anonymous enum)"), - Children(WithName("Black"))))))); -} - -TEST_F(DocumentSymbolsTest, FromMacro) { - std::string FilePath = testPath("foo.cpp"); - Annotations Main(R"( - #define FF(name) \ - class name##_Test {}; - - $expansion[[FF]](abc); - - #define FF2() \ - class $spelling[[Test]] {}; - - FF2(); - )"); - addFile(FilePath, Main.code()); - EXPECT_THAT( - getSymbols(FilePath), - ElementsAre( - AllOf(WithName("abc_Test"), SymNameRange(Main.range("expansion"))), - AllOf(WithName("Test"), SymNameRange(Main.range("spelling"))))); -} - -TEST_F(DocumentSymbolsTest, FuncTemplates) { - std::string FilePath = testPath("foo.cpp"); - Annotations Source(R"cpp( - template <class T> - T foo() {} - - auto x = foo<int>(); - auto y = foo<double>() - )cpp"); - addFile(FilePath, Source.code()); - // Make sure we only see the template declaration, not instantiations. - EXPECT_THAT(getSymbols(FilePath), - ElementsAre(WithName("foo"), WithName("x"), WithName("y"))); -} - -TEST_F(DocumentSymbolsTest, UsingDirectives) { - std::string FilePath = testPath("foo.cpp"); - Annotations Source(R"cpp( - namespace ns { - int foo; - } - - namespace ns_alias = ns; - - using namespace ::ns; // check we don't loose qualifiers. - using namespace ns_alias; // and namespace aliases. - )cpp"); - addFile(FilePath, Source.code()); - EXPECT_THAT(getSymbols(FilePath), - ElementsAre(WithName("ns"), WithName("ns_alias"), - WithName("using namespace ::ns"), - WithName("using namespace ns_alias"))); -} - -TEST_F(DocumentSymbolsTest, TempSpecs) { - addFile("foo.cpp", R"cpp( - template <typename T, typename U, int X = 5> class Foo {}; - template <typename T> class Foo<int, T> {}; - template <> class Foo<bool, int> {}; - template <> class Foo<bool, int, 3> {}; - )cpp"); - // Foo is higher ranked because of exact name match. - EXPECT_THAT( - getSymbols("foo.cpp"), - UnorderedElementsAre( - AllOf(WithName("Foo"), WithKind(SymbolKind::Class)), - AllOf(WithName("Foo<int, T>"), WithKind(SymbolKind::Class)), - AllOf(WithName("Foo<bool, int>"), WithKind(SymbolKind::Class)), - AllOf(WithName("Foo<bool, int, 3>"), WithKind(SymbolKind::Class)))); -} - -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/FunctionTests.cpp b/clang-tools-extra/unittests/clangd/FunctionTests.cpp deleted file mode 100644 index 0cd8b791046..00000000000 --- a/clang-tools-extra/unittests/clangd/FunctionTests.cpp +++ /dev/null @@ -1,51 +0,0 @@ -//===-- FunctionTests.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 "Function.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -TEST(EventTest, Subscriptions) { - Event<int> E; - int N = 0; - { - Event<int>::Subscription SubA; - // No subscriptions are active. - E.broadcast(42); - EXPECT_EQ(0, N); - - Event<int>::Subscription SubB = E.observe([&](int) { ++N; }); - // Now one is active. - E.broadcast(42); - EXPECT_EQ(1, N); - - SubA = E.observe([&](int) { ++N; }); - // Both are active. - EXPECT_EQ(1, N); - E.broadcast(42); - EXPECT_EQ(3, N); - - SubA = std::move(SubB); - // One is active. - EXPECT_EQ(3, N); - E.broadcast(42); - EXPECT_EQ(4, N); - } - // None are active. - EXPECT_EQ(4, N); - E.broadcast(42); - EXPECT_EQ(4, N); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/FuzzyMatchTests.cpp b/clang-tools-extra/unittests/clangd/FuzzyMatchTests.cpp deleted file mode 100644 index 6d5d88c0ed8..00000000000 --- a/clang-tools-extra/unittests/clangd/FuzzyMatchTests.cpp +++ /dev/null @@ -1,312 +0,0 @@ -//===-- FuzzyMatchTests.cpp - String fuzzy matcher tests --------*- C++ -*-===// -// -// 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 "FuzzyMatch.h" - -#include "llvm/ADT/StringExtras.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { -using testing::Not; - -struct ExpectedMatch { - // Annotations are optional, and will not be asserted if absent. - ExpectedMatch(llvm::StringRef Match) : Word(Match), Annotated(Match) { - for (char C : "[]") - Word.erase(std::remove(Word.begin(), Word.end(), C), Word.end()); - if (Word.size() == Annotated->size()) - Annotated = llvm::None; - } - bool accepts(llvm::StringRef ActualAnnotated) const { - return !Annotated || ActualAnnotated == *Annotated; - } - - friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const ExpectedMatch &M) { - OS << "'" << M.Word; - if (M.Annotated) - OS << "' as " << *M.Annotated; - return OS; - } - - std::string Word; - -private: - llvm::Optional<llvm::StringRef> Annotated; -}; - -struct MatchesMatcher : public testing::MatcherInterface<llvm::StringRef> { - ExpectedMatch Candidate; - llvm::Optional<float> Score; - MatchesMatcher(ExpectedMatch Candidate, llvm::Optional<float> Score) - : Candidate(std::move(Candidate)), Score(Score) {} - - void DescribeTo(::std::ostream *OS) const override { - llvm::raw_os_ostream(*OS) << "Matches " << Candidate; - if (Score) - *OS << " with score " << *Score; - } - - bool MatchAndExplain(llvm::StringRef Pattern, - testing::MatchResultListener *L) const override { - std::unique_ptr<llvm::raw_ostream> OS( - L->stream() - ? (llvm::raw_ostream *)(new llvm::raw_os_ostream(*L->stream())) - : new llvm::raw_null_ostream()); - FuzzyMatcher Matcher(Pattern); - auto Result = Matcher.match(Candidate.Word); - auto AnnotatedMatch = Matcher.dumpLast(*OS << "\n"); - return Result && Candidate.accepts(AnnotatedMatch) && - (!Score || testing::Value(*Result, testing::FloatEq(*Score))); - } -}; - -// Accepts patterns that match a given word, optionally requiring a score. -// Dumps the debug tables on match failure. -testing::Matcher<llvm::StringRef> matches(llvm::StringRef M, - llvm::Optional<float> Score = {}) { - return testing::MakeMatcher<llvm::StringRef>(new MatchesMatcher(M, Score)); -} - -TEST(FuzzyMatch, Matches) { - EXPECT_THAT("", matches("unique_ptr")); - EXPECT_THAT("u_p", matches("[u]nique[_p]tr")); - EXPECT_THAT("up", matches("[u]nique_[p]tr")); - EXPECT_THAT("uq", Not(matches("unique_ptr"))); - EXPECT_THAT("qp", Not(matches("unique_ptr"))); - EXPECT_THAT("log", Not(matches("SVGFEMorphologyElement"))); - - EXPECT_THAT("tit", matches("win.[tit]")); - EXPECT_THAT("title", matches("win.[title]")); - EXPECT_THAT("WordCla", matches("[Word]Character[Cla]ssifier")); - EXPECT_THAT("WordCCla", matches("[WordC]haracter[Cla]ssifier")); - - EXPECT_THAT("dete", Not(matches("editor.quickSuggestionsDelay"))); - - EXPECT_THAT("highlight", matches("editorHover[Highlight]")); - EXPECT_THAT("hhighlight", matches("editor[H]over[Highlight]")); - EXPECT_THAT("dhhighlight", Not(matches("editorHoverHighlight"))); - - EXPECT_THAT("-moz", matches("[-moz]-foo")); - EXPECT_THAT("moz", matches("-[moz]-foo")); - EXPECT_THAT("moza", matches("-[moz]-[a]nimation")); - - EXPECT_THAT("ab", matches("[ab]A")); - EXPECT_THAT("ccm", Not(matches("cacmelCase"))); - EXPECT_THAT("bti", Not(matches("the_black_knight"))); - EXPECT_THAT("ccm", Not(matches("camelCase"))); - EXPECT_THAT("cmcm", Not(matches("camelCase"))); - EXPECT_THAT("BK", matches("the_[b]lack_[k]night")); - EXPECT_THAT("KeyboardLayout=", Not(matches("KeyboardLayout"))); - EXPECT_THAT("LLL", matches("SVisual[L]ogger[L]ogs[L]ist")); - EXPECT_THAT("LLLL", Not(matches("SVilLoLosLi"))); - EXPECT_THAT("LLLL", Not(matches("SVisualLoggerLogsList"))); - EXPECT_THAT("TEdit", matches("[T]ext[Edit]")); - EXPECT_THAT("TEdit", matches("[T]ext[Edit]or")); - EXPECT_THAT("TEdit", Not(matches("[T]ext[edit]"))); - EXPECT_THAT("TEdit", matches("[t]ext_[edit]")); - EXPECT_THAT("TEditDt", matches("[T]ext[Edit]or[D]ecoration[T]ype")); - EXPECT_THAT("TEdit", matches("[T]ext[Edit]orDecorationType")); - EXPECT_THAT("Tedit", matches("[T]ext[Edit]")); - EXPECT_THAT("ba", Not(matches("?AB?"))); - EXPECT_THAT("bkn", matches("the_[b]lack_[kn]ight")); - EXPECT_THAT("bt", Not(matches("the_[b]lack_knigh[t]"))); - EXPECT_THAT("ccm", Not(matches("[c]amelCase[cm]"))); - EXPECT_THAT("fdm", Not(matches("[f]in[dM]odel"))); - EXPECT_THAT("fob", Not(matches("[fo]o[b]ar"))); - EXPECT_THAT("fobz", Not(matches("foobar"))); - EXPECT_THAT("foobar", matches("[foobar]")); - EXPECT_THAT("form", matches("editor.[form]atOnSave")); - EXPECT_THAT("g p", matches("[G]it:[ P]ull")); - EXPECT_THAT("g p", matches("[G]it:[ P]ull")); - EXPECT_THAT("gip", matches("[Gi]t: [P]ull")); - EXPECT_THAT("gip", matches("[Gi]t: [P]ull")); - EXPECT_THAT("gp", matches("[G]it: [P]ull")); - EXPECT_THAT("gp", matches("[G]it_Git_[P]ull")); - EXPECT_THAT("is", matches("[I]mport[S]tatement")); - EXPECT_THAT("is", matches("[is]Valid")); - EXPECT_THAT("lowrd", Not(matches("[low]Wo[rd]"))); - EXPECT_THAT("myvable", Not(matches("[myva]ria[ble]"))); - EXPECT_THAT("no", Not(matches(""))); - EXPECT_THAT("no", Not(matches("match"))); - EXPECT_THAT("ob", Not(matches("foobar"))); - EXPECT_THAT("sl", matches("[S]Visual[L]oggerLogsList")); - EXPECT_THAT("sllll", matches("[S]Visua[L]ogger[Ll]ama[L]ist")); - EXPECT_THAT("THRE", matches("H[T]ML[HRE]lement")); - EXPECT_THAT("b", Not(matches("NDEBUG"))); - EXPECT_THAT("Three", matches("[Three]")); - EXPECT_THAT("fo", Not(matches("barfoo"))); - EXPECT_THAT("fo", matches("bar_[fo]o")); - EXPECT_THAT("fo", matches("bar_[Fo]o")); - EXPECT_THAT("fo", matches("bar [fo]o")); - EXPECT_THAT("fo", matches("bar.[fo]o")); - EXPECT_THAT("fo", matches("bar/[fo]o")); - EXPECT_THAT("fo", matches("bar\\[fo]o")); - - EXPECT_THAT( - "aaaaaa", - matches("[aaaaaa]aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); - EXPECT_THAT("baba", Not(matches("ababababab"))); - EXPECT_THAT("fsfsfs", Not(matches("dsafdsafdsafdsafdsafdsafdsafasdfdsa"))); - EXPECT_THAT("fsfsfsfsfsfsfsf", - Not(matches("dsafdsafdsafdsafdsafdsafdsafasdfdsafdsafdsafdsafdsfd" - "safdsfdfdfasdnfdsajfndsjnafjndsajlknfdsa"))); - - EXPECT_THAT(" g", matches("[ g]roup")); - EXPECT_THAT("g", matches(" [g]roup")); - EXPECT_THAT("g g", Not(matches(" groupGroup"))); - EXPECT_THAT("g g", matches(" [g]roup[ G]roup")); - EXPECT_THAT(" g g", matches("[ ] [g]roup[ G]roup")); - EXPECT_THAT("zz", matches("[zz]Group")); - EXPECT_THAT("zzg", matches("[zzG]roup")); - EXPECT_THAT("g", matches("zz[G]roup")); - - EXPECT_THAT("aaaa", matches("_a_[aaaa]")); // Prefer consecutive. - // These would ideally match, but would need special segmentation rules. - EXPECT_THAT("printf", Not(matches("s[printf]"))); - EXPECT_THAT("str", Not(matches("o[str]eam"))); - EXPECT_THAT("strcpy", Not(matches("strncpy"))); - EXPECT_THAT("std", Not(matches("PTHREAD_MUTEX_STALLED"))); - EXPECT_THAT("std", Not(matches("pthread_condattr_setpshared"))); -} - -struct RankMatcher : public testing::MatcherInterface<llvm::StringRef> { - std::vector<ExpectedMatch> RankedStrings; - RankMatcher(std::initializer_list<ExpectedMatch> RankedStrings) - : RankedStrings(RankedStrings) {} - - void DescribeTo(::std::ostream *OS) const override { - llvm::raw_os_ostream O(*OS); - O << "Ranks strings in order: ["; - for (const auto &Str : RankedStrings) - O << "\n\t" << Str; - O << "\n]"; - } - - bool MatchAndExplain(llvm::StringRef Pattern, - testing::MatchResultListener *L) const override { - std::unique_ptr<llvm::raw_ostream> OS( - L->stream() - ? (llvm::raw_ostream *)(new llvm::raw_os_ostream(*L->stream())) - : new llvm::raw_null_ostream()); - FuzzyMatcher Matcher(Pattern); - const ExpectedMatch *LastMatch; - llvm::Optional<float> LastScore; - bool Ok = true; - for (const auto &Str : RankedStrings) { - auto Score = Matcher.match(Str.Word); - if (!Score) { - *OS << "\nDoesn't match '" << Str.Word << "'"; - Matcher.dumpLast(*OS << "\n"); - Ok = false; - } else { - std::string Buf; - llvm::raw_string_ostream Info(Buf); - auto AnnotatedMatch = Matcher.dumpLast(Info); - - if (!Str.accepts(AnnotatedMatch)) { - *OS << "\nDoesn't match " << Str << ", but " << AnnotatedMatch << "\n" - << Info.str(); - Ok = false; - } else if (LastScore && *LastScore < *Score) { - *OS << "\nRanks '" << Str.Word << "'=" << *Score << " above '" - << LastMatch->Word << "'=" << *LastScore << "\n" - << Info.str(); - Matcher.match(LastMatch->Word); - Matcher.dumpLast(*OS << "\n"); - Ok = false; - } - } - LastMatch = &Str; - LastScore = Score; - } - return Ok; - } -}; - -// Accepts patterns that match all the strings and rank them in the given order. -// Dumps the debug tables on match failure. -template <typename... T> -testing::Matcher<llvm::StringRef> ranks(T... RankedStrings) { - return testing::MakeMatcher<llvm::StringRef>( - new RankMatcher{ExpectedMatch(RankedStrings)...}); -} - -TEST(FuzzyMatch, Ranking) { - EXPECT_THAT("cons", - ranks("[cons]ole", "[Cons]ole", "ArrayBuffer[Cons]tructor")); - EXPECT_THAT("foo", ranks("[foo]", "[Foo]")); - EXPECT_THAT("onMes", - ranks("[onMes]sage", "[onmes]sage", "[on]This[M]ega[Es]capes")); - EXPECT_THAT("onmes", - ranks("[onmes]sage", "[onMes]sage", "[on]This[M]ega[Es]capes")); - EXPECT_THAT("CC", ranks("[C]amel[C]ase", "[c]amel[C]ase")); - EXPECT_THAT("cC", ranks("[c]amel[C]ase", "[C]amel[C]ase")); - EXPECT_THAT("p", ranks("[p]", "[p]arse", "[p]osix", "[p]afdsa", "[p]ath")); - EXPECT_THAT("pa", ranks("[pa]rse", "[pa]th", "[pa]fdsa")); - EXPECT_THAT("log", ranks("[log]", "Scroll[Log]icalPosition")); - EXPECT_THAT("e", ranks("[e]lse", "Abstract[E]lement")); - EXPECT_THAT("workbench.sideb", - ranks("[workbench.sideB]ar.location", - "[workbench.]editor.default[SideB]ySideLayout")); - EXPECT_THAT("editor.r", ranks("[editor.r]enderControlCharacter", - "[editor.]overview[R]ulerlanes", - "diff[Editor.r]enderSideBySide")); - EXPECT_THAT("-mo", ranks("[-mo]z-columns", "[-]ms-ime-[mo]de")); - EXPECT_THAT("convertModelPosition", - ranks("[convertModelPosition]ToViewPosition", - "[convert]ViewTo[ModelPosition]")); - EXPECT_THAT("is", ranks("[is]ValidViewletId", "[i]mport [s]tatement")); - EXPECT_THAT("strcpy", ranks("[strcpy]", "[strcpy]_s")); -} - -// Verify some bounds so we know scores fall in the right range. -// Testing exact scores is fragile, so we prefer Ranking tests. -TEST(FuzzyMatch, Scoring) { - EXPECT_THAT("abs", matches("[a]w[B]xYz[S]", 7.f / 12.f)); - EXPECT_THAT("abs", matches("[abs]l", 1.f)); - EXPECT_THAT("abs", matches("[abs]", 2.f)); - EXPECT_THAT("Abs", matches("[abs]", 2.f)); -} - -TEST(FuzzyMatch, InitialismAndPrefix) { - // We want these scores to be roughly the same. - EXPECT_THAT("up", matches("[u]nique_[p]tr", 3.f / 4.f)); - EXPECT_THAT("up", matches("[up]per_bound", 1.f)); -} - -// Returns pretty-printed segmentation of Text. -// e.g. std::basic_string --> +-- +---- +----- -std::string segment(llvm::StringRef Text) { - std::vector<CharRole> Roles(Text.size()); - calculateRoles(Text, Roles); - std::string Printed; - for (unsigned I = 0; I < Text.size(); ++I) - Printed.push_back("?-+ "[static_cast<unsigned>(Roles[I])]); - return Printed; -} - -// this is a no-op hack so clang-format will vertically align our testcases. -llvm::StringRef returns(llvm::StringRef Text) { return Text; } - -TEST(FuzzyMatch, Segmentation) { - EXPECT_THAT(segment("std::basic_string"), // - returns("+-- +---- +-----")); - EXPECT_THAT(segment("XMLHttpRequest"), // - returns("+--+---+------")); - EXPECT_THAT(segment("t3h PeNgU1N oF d00m!!!!!!!!"), // - returns("+-- +-+-+-+ ++ +--- ")); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/GlobalCompilationDatabaseTests.cpp b/clang-tools-extra/unittests/clangd/GlobalCompilationDatabaseTests.cpp deleted file mode 100644 index 7c7993cc0f9..00000000000 --- a/clang-tools-extra/unittests/clangd/GlobalCompilationDatabaseTests.cpp +++ /dev/null @@ -1,151 +0,0 @@ -//===-- GlobalCompilationDatabaseTests.cpp ----------------------*- C++ -*-===// -// -// 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 "GlobalCompilationDatabase.h" - -#include "TestFS.h" -#include "llvm/ADT/StringExtras.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { -using ::testing::AllOf; -using ::testing::Contains; -using ::testing::ElementsAre; -using ::testing::EndsWith; -using ::testing::Not; - -TEST(GlobalCompilationDatabaseTest, FallbackCommand) { - DirectoryBasedGlobalCompilationDatabase DB(None); - auto Cmd = DB.getFallbackCommand(testPath("foo/bar.cc")); - EXPECT_EQ(Cmd.Directory, testPath("foo")); - EXPECT_THAT(Cmd.CommandLine, - ElementsAre(EndsWith("clang"), testPath("foo/bar.cc"))); - EXPECT_EQ(Cmd.Output, ""); - - // .h files have unknown language, so they are parsed liberally as obj-c++. - Cmd = DB.getFallbackCommand(testPath("foo/bar.h")); - EXPECT_THAT(Cmd.CommandLine, - ElementsAre(EndsWith("clang"), "-xobjective-c++-header", - testPath("foo/bar.h"))); -} - -static tooling::CompileCommand cmd(llvm::StringRef File, llvm::StringRef Arg) { - return tooling::CompileCommand(testRoot(), File, {"clang", Arg, File}, ""); -} - -class OverlayCDBTest : public ::testing::Test { - class BaseCDB : public GlobalCompilationDatabase { - public: - llvm::Optional<tooling::CompileCommand> - getCompileCommand(llvm::StringRef File, - ProjectInfo *Project) const override { - if (File == testPath("foo.cc")) { - if (Project) - Project->SourceRoot = testRoot(); - return cmd(File, "-DA=1"); - } - return None; - } - - tooling::CompileCommand - getFallbackCommand(llvm::StringRef File) const override { - return cmd(File, "-DA=2"); - } - }; - -protected: - OverlayCDBTest() : Base(llvm::make_unique<BaseCDB>()) {} - std::unique_ptr<GlobalCompilationDatabase> Base; -}; - -TEST_F(OverlayCDBTest, GetCompileCommand) { - OverlayCDB CDB(Base.get(), {}, std::string("")); - EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine, - AllOf(Contains(testPath("foo.cc")), Contains("-DA=1"))); - EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), llvm::None); - - auto Override = cmd(testPath("foo.cc"), "-DA=3"); - CDB.setCompileCommand(testPath("foo.cc"), Override); - EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine, - Contains("-DA=3")); - EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), llvm::None); - CDB.setCompileCommand(testPath("missing.cc"), Override); - EXPECT_THAT(CDB.getCompileCommand(testPath("missing.cc"))->CommandLine, - Contains("-DA=3")); -} - -TEST_F(OverlayCDBTest, GetFallbackCommand) { - OverlayCDB CDB(Base.get(), {"-DA=4"}); - EXPECT_THAT(CDB.getFallbackCommand(testPath("bar.cc")).CommandLine, - ElementsAre("clang", "-DA=2", testPath("bar.cc"), "-DA=4")); -} - -TEST_F(OverlayCDBTest, NoBase) { - OverlayCDB CDB(nullptr, {"-DA=6"}, std::string("")); - EXPECT_EQ(CDB.getCompileCommand(testPath("bar.cc")), None); - auto Override = cmd(testPath("bar.cc"), "-DA=5"); - CDB.setCompileCommand(testPath("bar.cc"), Override); - EXPECT_THAT(CDB.getCompileCommand(testPath("bar.cc"))->CommandLine, - Contains("-DA=5")); - - EXPECT_THAT(CDB.getFallbackCommand(testPath("foo.cc")).CommandLine, - ElementsAre(EndsWith("clang"), testPath("foo.cc"), "-DA=6")); -} - -TEST_F(OverlayCDBTest, Watch) { - OverlayCDB Inner(nullptr); - OverlayCDB Outer(&Inner); - - std::vector<std::vector<std::string>> Changes; - auto Sub = Outer.watch([&](const std::vector<std::string> &ChangedFiles) { - Changes.push_back(ChangedFiles); - }); - - Inner.setCompileCommand("A.cpp", tooling::CompileCommand()); - Outer.setCompileCommand("B.cpp", tooling::CompileCommand()); - Inner.setCompileCommand("A.cpp", llvm::None); - Outer.setCompileCommand("C.cpp", llvm::None); - EXPECT_THAT(Changes, ElementsAre(ElementsAre("A.cpp"), ElementsAre("B.cpp"), - ElementsAre("A.cpp"), ElementsAre("C.cpp"))); -} - -TEST_F(OverlayCDBTest, Adjustments) { - OverlayCDB CDB(Base.get(), {}, std::string("")); - auto Cmd = CDB.getCompileCommand(testPath("foo.cc")).getValue(); - // Delete the file name. - Cmd.CommandLine.pop_back(); - - // Check dependency file commands are dropped. - Cmd.CommandLine.push_back("-MF"); - Cmd.CommandLine.push_back("random-dependency"); - - // Check plugin-related commands are dropped. - Cmd.CommandLine.push_back("-Xclang"); - Cmd.CommandLine.push_back("-load"); - Cmd.CommandLine.push_back("-Xclang"); - Cmd.CommandLine.push_back("random-plugin"); - - Cmd.CommandLine.push_back("-DA=5"); - Cmd.CommandLine.push_back(Cmd.Filename); - - CDB.setCompileCommand(testPath("foo.cc"), Cmd); - - EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine, - AllOf(Contains("-fsyntax-only"), Contains("-DA=5"), - Contains(testPath("foo.cc")), Not(Contains("-MF")), - Not(Contains("random-dependency")), - Not(Contains("-Xclang")), Not(Contains("-load")), - Not(Contains("random-plugin")))); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/HeadersTests.cpp b/clang-tools-extra/unittests/clangd/HeadersTests.cpp deleted file mode 100644 index e1591abb11f..00000000000 --- a/clang-tools-extra/unittests/clangd/HeadersTests.cpp +++ /dev/null @@ -1,279 +0,0 @@ -//===-- HeadersTests.cpp - Include headers unit tests -----------*- C++ -*-===// -// -// 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 "Headers.h" - -#include "Compiler.h" -#include "TestFS.h" -#include "TestTU.h" -#include "clang/Frontend/CompilerInstance.h" -#include "clang/Frontend/CompilerInvocation.h" -#include "clang/Frontend/FrontendActions.h" -#include "clang/Lex/PreprocessorOptions.h" -#include "llvm/Support/Path.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -using ::testing::AllOf; -using ::testing::ElementsAre; -using ::testing::UnorderedElementsAre; - -class HeadersTest : public ::testing::Test { -public: - HeadersTest() { - CDB.ExtraClangFlags = {SearchDirArg.c_str()}; - FS.Files[MainFile] = ""; - // Make sure directory sub/ exists. - FS.Files[testPath("sub/EMPTY")] = ""; - } - -private: - std::unique_ptr<CompilerInstance> setupClang() { - auto Cmd = CDB.getCompileCommand(MainFile); - assert(static_cast<bool>(Cmd)); - auto VFS = FS.getFileSystem(); - VFS->setCurrentWorkingDirectory(Cmd->Directory); - - ParseInputs PI; - PI.CompileCommand = *Cmd; - PI.FS = VFS; - auto CI = buildCompilerInvocation(PI); - EXPECT_TRUE(static_cast<bool>(CI)); - // The diagnostic options must be set before creating a CompilerInstance. - CI->getDiagnosticOpts().IgnoreWarnings = true; - auto Clang = prepareCompilerInstance( - std::move(CI), /*Preamble=*/nullptr, - llvm::MemoryBuffer::getMemBuffer(FS.Files[MainFile], MainFile), VFS, - IgnoreDiags); - - EXPECT_FALSE(Clang->getFrontendOpts().Inputs.empty()); - return Clang; - } - -protected: - IncludeStructure collectIncludes() { - auto Clang = setupClang(); - PreprocessOnlyAction Action; - EXPECT_TRUE( - Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])); - IncludeStructure Includes; - Clang->getPreprocessor().addPPCallbacks( - collectIncludeStructureCallback(Clang->getSourceManager(), &Includes)); - EXPECT_TRUE(Action.Execute()); - Action.EndSourceFile(); - return Includes; - } - - // Calculates the include path, or returns "" on error or header should not be - // inserted. - std::string calculate(PathRef Original, PathRef Preferred = "", - const std::vector<Inclusion> &Inclusions = {}) { - auto Clang = setupClang(); - PreprocessOnlyAction Action; - EXPECT_TRUE( - Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])); - - if (Preferred.empty()) - Preferred = Original; - auto ToHeaderFile = [](llvm::StringRef Header) { - return HeaderFile{Header, - /*Verbatim=*/!llvm::sys::path::is_absolute(Header)}; - }; - - IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(), - CDB.getCompileCommand(MainFile)->Directory, - &Clang->getPreprocessor().getHeaderSearchInfo()); - for (const auto &Inc : Inclusions) - Inserter.addExisting(Inc); - auto Inserted = ToHeaderFile(Preferred); - if (!Inserter.shouldInsertInclude(Original, Inserted)) - return ""; - std::string Path = Inserter.calculateIncludePath(Inserted); - Action.EndSourceFile(); - return Path; - } - - llvm::Optional<TextEdit> insert(llvm::StringRef VerbatimHeader) { - auto Clang = setupClang(); - PreprocessOnlyAction Action; - EXPECT_TRUE( - Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])); - - IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(), - CDB.getCompileCommand(MainFile)->Directory, - &Clang->getPreprocessor().getHeaderSearchInfo()); - auto Edit = Inserter.insert(VerbatimHeader); - Action.EndSourceFile(); - return Edit; - } - - MockFSProvider FS; - MockCompilationDatabase CDB; - std::string MainFile = testPath("main.cpp"); - std::string Subdir = testPath("sub"); - std::string SearchDirArg = (llvm::Twine("-I") + Subdir).str(); - IgnoringDiagConsumer IgnoreDiags; -}; - -MATCHER_P(Written, Name, "") { return arg.Written == Name; } -MATCHER_P(Resolved, Name, "") { return arg.Resolved == Name; } -MATCHER_P(IncludeLine, N, "") { return arg.R.start.line == N; } - -MATCHER_P2(Distance, File, D, "") { - if (arg.getKey() != File) - *result_listener << "file =" << arg.getKey().str(); - if (arg.getValue() != D) - *result_listener << "distance =" << arg.getValue(); - return arg.getKey() == File && arg.getValue() == D; -} - -TEST_F(HeadersTest, CollectRewrittenAndResolved) { - FS.Files[MainFile] = R"cpp( -#include "sub/bar.h" // not shortest -)cpp"; - std::string BarHeader = testPath("sub/bar.h"); - FS.Files[BarHeader] = ""; - - EXPECT_THAT(collectIncludes().MainFileIncludes, - UnorderedElementsAre( - AllOf(Written("\"sub/bar.h\""), Resolved(BarHeader)))); - EXPECT_THAT(collectIncludes().includeDepth(MainFile), - UnorderedElementsAre(Distance(MainFile, 0u), - Distance(testPath("sub/bar.h"), 1u))); -} - -TEST_F(HeadersTest, OnlyCollectInclusionsInMain) { - std::string BazHeader = testPath("sub/baz.h"); - FS.Files[BazHeader] = ""; - std::string BarHeader = testPath("sub/bar.h"); - FS.Files[BarHeader] = R"cpp( -#include "baz.h" -)cpp"; - FS.Files[MainFile] = R"cpp( -#include "bar.h" -)cpp"; - EXPECT_THAT( - collectIncludes().MainFileIncludes, - UnorderedElementsAre(AllOf(Written("\"bar.h\""), Resolved(BarHeader)))); - EXPECT_THAT(collectIncludes().includeDepth(MainFile), - UnorderedElementsAre(Distance(MainFile, 0u), - Distance(testPath("sub/bar.h"), 1u), - Distance(testPath("sub/baz.h"), 2u))); - // includeDepth() also works for non-main files. - EXPECT_THAT(collectIncludes().includeDepth(testPath("sub/bar.h")), - UnorderedElementsAre(Distance(testPath("sub/bar.h"), 0u), - Distance(testPath("sub/baz.h"), 1u))); -} - -TEST_F(HeadersTest, PreambleIncludesPresentOnce) { - // We use TestTU here, to ensure we use the preamble replay logic. - // We're testing that the logic doesn't crash, and doesn't result in duplicate - // includes. (We'd test more directly, but it's pretty well encapsulated!) - auto TU = TestTU::withCode(R"cpp( - #include "a.h" - #include "a.h" - void foo(); - #include "a.h" - )cpp"); - TU.HeaderFilename = "a.h"; // suppress "not found". - EXPECT_THAT(TU.build().getIncludeStructure().MainFileIncludes, - ElementsAre(IncludeLine(1), IncludeLine(2), IncludeLine(4))); -} - -TEST_F(HeadersTest, UnResolvedInclusion) { - FS.Files[MainFile] = R"cpp( -#include "foo.h" -)cpp"; - - EXPECT_THAT(collectIncludes().MainFileIncludes, - UnorderedElementsAre(AllOf(Written("\"foo.h\""), Resolved("")))); - EXPECT_THAT(collectIncludes().includeDepth(MainFile), - UnorderedElementsAre(Distance(MainFile, 0u))); -} - -TEST_F(HeadersTest, InsertInclude) { - std::string Path = testPath("sub/bar.h"); - FS.Files[Path] = ""; - EXPECT_EQ(calculate(Path), "\"bar.h\""); -} - -TEST_F(HeadersTest, DoNotInsertIfInSameFile) { - MainFile = testPath("main.h"); - EXPECT_EQ(calculate(MainFile), ""); -} - -TEST_F(HeadersTest, ShortenedInclude) { - std::string BarHeader = testPath("sub/bar.h"); - EXPECT_EQ(calculate(BarHeader), "\"bar.h\""); - - SearchDirArg = (llvm::Twine("-I") + Subdir + "/..").str(); - CDB.ExtraClangFlags = {SearchDirArg.c_str()}; - BarHeader = testPath("sub/bar.h"); - EXPECT_EQ(calculate(BarHeader), "\"sub/bar.h\""); -} - -TEST_F(HeadersTest, NotShortenedInclude) { - std::string BarHeader = - llvm::sys::path::convert_to_slash(testPath("sub-2/bar.h")); - EXPECT_EQ(calculate(BarHeader, ""), "\"" + BarHeader + "\""); -} - -TEST_F(HeadersTest, PreferredHeader) { - std::string BarHeader = testPath("sub/bar.h"); - EXPECT_EQ(calculate(BarHeader, "<bar>"), "<bar>"); - - std::string BazHeader = testPath("sub/baz.h"); - EXPECT_EQ(calculate(BarHeader, BazHeader), "\"baz.h\""); -} - -TEST_F(HeadersTest, DontInsertDuplicatePreferred) { - Inclusion Inc; - Inc.Written = "\"bar.h\""; - Inc.Resolved = ""; - EXPECT_EQ(calculate(testPath("sub/bar.h"), "\"bar.h\"", {Inc}), ""); - EXPECT_EQ(calculate("\"x.h\"", "\"bar.h\"", {Inc}), ""); -} - -TEST_F(HeadersTest, DontInsertDuplicateResolved) { - Inclusion Inc; - Inc.Written = "fake-bar.h"; - Inc.Resolved = testPath("sub/bar.h"); - EXPECT_EQ(calculate(Inc.Resolved, "", {Inc}), ""); - // Do not insert preferred. - EXPECT_EQ(calculate(Inc.Resolved, "\"BAR.h\"", {Inc}), ""); -} - -TEST_F(HeadersTest, PreferInserted) { - auto Edit = insert("<y>"); - EXPECT_TRUE(Edit.hasValue()); - EXPECT_TRUE(StringRef(Edit->newText).contains("<y>")); -} - -TEST(Headers, NoHeaderSearchInfo) { - std::string MainFile = testPath("main.cpp"); - IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(), - /*BuildDir=*/"", /*HeaderSearchInfo=*/nullptr); - - auto HeaderPath = testPath("sub/bar.h"); - auto Inserting = HeaderFile{HeaderPath, /*Verbatim=*/false}; - auto Verbatim = HeaderFile{"<x>", /*Verbatim=*/true}; - - EXPECT_EQ(Inserter.calculateIncludePath(Inserting), "\"" + HeaderPath + "\""); - EXPECT_EQ(Inserter.shouldInsertInclude(HeaderPath, Inserting), false); - - EXPECT_EQ(Inserter.calculateIncludePath(Verbatim), "<x>"); - EXPECT_EQ(Inserter.shouldInsertInclude(HeaderPath, Verbatim), true); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/IndexActionTests.cpp b/clang-tools-extra/unittests/clangd/IndexActionTests.cpp deleted file mode 100644 index a7a9a56e879..00000000000 --- a/clang-tools-extra/unittests/clangd/IndexActionTests.cpp +++ /dev/null @@ -1,253 +0,0 @@ -//===------ IndexActionTests.cpp -------------------------------*- C++ -*-===// -// -// 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 "TestFS.h" -#include "index/IndexAction.h" -#include "clang/Tooling/Tooling.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -using ::testing::AllOf; -using ::testing::ElementsAre; -using ::testing::Not; -using ::testing::Pair; -using ::testing::UnorderedElementsAre; -using ::testing::UnorderedPointwise; - -std::string toUri(llvm::StringRef Path) { return URI::create(Path).toString(); } - -MATCHER(IsTU, "") { return arg.IsTU; } - -MATCHER_P(HasDigest, Digest, "") { return arg.Digest == Digest; } - -MATCHER_P(HasName, Name, "") { return arg.Name == Name; } - -MATCHER(HasSameURI, "") { - llvm::StringRef URI = testing::get<0>(arg); - const std::string &Path = testing::get<1>(arg); - return toUri(Path) == URI; -} - -testing::Matcher<const IncludeGraphNode &> -IncludesAre(const std::vector<std::string> &Includes) { - return ::testing::Field(&IncludeGraphNode::DirectIncludes, - UnorderedPointwise(HasSameURI(), Includes)); -} - -void checkNodesAreInitialized(const IndexFileIn &IndexFile, - const std::vector<std::string> &Paths) { - ASSERT_TRUE(IndexFile.Sources); - EXPECT_THAT(Paths.size(), IndexFile.Sources->size()); - for (llvm::StringRef Path : Paths) { - auto URI = toUri(Path); - const auto &Node = IndexFile.Sources->lookup(URI); - // Uninitialized nodes will have an empty URI. - EXPECT_EQ(Node.URI.data(), IndexFile.Sources->find(URI)->getKeyData()); - } -} - -std::map<std::string, const IncludeGraphNode &> toMap(const IncludeGraph &IG) { - std::map<std::string, const IncludeGraphNode &> Nodes; - for (auto &I : IG) - Nodes.emplace(I.getKey(), I.getValue()); - return Nodes; -} - -class IndexActionTest : public ::testing::Test { -public: - IndexActionTest() : InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem) {} - - IndexFileIn - runIndexingAction(llvm::StringRef MainFilePath, - const std::vector<std::string> &ExtraArgs = {}) { - IndexFileIn IndexFile; - llvm::IntrusiveRefCntPtr<FileManager> Files( - new FileManager(FileSystemOptions(), InMemoryFileSystem)); - - auto Action = createStaticIndexingAction( - SymbolCollector::Options(), - [&](SymbolSlab S) { IndexFile.Symbols = std::move(S); }, - [&](RefSlab R) { IndexFile.Refs = std::move(R); }, - [&](IncludeGraph IG) { IndexFile.Sources = std::move(IG); }); - - std::vector<std::string> Args = {"index_action", "-fsyntax-only", - "-xc++", "-std=c++11", - "-iquote", testRoot()}; - Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); - Args.push_back(MainFilePath); - - tooling::ToolInvocation Invocation( - Args, Action.release(), Files.get(), - std::make_shared<PCHContainerOperations>()); - - Invocation.run(); - - checkNodesAreInitialized(IndexFile, FilePaths); - return IndexFile; - } - - void addFile(llvm::StringRef Path, llvm::StringRef Content) { - InMemoryFileSystem->addFile(Path, 0, - llvm::MemoryBuffer::getMemBuffer(Content)); - FilePaths.push_back(Path); - } - -protected: - std::vector<std::string> FilePaths; - llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem; -}; - -TEST_F(IndexActionTest, CollectIncludeGraph) { - std::string MainFilePath = testPath("main.cpp"); - std::string MainCode = "#include \"level1.h\""; - std::string Level1HeaderPath = testPath("level1.h"); - std::string Level1HeaderCode = "#include \"level2.h\""; - std::string Level2HeaderPath = testPath("level2.h"); - std::string Level2HeaderCode = ""; - - addFile(MainFilePath, MainCode); - addFile(Level1HeaderPath, Level1HeaderCode); - addFile(Level2HeaderPath, Level2HeaderCode); - - IndexFileIn IndexFile = runIndexingAction(MainFilePath); - auto Nodes = toMap(*IndexFile.Sources); - - EXPECT_THAT(Nodes, - UnorderedElementsAre( - Pair(toUri(MainFilePath), - AllOf(IsTU(), IncludesAre({Level1HeaderPath}), - HasDigest(digest(MainCode)))), - Pair(toUri(Level1HeaderPath), - AllOf(Not(IsTU()), IncludesAre({Level2HeaderPath}), - HasDigest(digest(Level1HeaderCode)))), - Pair(toUri(Level2HeaderPath), - AllOf(Not(IsTU()), IncludesAre({}), - HasDigest(digest(Level2HeaderCode)))))); -} - -TEST_F(IndexActionTest, IncludeGraphSelfInclude) { - std::string MainFilePath = testPath("main.cpp"); - std::string MainCode = "#include \"header.h\""; - std::string HeaderPath = testPath("header.h"); - std::string HeaderCode = R"cpp( - #ifndef _GUARD_ - #define _GUARD_ - #include "header.h" - #endif)cpp"; - - addFile(MainFilePath, MainCode); - addFile(HeaderPath, HeaderCode); - - IndexFileIn IndexFile = runIndexingAction(MainFilePath); - auto Nodes = toMap(*IndexFile.Sources); - - EXPECT_THAT( - Nodes, - UnorderedElementsAre( - Pair(toUri(MainFilePath), AllOf(IsTU(), IncludesAre({HeaderPath}), - HasDigest(digest(MainCode)))), - Pair(toUri(HeaderPath), AllOf(Not(IsTU()), IncludesAre({HeaderPath}), - HasDigest(digest(HeaderCode)))))); -} - -TEST_F(IndexActionTest, IncludeGraphSkippedFile) { - std::string MainFilePath = testPath("main.cpp"); - std::string MainCode = R"cpp( - #include "common.h" - #include "header.h" - )cpp"; - - std::string CommonHeaderPath = testPath("common.h"); - std::string CommonHeaderCode = R"cpp( - #ifndef _GUARD_ - #define _GUARD_ - void f(); - #endif)cpp"; - - std::string HeaderPath = testPath("header.h"); - std::string HeaderCode = R"cpp( - #include "common.h" - void g();)cpp"; - - addFile(MainFilePath, MainCode); - addFile(HeaderPath, HeaderCode); - addFile(CommonHeaderPath, CommonHeaderCode); - - IndexFileIn IndexFile = runIndexingAction(MainFilePath); - auto Nodes = toMap(*IndexFile.Sources); - - EXPECT_THAT( - Nodes, UnorderedElementsAre( - Pair(toUri(MainFilePath), - AllOf(IsTU(), IncludesAre({HeaderPath, CommonHeaderPath}), - HasDigest(digest(MainCode)))), - Pair(toUri(HeaderPath), - AllOf(Not(IsTU()), IncludesAre({CommonHeaderPath}), - HasDigest(digest(HeaderCode)))), - Pair(toUri(CommonHeaderPath), - AllOf(Not(IsTU()), IncludesAre({}), - HasDigest(digest(CommonHeaderCode)))))); -} - -TEST_F(IndexActionTest, IncludeGraphDynamicInclude) { - std::string MainFilePath = testPath("main.cpp"); - std::string MainCode = R"cpp( - #ifndef FOO - #define FOO "main.cpp" - #else - #define FOO "header.h" - #endif - - #include FOO)cpp"; - std::string HeaderPath = testPath("header.h"); - std::string HeaderCode = ""; - - addFile(MainFilePath, MainCode); - addFile(HeaderPath, HeaderCode); - - IndexFileIn IndexFile = runIndexingAction(MainFilePath); - auto Nodes = toMap(*IndexFile.Sources); - - EXPECT_THAT( - Nodes, - UnorderedElementsAre( - Pair(toUri(MainFilePath), - AllOf(IsTU(), IncludesAre({MainFilePath, HeaderPath}), - HasDigest(digest(MainCode)))), - Pair(toUri(HeaderPath), AllOf(Not(IsTU()), IncludesAre({}), - HasDigest(digest(HeaderCode)))))); -} - -TEST_F(IndexActionTest, NoWarnings) { - std::string MainFilePath = testPath("main.cpp"); - std::string MainCode = R"cpp( - void foo(int x) { - if (x = 1) // -Wparentheses - return; - if (x = 1) // -Wparentheses - return; - } - void bar() {} - )cpp"; - addFile(MainFilePath, MainCode); - // We set -ferror-limit so the warning-promoted-to-error would be fatal. - // This would cause indexing to stop (if warnings weren't disabled). - IndexFileIn IndexFile = runIndexingAction( - MainFilePath, {"-ferror-limit=1", "-Wparentheses", "-Werror"}); - ASSERT_TRUE(IndexFile.Sources); - ASSERT_NE(0u, IndexFile.Sources->size()); - EXPECT_THAT(*IndexFile.Symbols, ElementsAre(HasName("foo"), HasName("bar"))); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/IndexTests.cpp b/clang-tools-extra/unittests/clangd/IndexTests.cpp deleted file mode 100644 index 2f67654e935..00000000000 --- a/clang-tools-extra/unittests/clangd/IndexTests.cpp +++ /dev/null @@ -1,408 +0,0 @@ -//===-- IndexTests.cpp -------------------------------*- C++ -*-----------===// -// -// 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 "Annotations.h" -#include "TestIndex.h" -#include "TestTU.h" -#include "index/FileIndex.h" -#include "index/Index.h" -#include "index/MemIndex.h" -#include "index/Merge.h" -#include "index/Symbol.h" -#include "clang/Index/IndexSymbol.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -using testing::_; -using testing::AllOf; -using testing::AnyOf; -using testing::ElementsAre; -using testing::IsEmpty; -using testing::Pair; -using testing::Pointee; -using testing::UnorderedElementsAre; - -namespace clang { -namespace clangd { -namespace { - -MATCHER_P(Named, N, "") { return arg.Name == N; } -MATCHER_P(RefRange, Range, "") { - return std::make_tuple(arg.Location.Start.line(), arg.Location.Start.column(), - arg.Location.End.line(), arg.Location.End.column()) == - std::make_tuple(Range.start.line, Range.start.character, - Range.end.line, Range.end.character); -} -MATCHER_P(FileURI, F, "") { return StringRef(arg.Location.FileURI) == F; } - -TEST(SymbolLocation, Position) { - using Position = SymbolLocation::Position; - Position Pos; - - Pos.setLine(1); - EXPECT_EQ(1u, Pos.line()); - Pos.setColumn(2); - EXPECT_EQ(2u, Pos.column()); - EXPECT_FALSE(Pos.hasOverflow()); - - Pos.setLine(Position::MaxLine + 1); // overflow - EXPECT_TRUE(Pos.hasOverflow()); - EXPECT_EQ(Pos.line(), Position::MaxLine); - Pos.setLine(1); // reset the overflowed line. - - Pos.setColumn(Position::MaxColumn + 1); // overflow - EXPECT_TRUE(Pos.hasOverflow()); - EXPECT_EQ(Pos.column(), Position::MaxColumn); -} - -TEST(SymbolSlab, FindAndIterate) { - SymbolSlab::Builder B; - B.insert(symbol("Z")); - B.insert(symbol("Y")); - B.insert(symbol("X")); - EXPECT_EQ(nullptr, B.find(SymbolID("W"))); - for (const char *Sym : {"X", "Y", "Z"}) - EXPECT_THAT(B.find(SymbolID(Sym)), Pointee(Named(Sym))); - - SymbolSlab S = std::move(B).build(); - EXPECT_THAT(S, UnorderedElementsAre(Named("X"), Named("Y"), Named("Z"))); - EXPECT_EQ(S.end(), S.find(SymbolID("W"))); - for (const char *Sym : {"X", "Y", "Z"}) - EXPECT_THAT(*S.find(SymbolID(Sym)), Named(Sym)); -} - -TEST(SwapIndexTest, OldIndexRecycled) { - auto Token = std::make_shared<int>(); - std::weak_ptr<int> WeakToken = Token; - - SwapIndex S(llvm::make_unique<MemIndex>( - SymbolSlab(), RefSlab(), std::move(Token), /*BackingDataSize=*/0)); - EXPECT_FALSE(WeakToken.expired()); // Current MemIndex keeps it alive. - S.reset(llvm::make_unique<MemIndex>()); // Now the MemIndex is destroyed. - EXPECT_TRUE(WeakToken.expired()); // So the token is too. -} - -TEST(MemIndexTest, MemIndexDeduplicate) { - std::vector<Symbol> Symbols = {symbol("1"), symbol("2"), symbol("3"), - symbol("2") /* duplicate */}; - FuzzyFindRequest Req; - Req.Query = "2"; - Req.AnyScope = true; - MemIndex I(Symbols, RefSlab()); - EXPECT_THAT(match(I, Req), ElementsAre("2")); -} - -TEST(MemIndexTest, MemIndexLimitedNumMatches) { - auto I = MemIndex::build(generateNumSymbols(0, 100), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "5"; - Req.AnyScope = true; - Req.Limit = 3; - bool Incomplete; - auto Matches = match(*I, Req, &Incomplete); - EXPECT_TRUE(Req.Limit); - EXPECT_EQ(Matches.size(), *Req.Limit); - EXPECT_TRUE(Incomplete); -} - -TEST(MemIndexTest, FuzzyMatch) { - auto I = MemIndex::build( - generateSymbols({"LaughingOutLoud", "LionPopulation", "LittleOldLady"}), - RefSlab()); - FuzzyFindRequest Req; - Req.Query = "lol"; - Req.AnyScope = true; - Req.Limit = 2; - EXPECT_THAT(match(*I, Req), - UnorderedElementsAre("LaughingOutLoud", "LittleOldLady")); -} - -TEST(MemIndexTest, MatchQualifiedNamesWithoutSpecificScope) { - auto I = - MemIndex::build(generateSymbols({"a::y1", "b::y2", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.AnyScope = true; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "b::y2", "y3")); -} - -TEST(MemIndexTest, MatchQualifiedNamesWithGlobalScope) { - auto I = - MemIndex::build(generateSymbols({"a::y1", "b::y2", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {""}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("y3")); -} - -TEST(MemIndexTest, MatchQualifiedNamesWithOneScope) { - auto I = MemIndex::build( - generateSymbols({"a::y1", "a::y2", "a::x", "b::y2", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {"a::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "a::y2")); -} - -TEST(MemIndexTest, MatchQualifiedNamesWithMultipleScopes) { - auto I = MemIndex::build( - generateSymbols({"a::y1", "a::y2", "a::x", "b::y3", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {"a::", "b::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "a::y2", "b::y3")); -} - -TEST(MemIndexTest, NoMatchNestedScopes) { - auto I = MemIndex::build(generateSymbols({"a::y1", "a::b::y2"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {"a::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1")); -} - -TEST(MemIndexTest, IgnoreCases) { - auto I = MemIndex::build(generateSymbols({"ns::ABC", "ns::abc"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "AB"; - Req.Scopes = {"ns::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("ns::ABC", "ns::abc")); -} - -TEST(MemIndexTest, Lookup) { - auto I = MemIndex::build(generateSymbols({"ns::abc", "ns::xyz"}), RefSlab()); - EXPECT_THAT(lookup(*I, SymbolID("ns::abc")), UnorderedElementsAre("ns::abc")); - EXPECT_THAT(lookup(*I, {SymbolID("ns::abc"), SymbolID("ns::xyz")}), - UnorderedElementsAre("ns::abc", "ns::xyz")); - EXPECT_THAT(lookup(*I, {SymbolID("ns::nonono"), SymbolID("ns::xyz")}), - UnorderedElementsAre("ns::xyz")); - EXPECT_THAT(lookup(*I, SymbolID("ns::nonono")), UnorderedElementsAre()); -} - -TEST(MemIndexTest, TemplateSpecialization) { - SymbolSlab::Builder B; - - Symbol S = symbol("TempSpec"); - S.ID = SymbolID("1"); - B.insert(S); - - S = symbol("TempSpec"); - S.ID = SymbolID("2"); - S.TemplateSpecializationArgs = "<int, bool>"; - S.SymInfo.Properties = static_cast<index::SymbolPropertySet>( - index::SymbolProperty::TemplateSpecialization); - B.insert(S); - - S = symbol("TempSpec"); - S.ID = SymbolID("3"); - S.TemplateSpecializationArgs = "<int, U>"; - S.SymInfo.Properties = static_cast<index::SymbolPropertySet>( - index::SymbolProperty::TemplatePartialSpecialization); - B.insert(S); - - auto I = MemIndex::build(std::move(B).build(), RefSlab()); - FuzzyFindRequest Req; - Req.AnyScope = true; - - Req.Query = "TempSpec"; - EXPECT_THAT(match(*I, Req), - UnorderedElementsAre("TempSpec", "TempSpec<int, bool>", - "TempSpec<int, U>")); - - // FIXME: Add filtering for template argument list. - Req.Query = "TempSpec<int"; - EXPECT_THAT(match(*I, Req), IsEmpty()); -} - -TEST(MergeIndexTest, Lookup) { - auto I = MemIndex::build(generateSymbols({"ns::A", "ns::B"}), RefSlab()), - J = MemIndex::build(generateSymbols({"ns::B", "ns::C"}), RefSlab()); - MergedIndex M(I.get(), J.get()); - EXPECT_THAT(lookup(M, SymbolID("ns::A")), UnorderedElementsAre("ns::A")); - EXPECT_THAT(lookup(M, SymbolID("ns::B")), UnorderedElementsAre("ns::B")); - EXPECT_THAT(lookup(M, SymbolID("ns::C")), UnorderedElementsAre("ns::C")); - EXPECT_THAT(lookup(M, {SymbolID("ns::A"), SymbolID("ns::B")}), - UnorderedElementsAre("ns::A", "ns::B")); - EXPECT_THAT(lookup(M, {SymbolID("ns::A"), SymbolID("ns::C")}), - UnorderedElementsAre("ns::A", "ns::C")); - EXPECT_THAT(lookup(M, SymbolID("ns::D")), UnorderedElementsAre()); - EXPECT_THAT(lookup(M, {}), UnorderedElementsAre()); -} - -TEST(MergeIndexTest, FuzzyFind) { - auto I = MemIndex::build(generateSymbols({"ns::A", "ns::B"}), RefSlab()), - J = MemIndex::build(generateSymbols({"ns::B", "ns::C"}), RefSlab()); - FuzzyFindRequest Req; - Req.Scopes = {"ns::"}; - EXPECT_THAT(match(MergedIndex(I.get(), J.get()), Req), - UnorderedElementsAre("ns::A", "ns::B", "ns::C")); -} - -TEST(MergeTest, Merge) { - Symbol L, R; - L.ID = R.ID = SymbolID("hello"); - L.Name = R.Name = "Foo"; // same in both - L.CanonicalDeclaration.FileURI = "file:///left.h"; // differs - R.CanonicalDeclaration.FileURI = "file:///right.h"; - L.References = 1; - R.References = 2; - L.Signature = "()"; // present in left only - R.CompletionSnippetSuffix = "{$1:0}"; // present in right only - R.Documentation = "--doc--"; - L.Origin = SymbolOrigin::Dynamic; - R.Origin = SymbolOrigin::Static; - R.Type = "expectedType"; - - Symbol M = mergeSymbol(L, R); - EXPECT_EQ(M.Name, "Foo"); - EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:///left.h"); - EXPECT_EQ(M.References, 3u); - EXPECT_EQ(M.Signature, "()"); - EXPECT_EQ(M.CompletionSnippetSuffix, "{$1:0}"); - EXPECT_EQ(M.Documentation, "--doc--"); - EXPECT_EQ(M.Type, "expectedType"); - EXPECT_EQ(M.Origin, - SymbolOrigin::Dynamic | SymbolOrigin::Static | SymbolOrigin::Merge); -} - -TEST(MergeTest, PreferSymbolWithDefn) { - Symbol L, R; - - L.ID = R.ID = SymbolID("hello"); - L.CanonicalDeclaration.FileURI = "file:/left.h"; - R.CanonicalDeclaration.FileURI = "file:/right.h"; - L.Name = "left"; - R.Name = "right"; - - Symbol M = mergeSymbol(L, R); - EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:/left.h"); - EXPECT_EQ(StringRef(M.Definition.FileURI), ""); - EXPECT_EQ(M.Name, "left"); - - R.Definition.FileURI = "file:/right.cpp"; // Now right will be favored. - M = mergeSymbol(L, R); - EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:/right.h"); - EXPECT_EQ(StringRef(M.Definition.FileURI), "file:/right.cpp"); - EXPECT_EQ(M.Name, "right"); -} - -TEST(MergeTest, PreferSymbolLocationInCodegenFile) { - Symbol L, R; - - L.ID = R.ID = SymbolID("hello"); - L.CanonicalDeclaration.FileURI = "file:/x.proto.h"; - R.CanonicalDeclaration.FileURI = "file:/x.proto"; - - Symbol M = mergeSymbol(L, R); - EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:/x.proto"); - - // Prefer L if both have codegen suffix. - L.CanonicalDeclaration.FileURI = "file:/y.proto"; - M = mergeSymbol(L, R); - EXPECT_EQ(StringRef(M.CanonicalDeclaration.FileURI), "file:/y.proto"); -} - -TEST(MergeIndexTest, Refs) { - FileIndex Dyn; - FileIndex StaticIndex; - MergedIndex Merge(&Dyn, &StaticIndex); - - const char *HeaderCode = "class Foo;"; - auto HeaderSymbols = TestTU::withHeaderCode("class Foo;").headerSymbols(); - auto Foo = findSymbol(HeaderSymbols, "Foo"); - - // Build dynamic index for test.cc. - Annotations Test1Code(R"(class $Foo[[Foo]];)"); - TestTU Test; - Test.HeaderCode = HeaderCode; - Test.Code = Test1Code.code(); - Test.Filename = "test.cc"; - auto AST = Test.build(); - Dyn.updateMain(Test.Filename, AST); - - // Build static index for test.cc. - Test.HeaderCode = HeaderCode; - Test.Code = "// static\nclass Foo {};"; - Test.Filename = "test.cc"; - auto StaticAST = Test.build(); - // Add stale refs for test.cc. - StaticIndex.updateMain(Test.Filename, StaticAST); - - // Add refs for test2.cc - Annotations Test2Code(R"(class $Foo[[Foo]] {};)"); - TestTU Test2; - Test2.HeaderCode = HeaderCode; - Test2.Code = Test2Code.code(); - Test2.Filename = "test2.cc"; - StaticAST = Test2.build(); - StaticIndex.updateMain(Test2.Filename, StaticAST); - - RefsRequest Request; - Request.IDs = {Foo.ID}; - RefSlab::Builder Results; - Merge.refs(Request, [&](const Ref &O) { Results.insert(Foo.ID, O); }); - EXPECT_THAT( - std::move(Results).build(), - ElementsAre(Pair( - _, UnorderedElementsAre(AllOf(RefRange(Test1Code.range("Foo")), - FileURI("unittest:///test.cc")), - AllOf(RefRange(Test2Code.range("Foo")), - FileURI("unittest:///test2.cc")))))); - - Request.Limit = 1; - RefSlab::Builder Results2; - Merge.refs(Request, [&](const Ref &O) { Results2.insert(Foo.ID, O); }); - EXPECT_THAT(std::move(Results2).build(), - ElementsAre(Pair( - _, ElementsAre(AnyOf(FileURI("unittest:///test.cc"), - FileURI("unittest:///test2.cc")))))); -} - -MATCHER_P2(IncludeHeaderWithRef, IncludeHeader, References, "") { - return (arg.IncludeHeader == IncludeHeader) && (arg.References == References); -} - -TEST(MergeTest, MergeIncludesOnDifferentDefinitions) { - Symbol L, R; - L.Name = "left"; - R.Name = "right"; - L.ID = R.ID = SymbolID("hello"); - L.IncludeHeaders.emplace_back("common", 1); - R.IncludeHeaders.emplace_back("common", 1); - R.IncludeHeaders.emplace_back("new", 1); - - // Both have no definition. - Symbol M = mergeSymbol(L, R); - EXPECT_THAT(M.IncludeHeaders, - UnorderedElementsAre(IncludeHeaderWithRef("common", 2u), - IncludeHeaderWithRef("new", 1u))); - - // Only merge references of the same includes but do not merge new #includes. - L.Definition.FileURI = "file:/left.h"; - M = mergeSymbol(L, R); - EXPECT_THAT(M.IncludeHeaders, - UnorderedElementsAre(IncludeHeaderWithRef("common", 2u))); - - // Definitions are the same. - R.Definition.FileURI = "file:/right.h"; - M = mergeSymbol(L, R); - EXPECT_THAT(M.IncludeHeaders, - UnorderedElementsAre(IncludeHeaderWithRef("common", 2u), - IncludeHeaderWithRef("new", 1u))); - - // Definitions are different. - R.Definition.FileURI = "file:/right.h"; - M = mergeSymbol(L, R); - EXPECT_THAT(M.IncludeHeaders, - UnorderedElementsAre(IncludeHeaderWithRef("common", 2u), - IncludeHeaderWithRef("new", 1u))); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/JSONTransportTests.cpp b/clang-tools-extra/unittests/clangd/JSONTransportTests.cpp deleted file mode 100644 index 3f71a10c62f..00000000000 --- a/clang-tools-extra/unittests/clangd/JSONTransportTests.cpp +++ /dev/null @@ -1,205 +0,0 @@ -//===-- JSONTransportTests.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 "Protocol.h" -#include "Transport.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include <cstdio> - -namespace clang { -namespace clangd { -namespace { - -// No fmemopen on windows or on versions of MacOS X earlier than 10.13, so we -// can't easily run this test. -#if !(defined(_WIN32) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ - __MAC_OS_X_VERSION_MIN_REQUIRED < 101300)) - -// Fixture takes care of managing the input/output buffers for the transport. -class JSONTransportTest : public ::testing::Test { - std::string InBuf, OutBuf, MirrorBuf; - llvm::raw_string_ostream Out, Mirror; - std::unique_ptr<FILE, int (*)(FILE *)> In; - -protected: - JSONTransportTest() : Out(OutBuf), Mirror(MirrorBuf), In(nullptr, nullptr) {} - - template <typename... Args> - std::unique_ptr<Transport> transport(std::string InData, bool Pretty, - JSONStreamStyle Style) { - InBuf = std::move(InData); - In = {fmemopen(&InBuf[0], InBuf.size(), "r"), &fclose}; - return newJSONTransport(In.get(), Out, &Mirror, Pretty, Style); - } - - std::string input() const { return InBuf; } - std::string output() { return Out.str(); } - std::string input_mirror() { return Mirror.str(); } -}; - -// Echo is a simple server running on a transport: -// - logs each message it gets. -// - when it gets a call, replies to it -// - when it gets a notification for method "call", makes a call on Target -// Hangs up when it gets an exit notification. -class Echo : public Transport::MessageHandler { - Transport &Target; - std::string LogBuf; - llvm::raw_string_ostream Log; - -public: - Echo(Transport &Target) : Target(Target), Log(LogBuf) {} - - std::string log() { return Log.str(); } - - bool onNotify(llvm::StringRef Method, llvm::json::Value Params) override { - Log << "Notification " << Method << ": " << Params << "\n"; - if (Method == "call") - Target.call("echo call", std::move(Params), 42); - return Method != "exit"; - } - - bool onCall(llvm::StringRef Method, llvm::json::Value Params, - llvm::json::Value ID) override { - Log << "Call " << Method << "(" << ID << "): " << Params << "\n"; - if (Method == "err") - Target.reply( - ID, llvm::make_error<LSPError>("trouble at mill", ErrorCode(88))); - else - Target.reply(ID, std::move(Params)); - return true; - } - - bool onReply(llvm::json::Value ID, - llvm::Expected<llvm::json::Value> Params) override { - if (Params) - Log << "Reply(" << ID << "): " << *Params << "\n"; - else - Log << "Reply(" << ID - << "): error = " << llvm::toString(Params.takeError()) << "\n"; - return true; - } -}; - -std::string trim(llvm::StringRef S) { return S.trim().str(); } - -// Runs an Echo session using the standard JSON-RPC format we use in production. -TEST_F(JSONTransportTest, StandardDense) { - auto T = transport( - "Content-Length: 52\r\n\r\n" - R"({"jsonrpc": "2.0", "method": "call", "params": 1234})" - "Content-Length: 46\r\n\r\n" - R"({"jsonrpc": "2.0", "id": 1234, "result": 5678})" - "Content-Length: 67\r\n\r\n" - R"({"jsonrpc": "2.0", "method": "foo", "id": "abcd", "params": "efgh"})" - "Content-Length: 73\r\n\r\n" - R"({"jsonrpc": "2.0", "id": "xyz", "error": {"code": 99, "message": "bad!"}})" - "Content-Length: 68\r\n\r\n" - R"({"jsonrpc": "2.0", "method": "err", "id": "wxyz", "params": "boom!"})" - "Content-Length: 36\r\n\r\n" - R"({"jsonrpc": "2.0", "method": "exit"})", - /*Pretty=*/false, JSONStreamStyle::Standard); - Echo E(*T); - auto Err = T->loop(E); - EXPECT_FALSE(bool(Err)) << toString(std::move(Err)); - - const char *WantLog = R"( -Notification call: 1234 -Reply(1234): 5678 -Call foo("abcd"): "efgh" -Reply("xyz"): error = 99: bad! -Call err("wxyz"): "boom!" -Notification exit: null - )"; - EXPECT_EQ(trim(E.log()), trim(WantLog)); - const char *WantOutput = - "Content-Length: 60\r\n\r\n" - R"({"id":42,"jsonrpc":"2.0","method":"echo call","params":1234})" - "Content-Length: 45\r\n\r\n" - R"({"id":"abcd","jsonrpc":"2.0","result":"efgh"})" - "Content-Length: 77\r\n\r\n" - R"({"error":{"code":88,"message":"trouble at mill"},"id":"wxyz","jsonrpc":"2.0"})"; - EXPECT_EQ(output(), WantOutput); - EXPECT_EQ(trim(input_mirror()), trim(input())); -} - -// Runs an Echo session using the "delimited" input and pretty-printed output -// that we use in lit tests. -TEST_F(JSONTransportTest, DelimitedPretty) { - auto T = transport(R"jsonrpc( -{"jsonrpc": "2.0", "method": "call", "params": 1234} ---- -{"jsonrpc": "2.0", "id": 1234, "result": 5678} ---- -{"jsonrpc": "2.0", "method": "foo", "id": "abcd", "params": "efgh"} ---- -{"jsonrpc": "2.0", "id": "xyz", "error": {"code": 99, "message": "bad!"}} ---- -{"jsonrpc": "2.0", "method": "err", "id": "wxyz", "params": "boom!"} ---- -{"jsonrpc": "2.0", "method": "exit"} - )jsonrpc", - /*Pretty=*/true, JSONStreamStyle::Delimited); - Echo E(*T); - auto Err = T->loop(E); - EXPECT_FALSE(bool(Err)) << toString(std::move(Err)); - - const char *WantLog = R"( -Notification call: 1234 -Reply(1234): 5678 -Call foo("abcd"): "efgh" -Reply("xyz"): error = 99: bad! -Call err("wxyz"): "boom!" -Notification exit: null - )"; - EXPECT_EQ(trim(E.log()), trim(WantLog)); - const char *WantOutput = "Content-Length: 77\r\n\r\n" - R"({ - "id": 42, - "jsonrpc": "2.0", - "method": "echo call", - "params": 1234 -})" - "Content-Length: 58\r\n\r\n" - R"({ - "id": "abcd", - "jsonrpc": "2.0", - "result": "efgh" -})" - "Content-Length: 105\r\n\r\n" - R"({ - "error": { - "code": 88, - "message": "trouble at mill" - }, - "id": "wxyz", - "jsonrpc": "2.0" -})"; - EXPECT_EQ(output(), WantOutput); - EXPECT_EQ(trim(input_mirror()), trim(input())); -} - -// IO errors such as EOF ane reported. -// The only successful return from loop() is if a handler returned false. -TEST_F(JSONTransportTest, EndOfFile) { - auto T = transport("Content-Length: 52\r\n\r\n" - R"({"jsonrpc": "2.0", "method": "call", "params": 1234})", - /*Pretty=*/false, JSONStreamStyle::Standard); - Echo E(*T); - auto Err = T->loop(E); - EXPECT_EQ(trim(E.log()), "Notification call: 1234"); - EXPECT_TRUE(bool(Err)); // Ran into EOF with no handler signalling done. - consumeError(std::move(Err)); - EXPECT_EQ(trim(input_mirror()), trim(input())); -} - -#endif - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/Matchers.h b/clang-tools-extra/unittests/clangd/Matchers.h deleted file mode 100644 index 0946398d814..00000000000 --- a/clang-tools-extra/unittests/clangd/Matchers.h +++ /dev/null @@ -1,199 +0,0 @@ -//===-- Matchers.h ----------------------------------------------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// -// -// GMock matchers that aren't specific to particular tests. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_MATCHERS_H -#define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_MATCHERS_H -#include "Protocol.h" -#include "gmock/gmock.h" - -namespace clang { -namespace clangd { -using ::testing::Matcher; - -// EXPECT_IFF expects matcher if condition is true, and Not(matcher) if false. -// This is hard to write as a function, because matchers may be polymorphic. -#define EXPECT_IFF(condition, value, matcher) \ - do { \ - if (condition) \ - EXPECT_THAT(value, matcher); \ - else \ - EXPECT_THAT(value, ::testing::Not(matcher)); \ - } while (0) - -// HasSubsequence(m1, m2, ...) matches a vector containing elements that match -// m1, m2 ... in that order. -// -// SubsequenceMatcher implements this once the type of vector is known. -template <typename T> -class SubsequenceMatcher - : public ::testing::MatcherInterface<const std::vector<T> &> { - std::vector<Matcher<T>> Matchers; - -public: - SubsequenceMatcher(std::vector<Matcher<T>> M) : Matchers(M) {} - - void DescribeTo(std::ostream *OS) const override { - *OS << "Contains the subsequence ["; - const char *Sep = ""; - for (const auto &M : Matchers) { - *OS << Sep; - M.DescribeTo(OS); - Sep = ", "; - } - *OS << "]"; - } - - bool MatchAndExplain(const std::vector<T> &V, - ::testing::MatchResultListener *L) const override { - std::vector<int> Matches(Matchers.size()); - size_t I = 0; - for (size_t J = 0; I < Matchers.size() && J < V.size(); ++J) - if (Matchers[I].Matches(V[J])) - Matches[I++] = J; - if (I == Matchers.size()) // We exhausted all matchers. - return true; - if (L->IsInterested()) { - *L << "\n Matched:"; - for (size_t K = 0; K < I; ++K) { - *L << "\n\t"; - Matchers[K].DescribeTo(L->stream()); - *L << " ==> " << ::testing::PrintToString(V[Matches[K]]); - } - *L << "\n\t"; - Matchers[I].DescribeTo(L->stream()); - *L << " ==> no subsequent match"; - } - return false; - } -}; - -// PolySubsequenceMatcher implements a "polymorphic" SubsequenceMatcher. -// It captures the types of the element matchers, and can be converted to -// Matcher<vector<T>> if each matcher can be converted to Matcher<T>. -// This allows HasSubsequence() to accept polymorphic matchers like Not(). -template <typename... M> class PolySubsequenceMatcher { - std::tuple<M...> Matchers; - -public: - PolySubsequenceMatcher(M &&... Args) - : Matchers(std::make_tuple(std::forward<M>(Args)...)) {} - - template <typename T> operator Matcher<const std::vector<T> &>() const { - return ::testing::MakeMatcher(new SubsequenceMatcher<T>( - TypedMatchers<T>(llvm::index_sequence_for<M...>{}))); - } - -private: - template <typename T, size_t... I> - std::vector<Matcher<T>> TypedMatchers(llvm::index_sequence<I...>) const { - return {std::get<I>(Matchers)...}; - } -}; - -// HasSubsequence(m1, m2, ...) matches a vector containing elements that match -// m1, m2 ... in that order. -// The real implementation is in SubsequenceMatcher. -template <typename... Args> -PolySubsequenceMatcher<Args...> HasSubsequence(Args &&... M) { - return PolySubsequenceMatcher<Args...>(std::forward<Args>(M)...); -} - -// EXPECT_ERROR seems like a pretty generic name, make sure it's not defined -// already. -#ifdef EXPECT_ERROR -#error "Refusing to redefine EXPECT_ERROR" -#endif - -// Consumes llvm::Expected<T>, checks it contains an error and marks it as -// handled. -#define EXPECT_ERROR(expectedValue) \ - do { \ - auto &&ComputedValue = (expectedValue); \ - if (ComputedValue) { \ - ADD_FAILURE() << "expected an error from " << #expectedValue \ - << " but got " \ - << ::testing::PrintToString(*ComputedValue); \ - break; \ - } \ - llvm::consumeError(ComputedValue.takeError()); \ - } while (false) - -// Implements the HasValue(m) matcher for matching an Optional whose -// value matches matcher m. -template <typename InnerMatcher> class OptionalMatcher { -public: - explicit OptionalMatcher(const InnerMatcher &matcher) : matcher_(matcher) {} - - // This type conversion operator template allows Optional(m) to be - // used as a matcher for any Optional type whose value type is - // compatible with the inner matcher. - // - // The reason we do this instead of relying on - // MakePolymorphicMatcher() is that the latter is not flexible - // enough for implementing the DescribeTo() method of Optional(). - template <typename Optional> operator Matcher<Optional>() const { - return MakeMatcher(new Impl<Optional>(matcher_)); - } - -private: - // The monomorphic implementation that works for a particular optional type. - template <typename Optional> - class Impl : public ::testing::MatcherInterface<Optional> { - public: - using Value = typename std::remove_const< - typename std::remove_reference<Optional>::type>::type::value_type; - - explicit Impl(const InnerMatcher &matcher) - : matcher_(::testing::MatcherCast<const Value &>(matcher)) {} - - virtual void DescribeTo(::std::ostream *os) const { - *os << "has a value that "; - matcher_.DescribeTo(os); - } - - virtual void DescribeNegationTo(::std::ostream *os) const { - *os << "does not have a value that "; - matcher_.DescribeTo(os); - } - - virtual bool - MatchAndExplain(Optional optional, - ::testing::MatchResultListener *listener) const { - if (!optional.hasValue()) - return false; - - *listener << "which has a value "; - return MatchPrintAndExplain(*optional, matcher_, listener); - } - - private: - const Matcher<const Value &> matcher_; - - GTEST_DISALLOW_ASSIGN_(Impl); - }; - - const InnerMatcher matcher_; - - GTEST_DISALLOW_ASSIGN_(OptionalMatcher); -}; - -// Creates a matcher that matches an Optional that has a value -// that matches inner_matcher. -template <typename InnerMatcher> -inline OptionalMatcher<InnerMatcher> -HasValue(const InnerMatcher &inner_matcher) { - return OptionalMatcher<InnerMatcher>(inner_matcher); -} - -} // namespace clangd -} // namespace clang -#endif diff --git a/clang-tools-extra/unittests/clangd/PrintASTTests.cpp b/clang-tools-extra/unittests/clangd/PrintASTTests.cpp deleted file mode 100644 index acd77f52f17..00000000000 --- a/clang-tools-extra/unittests/clangd/PrintASTTests.cpp +++ /dev/null @@ -1,102 +0,0 @@ -//===--- PrintASTTests.cpp ----------------------------------------- C++-*-===// -// -// 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 "AST.h" -#include "Annotations.h" -#include "Protocol.h" -#include "SourceCode.h" -#include "TestTU.h" -#include "clang/AST/RecursiveASTVisitor.h" -#include "gmock/gmock.h" -#include "gtest/gtest-param-test.h" -#include "gtest/gtest.h" -#include "gtest/internal/gtest-param-util-generated.h" - -namespace clang { -namespace clangd { -namespace { - -using testing::ElementsAreArray; - -struct Case { - const char *AnnotatedCode; - std::vector<const char *> Expected; -}; -class ASTUtils : public testing::Test, - public ::testing::WithParamInterface<Case> {}; - -TEST_P(ASTUtils, PrintTemplateArgs) { - auto Pair = GetParam(); - Annotations Test(Pair.AnnotatedCode); - auto AST = TestTU::withCode(Test.code()).build(); - struct Visitor : RecursiveASTVisitor<Visitor> { - Visitor(std::vector<Position> Points) : Points(std::move(Points)) {} - bool VisitNamedDecl(const NamedDecl *ND) { - if (TemplateArgsAtPoints.size() == Points.size()) - return true; - auto Pos = sourceLocToPosition(ND->getASTContext().getSourceManager(), - ND->getLocation()); - if (Pos != Points[TemplateArgsAtPoints.size()]) - return true; - TemplateArgsAtPoints.push_back(printTemplateSpecializationArgs(*ND)); - return true; - } - std::vector<std::string> TemplateArgsAtPoints; - const std::vector<Position> Points; - }; - Visitor V(Test.points()); - V.TraverseDecl(AST.getASTContext().getTranslationUnitDecl()); - EXPECT_THAT(V.TemplateArgsAtPoints, ElementsAreArray(Pair.Expected)); -} - -INSTANTIATE_TEST_CASE_P(ASTUtilsTests, ASTUtils, - testing::ValuesIn(std::vector<Case>({ - { - R"cpp( - template <class X> class Bar {}; - template <> class ^Bar<double> {};)cpp", - {"<double>"}}, - { - R"cpp( - template <class X> class Bar {}; - template <class T, class U, - template<typename> class Z, int Q> - struct Foo {}; - template struct ^Foo<int, bool, Bar, 8>; - template <typename T> - struct ^Foo<T *, T, Bar, 3> {};)cpp", - {"<int, bool, Bar, 8>", "<T *, T, Bar, 3>"}}, - { - R"cpp( - template <int ...> void Foz() {}; - template <> void ^Foz<3, 5, 8>() {};)cpp", - {"<3, 5, 8>"}}, - { - R"cpp( - template <class X> class Bar {}; - template <template <class> class ...> - class Aux {}; - template <> class ^Aux<Bar, Bar> {}; - template <template <class> T> - class ^Aux<T, T> {};)cpp", - {"<Bar, Bar>", "<T, T>"}}, - { - R"cpp( - template <typename T> T var = 1234; - template <> int ^var<int> = 1;)cpp", - {"<int>"}}, - { - R"cpp( - template <typename T> struct Foo; - struct Bar { friend class Foo<int>; }; - template <> struct ^Foo<int> {};)cpp", - {"<int>"}}, - }))); -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/QualityTests.cpp b/clang-tools-extra/unittests/clangd/QualityTests.cpp deleted file mode 100644 index b797a48f7c7..00000000000 --- a/clang-tools-extra/unittests/clangd/QualityTests.cpp +++ /dev/null @@ -1,483 +0,0 @@ -//===-- QualityTests.cpp ----------------------------------------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// -// -// Evaluating scoring functions isn't a great fit for assert-based tests. -// For interesting cases, both exact scores and "X beats Y" are too brittle to -// make good hard assertions. -// -// Here we test the signal extraction and sanity-check that signals point in -// the right direction. This should be supplemented by quality metrics which -// we can compute from a corpus of queries and preferred rankings. -// -//===----------------------------------------------------------------------===// - -#include "FileDistance.h" -#include "Quality.h" -#include "TestFS.h" -#include "TestTU.h" -#include "clang/AST/Decl.h" -#include "clang/AST/DeclCXX.h" -#include "clang/AST/Type.h" -#include "clang/Sema/CodeCompleteConsumer.h" -#include "llvm/Support/Casting.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include <vector> - -namespace clang { -namespace clangd { - -// Force the unittest URI scheme to be linked, -static int LLVM_ATTRIBUTE_UNUSED UnittestSchemeAnchorDest = - UnittestSchemeAnchorSource; - -namespace { - -TEST(QualityTests, SymbolQualitySignalExtraction) { - auto Header = TestTU::withHeaderCode(R"cpp( - int _X; - - [[deprecated]] - int _f() { return _X; } - - #define DECL_NAME(x, y) x##_##y##_Decl - #define DECL(x, y) class DECL_NAME(x, y) {}; - DECL(X, Y); // X_Y_Decl - )cpp"); - - auto Symbols = Header.headerSymbols(); - auto AST = Header.build(); - - SymbolQualitySignals Quality; - Quality.merge(findSymbol(Symbols, "_X")); - EXPECT_FALSE(Quality.Deprecated); - EXPECT_FALSE(Quality.ImplementationDetail); - EXPECT_TRUE(Quality.ReservedName); - EXPECT_EQ(Quality.References, SymbolQualitySignals().References); - EXPECT_EQ(Quality.Category, SymbolQualitySignals::Variable); - - Quality.merge(findSymbol(Symbols, "X_Y_Decl")); - EXPECT_TRUE(Quality.ImplementationDetail); - - Symbol F = findSymbol(Symbols, "_f"); - F.References = 24; // TestTU doesn't count references, so fake it. - Quality = {}; - Quality.merge(F); - EXPECT_TRUE(Quality.Deprecated); - EXPECT_FALSE(Quality.ReservedName); - EXPECT_EQ(Quality.References, 24u); - EXPECT_EQ(Quality.Category, SymbolQualitySignals::Function); - - Quality = {}; - Quality.merge(CodeCompletionResult(&findDecl(AST, "_f"), /*Priority=*/42)); - EXPECT_TRUE(Quality.Deprecated); - EXPECT_FALSE(Quality.ReservedName); - EXPECT_EQ(Quality.References, SymbolQualitySignals().References); - EXPECT_EQ(Quality.Category, SymbolQualitySignals::Function); - - Quality = {}; - Quality.merge(CodeCompletionResult("if")); - EXPECT_EQ(Quality.Category, SymbolQualitySignals::Keyword); -} - -TEST(QualityTests, SymbolRelevanceSignalExtraction) { - TestTU Test; - Test.HeaderCode = R"cpp( - int header(); - int header_main(); - - namespace hdr { class Bar {}; } // namespace hdr - - #define DEFINE_FLAG(X) \ - namespace flags { \ - int FLAGS_##X; \ - } \ - - DEFINE_FLAG(FOO) - )cpp"; - Test.Code = R"cpp( - using hdr::Bar; - - using flags::FLAGS_FOO; - - int ::header_main() {} - int main(); - - [[deprecated]] - int deprecated() { return 0; } - - namespace { struct X { void y() { int z; } }; } - struct S{} - )cpp"; - auto AST = Test.build(); - - SymbolRelevanceSignals Relevance; - Relevance.merge(CodeCompletionResult(&findDecl(AST, "deprecated"), - /*Priority=*/42, nullptr, false, - /*Accessible=*/false)); - EXPECT_EQ(Relevance.NameMatch, SymbolRelevanceSignals().NameMatch); - EXPECT_TRUE(Relevance.Forbidden); - EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::GlobalScope); - - Relevance = {}; - Relevance.merge(CodeCompletionResult(&findDecl(AST, "main"), 42)); - EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 1.0f) - << "Decl in current file"; - Relevance = {}; - Relevance.merge(CodeCompletionResult(&findDecl(AST, "header"), 42)); - EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 0.6f) << "Decl from header"; - Relevance = {}; - Relevance.merge(CodeCompletionResult(&findDecl(AST, "header_main"), 42)); - EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 1.0f) - << "Current file and header"; - - auto constructShadowDeclCompletionResult = [&](const std::string DeclName) { - auto *Shadow = - *dyn_cast<UsingDecl>(&findDecl(AST, [&](const NamedDecl &ND) { - if (const UsingDecl *Using = dyn_cast<UsingDecl>(&ND)) - if (Using->shadow_size() && - Using->getQualifiedNameAsString() == DeclName) - return true; - return false; - }))->shadow_begin(); - CodeCompletionResult Result(Shadow->getTargetDecl(), 42); - Result.ShadowDecl = Shadow; - return Result; - }; - - Relevance = {}; - Relevance.merge(constructShadowDeclCompletionResult("Bar")); - EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 1.0f) - << "Using declaration in main file"; - Relevance.merge(constructShadowDeclCompletionResult("FLAGS_FOO")); - EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 1.0f) - << "Using declaration in main file"; - - Relevance = {}; - Relevance.merge(CodeCompletionResult(&findUnqualifiedDecl(AST, "X"), 42)); - EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::FileScope); - Relevance = {}; - Relevance.merge(CodeCompletionResult(&findUnqualifiedDecl(AST, "y"), 42)); - EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::ClassScope); - Relevance = {}; - Relevance.merge(CodeCompletionResult(&findUnqualifiedDecl(AST, "z"), 42)); - EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::FunctionScope); - // The injected class name is treated as the outer class name. - Relevance = {}; - Relevance.merge(CodeCompletionResult(&findDecl(AST, "S::S"), 42)); - EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::GlobalScope); - - Relevance = {}; - EXPECT_FALSE(Relevance.InBaseClass); - auto BaseMember = CodeCompletionResult(&findUnqualifiedDecl(AST, "y"), 42); - BaseMember.InBaseClass = true; - Relevance.merge(BaseMember); - EXPECT_TRUE(Relevance.InBaseClass); - - auto Index = Test.index(); - FuzzyFindRequest Req; - Req.Query = "X"; - Req.AnyScope = true; - bool Matched = false; - Index->fuzzyFind(Req, [&](const Symbol &S) { - Matched = true; - Relevance = {}; - Relevance.merge(S); - EXPECT_EQ(Relevance.Scope, SymbolRelevanceSignals::FileScope); - }); - EXPECT_TRUE(Matched); -} - -// Do the signals move the scores in the direction we expect? -TEST(QualityTests, SymbolQualitySignalsSanity) { - SymbolQualitySignals Default; - EXPECT_EQ(Default.evaluate(), 1); - - SymbolQualitySignals Deprecated; - Deprecated.Deprecated = true; - EXPECT_LT(Deprecated.evaluate(), Default.evaluate()); - - SymbolQualitySignals ReservedName; - ReservedName.ReservedName = true; - EXPECT_LT(ReservedName.evaluate(), Default.evaluate()); - - SymbolQualitySignals ImplementationDetail; - ImplementationDetail.ImplementationDetail = true; - EXPECT_LT(ImplementationDetail.evaluate(), Default.evaluate()); - - SymbolQualitySignals WithReferences, ManyReferences; - WithReferences.References = 20; - ManyReferences.References = 1000; - EXPECT_GT(WithReferences.evaluate(), Default.evaluate()); - EXPECT_GT(ManyReferences.evaluate(), WithReferences.evaluate()); - - SymbolQualitySignals Keyword, Variable, Macro, Constructor, Function, - Destructor, Operator; - Keyword.Category = SymbolQualitySignals::Keyword; - Variable.Category = SymbolQualitySignals::Variable; - Macro.Category = SymbolQualitySignals::Macro; - Constructor.Category = SymbolQualitySignals::Constructor; - Destructor.Category = SymbolQualitySignals::Destructor; - Destructor.Category = SymbolQualitySignals::Destructor; - Operator.Category = SymbolQualitySignals::Operator; - Function.Category = SymbolQualitySignals::Function; - EXPECT_GT(Variable.evaluate(), Default.evaluate()); - EXPECT_GT(Keyword.evaluate(), Variable.evaluate()); - EXPECT_LT(Macro.evaluate(), Default.evaluate()); - EXPECT_LT(Operator.evaluate(), Default.evaluate()); - EXPECT_LT(Constructor.evaluate(), Function.evaluate()); - EXPECT_LT(Destructor.evaluate(), Constructor.evaluate()); -} - -TEST(QualityTests, SymbolRelevanceSignalsSanity) { - SymbolRelevanceSignals Default; - EXPECT_EQ(Default.evaluate(), 1); - - SymbolRelevanceSignals Forbidden; - Forbidden.Forbidden = true; - EXPECT_LT(Forbidden.evaluate(), Default.evaluate()); - - SymbolRelevanceSignals PoorNameMatch; - PoorNameMatch.NameMatch = 0.2f; - EXPECT_LT(PoorNameMatch.evaluate(), Default.evaluate()); - - SymbolRelevanceSignals WithSemaFileProximity; - WithSemaFileProximity.SemaFileProximityScore = 0.2f; - EXPECT_GT(WithSemaFileProximity.evaluate(), Default.evaluate()); - - ScopeDistance ScopeProximity({"x::y::"}); - - SymbolRelevanceSignals WithSemaScopeProximity; - WithSemaScopeProximity.ScopeProximityMatch = &ScopeProximity; - WithSemaScopeProximity.SemaSaysInScope = true; - EXPECT_GT(WithSemaScopeProximity.evaluate(), Default.evaluate()); - - SymbolRelevanceSignals WithIndexScopeProximity; - WithIndexScopeProximity.ScopeProximityMatch = &ScopeProximity; - WithIndexScopeProximity.SymbolScope = "x::"; - EXPECT_GT(WithSemaScopeProximity.evaluate(), Default.evaluate()); - - SymbolRelevanceSignals IndexProximate; - IndexProximate.SymbolURI = "unittest:/foo/bar.h"; - llvm::StringMap<SourceParams> ProxSources; - ProxSources.try_emplace(testPath("foo/baz.h")); - URIDistance Distance(ProxSources); - IndexProximate.FileProximityMatch = &Distance; - EXPECT_GT(IndexProximate.evaluate(), Default.evaluate()); - SymbolRelevanceSignals IndexDistant = IndexProximate; - IndexDistant.SymbolURI = "unittest:/elsewhere/path.h"; - EXPECT_GT(IndexProximate.evaluate(), IndexDistant.evaluate()) - << IndexProximate << IndexDistant; - EXPECT_GT(IndexDistant.evaluate(), Default.evaluate()); - - SymbolRelevanceSignals Scoped; - Scoped.Scope = SymbolRelevanceSignals::FileScope; - EXPECT_LT(Scoped.evaluate(), Default.evaluate()); - Scoped.Query = SymbolRelevanceSignals::CodeComplete; - EXPECT_GT(Scoped.evaluate(), Default.evaluate()); - - SymbolRelevanceSignals Instance; - Instance.IsInstanceMember = false; - EXPECT_EQ(Instance.evaluate(), Default.evaluate()); - Instance.Context = CodeCompletionContext::CCC_DotMemberAccess; - EXPECT_LT(Instance.evaluate(), Default.evaluate()); - Instance.IsInstanceMember = true; - EXPECT_EQ(Instance.evaluate(), Default.evaluate()); - - SymbolRelevanceSignals InBaseClass; - InBaseClass.InBaseClass = true; - EXPECT_LT(InBaseClass.evaluate(), Default.evaluate()); -} - -TEST(QualityTests, ScopeProximity) { - SymbolRelevanceSignals Relevance; - ScopeDistance ScopeProximity({"x::y::z::", "x::", "llvm::", ""}); - Relevance.ScopeProximityMatch = &ScopeProximity; - - Relevance.SymbolScope = "other::"; - float NotMatched = Relevance.evaluate(); - - Relevance.SymbolScope = ""; - float Global = Relevance.evaluate(); - EXPECT_GT(Global, NotMatched); - - Relevance.SymbolScope = "llvm::"; - float NonParent = Relevance.evaluate(); - EXPECT_GT(NonParent, Global); - - Relevance.SymbolScope = "x::"; - float GrandParent = Relevance.evaluate(); - EXPECT_GT(GrandParent, Global); - - Relevance.SymbolScope = "x::y::"; - float Parent = Relevance.evaluate(); - EXPECT_GT(Parent, GrandParent); - - Relevance.SymbolScope = "x::y::z::"; - float Enclosing = Relevance.evaluate(); - EXPECT_GT(Enclosing, Parent); -} - -TEST(QualityTests, SortText) { - EXPECT_LT(sortText(std::numeric_limits<float>::infinity()), - sortText(1000.2f)); - EXPECT_LT(sortText(1000.2f), sortText(1)); - EXPECT_LT(sortText(1), sortText(0.3f)); - EXPECT_LT(sortText(0.3f), sortText(0)); - EXPECT_LT(sortText(0), sortText(-10)); - EXPECT_LT(sortText(-10), sortText(-std::numeric_limits<float>::infinity())); - - EXPECT_LT(sortText(1, "z"), sortText(0, "a")); - EXPECT_LT(sortText(0, "a"), sortText(0, "z")); -} - -TEST(QualityTests, NoBoostForClassConstructor) { - auto Header = TestTU::withHeaderCode(R"cpp( - class Foo { - public: - Foo(int); - }; - )cpp"); - auto Symbols = Header.headerSymbols(); - auto AST = Header.build(); - - const NamedDecl *Foo = &findDecl(AST, "Foo"); - SymbolRelevanceSignals Cls; - Cls.merge(CodeCompletionResult(Foo, /*Priority=*/0)); - - const NamedDecl *CtorDecl = &findDecl(AST, [](const NamedDecl &ND) { - return (ND.getQualifiedNameAsString() == "Foo::Foo") && - isa<CXXConstructorDecl>(&ND); - }); - SymbolRelevanceSignals Ctor; - Ctor.merge(CodeCompletionResult(CtorDecl, /*Priority=*/0)); - - EXPECT_EQ(Cls.Scope, SymbolRelevanceSignals::GlobalScope); - EXPECT_EQ(Ctor.Scope, SymbolRelevanceSignals::GlobalScope); -} - -TEST(QualityTests, IsInstanceMember) { - auto Header = TestTU::withHeaderCode(R"cpp( - class Foo { - public: - static void foo() {} - - template <typename T> void tpl(T *t) {} - - void bar() {} - }; - )cpp"); - auto Symbols = Header.headerSymbols(); - - SymbolRelevanceSignals Rel; - const Symbol &FooSym = findSymbol(Symbols, "Foo::foo"); - Rel.merge(FooSym); - EXPECT_FALSE(Rel.IsInstanceMember); - const Symbol &BarSym = findSymbol(Symbols, "Foo::bar"); - Rel.merge(BarSym); - EXPECT_TRUE(Rel.IsInstanceMember); - - Rel.IsInstanceMember = false; - const Symbol &TplSym = findSymbol(Symbols, "Foo::tpl"); - Rel.merge(TplSym); - EXPECT_TRUE(Rel.IsInstanceMember); - - auto AST = Header.build(); - const NamedDecl *Foo = &findDecl(AST, "Foo::foo"); - const NamedDecl *Bar = &findDecl(AST, "Foo::bar"); - const NamedDecl *Tpl = &findDecl(AST, "Foo::tpl"); - - Rel.IsInstanceMember = false; - Rel.merge(CodeCompletionResult(Foo, /*Priority=*/0)); - EXPECT_FALSE(Rel.IsInstanceMember); - Rel.merge(CodeCompletionResult(Bar, /*Priority=*/0)); - EXPECT_TRUE(Rel.IsInstanceMember); - Rel.IsInstanceMember = false; - Rel.merge(CodeCompletionResult(Tpl, /*Priority=*/0)); - EXPECT_TRUE(Rel.IsInstanceMember); -} - -TEST(QualityTests, ConstructorDestructor) { - auto Header = TestTU::withHeaderCode(R"cpp( - class Foo { - public: - Foo(int); - ~Foo(); - }; - )cpp"); - auto Symbols = Header.headerSymbols(); - auto AST = Header.build(); - - const NamedDecl *CtorDecl = &findDecl(AST, [](const NamedDecl &ND) { - return (ND.getQualifiedNameAsString() == "Foo::Foo") && - isa<CXXConstructorDecl>(&ND); - }); - const NamedDecl *DtorDecl = &findDecl(AST, [](const NamedDecl &ND) { - return (ND.getQualifiedNameAsString() == "Foo::~Foo") && - isa<CXXDestructorDecl>(&ND); - }); - - SymbolQualitySignals CtorQ; - CtorQ.merge(CodeCompletionResult(CtorDecl, /*Priority=*/0)); - EXPECT_EQ(CtorQ.Category, SymbolQualitySignals::Constructor); - - CtorQ.Category = SymbolQualitySignals::Unknown; - const Symbol &CtorSym = findSymbol(Symbols, "Foo::Foo"); - CtorQ.merge(CtorSym); - EXPECT_EQ(CtorQ.Category, SymbolQualitySignals::Constructor); - - SymbolQualitySignals DtorQ; - DtorQ.merge(CodeCompletionResult(DtorDecl, /*Priority=*/0)); - EXPECT_EQ(DtorQ.Category, SymbolQualitySignals::Destructor); -} - -TEST(QualityTests, Operator) { - auto Header = TestTU::withHeaderCode(R"cpp( - class Foo { - public: - bool operator<(const Foo& f1); - }; - )cpp"); - auto AST = Header.build(); - - const NamedDecl *Operator = &findDecl(AST, [](const NamedDecl &ND) { - if (const auto *OD = dyn_cast<FunctionDecl>(&ND)) - if (OD->isOverloadedOperator()) - return true; - return false; - }); - SymbolQualitySignals Q; - Q.merge(CodeCompletionResult(Operator, /*Priority=*/0)); - EXPECT_EQ(Q.Category, SymbolQualitySignals::Operator); -} - -TEST(QualityTests, ItemWithFixItsRankedDown) { - CodeCompleteOptions Opts; - Opts.IncludeFixIts = true; - - auto Header = TestTU::withHeaderCode(R"cpp( - int x; - )cpp"); - auto AST = Header.build(); - - SymbolRelevanceSignals RelevanceWithFixIt; - RelevanceWithFixIt.merge(CodeCompletionResult(&findDecl(AST, "x"), 0, nullptr, - false, true, {FixItHint{}})); - EXPECT_TRUE(RelevanceWithFixIt.NeedsFixIts); - - SymbolRelevanceSignals RelevanceWithoutFixIt; - RelevanceWithoutFixIt.merge( - CodeCompletionResult(&findDecl(AST, "x"), 0, nullptr, false, true, {})); - EXPECT_FALSE(RelevanceWithoutFixIt.NeedsFixIts); - - EXPECT_LT(RelevanceWithFixIt.evaluate(), RelevanceWithoutFixIt.evaluate()); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/RIFFTests.cpp b/clang-tools-extra/unittests/clangd/RIFFTests.cpp deleted file mode 100644 index 4cd54f401d5..00000000000 --- a/clang-tools-extra/unittests/clangd/RIFFTests.cpp +++ /dev/null @@ -1,37 +0,0 @@ -//===-- RIFFTests.cpp - Binary container unit tests -----------------------===// -// -// 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 "RIFF.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { -using ::testing::ElementsAre; - -TEST(RIFFTest, File) { - riff::File File{riff::fourCC("test"), - { - {riff::fourCC("even"), "abcd"}, - {riff::fourCC("oddd"), "abcde"}, - }}; - llvm::StringRef Serialized = llvm::StringRef("RIFF\x1e\0\0\0test" - "even\x04\0\0\0abcd" - "oddd\x05\0\0\0abcde\0", - 38); - - EXPECT_EQ(llvm::to_string(File), Serialized); - auto Parsed = riff::readFile(Serialized); - ASSERT_TRUE(bool(Parsed)) << Parsed.takeError(); - EXPECT_EQ(*Parsed, File); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/SelectionTests.cpp b/clang-tools-extra/unittests/clangd/SelectionTests.cpp deleted file mode 100644 index ac9facca839..00000000000 --- a/clang-tools-extra/unittests/clangd/SelectionTests.cpp +++ /dev/null @@ -1,259 +0,0 @@ -//===-- SelectionTests.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 "Annotations.h" -#include "Selection.h" -#include "SourceCode.h" -#include "TestTU.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { -using ::testing::UnorderedElementsAreArray; - -SelectionTree makeSelectionTree(const StringRef MarkedCode, ParsedAST &AST) { - Annotations Test(MarkedCode); - switch (Test.points().size()) { - case 1: // Point selection. - return SelectionTree(AST.getASTContext(), - cantFail(positionToOffset(Test.code(), Test.point()))); - case 2: // Range selection. - return SelectionTree( - AST.getASTContext(), - cantFail(positionToOffset(Test.code(), Test.points()[0])), - cantFail(positionToOffset(Test.code(), Test.points()[1]))); - default: - ADD_FAILURE() << "Expected 1-2 points for selection.\n" << MarkedCode; - return SelectionTree(AST.getASTContext(), 0u, 0u); - } -} - -Range nodeRange(const SelectionTree::Node *N, ParsedAST &AST) { - if (!N) - return Range{}; - SourceManager &SM = AST.getASTContext().getSourceManager(); - StringRef Buffer = SM.getBufferData(SM.getMainFileID()); - SourceRange SR = N->ASTNode.getSourceRange(); - SR.setBegin(SM.getFileLoc(SR.getBegin())); - SR.setEnd(SM.getFileLoc(SR.getEnd())); - CharSourceRange R = - Lexer::getAsCharRange(SR, SM, AST.getASTContext().getLangOpts()); - return Range{offsetToPosition(Buffer, SM.getFileOffset(R.getBegin())), - offsetToPosition(Buffer, SM.getFileOffset(R.getEnd()))}; -} - -std::string nodeKind(const SelectionTree::Node *N) { - if (!N) - return "<null>"; - return N->ASTNode.getNodeKind().asStringRef().str(); -} - -std::vector<const SelectionTree::Node *> allNodes(const SelectionTree &T) { - std::vector<const SelectionTree::Node *> Result = {T.root()}; - for (unsigned I = 0; I < Result.size(); ++I) { - const SelectionTree::Node *N = Result[I]; - Result.insert(Result.end(), N->Children.begin(), N->Children.end()); - } - return Result; -} - -// Returns true if Common is a descendent of Root. -// Verifies nothing is selected above Common. -bool verifyCommonAncestor(const SelectionTree::Node *Root, - const SelectionTree::Node *Common, - StringRef MarkedCode) { - if (Root == Common) - return true; - if (Root->Selected) - ADD_FAILURE() << "Selected nodes outside common ancestor\n" << MarkedCode; - bool Seen = false; - for (const SelectionTree::Node *Child : Root->Children) - if (verifyCommonAncestor(Child, Common, MarkedCode)) { - if (Seen) - ADD_FAILURE() << "Saw common ancestor twice\n" << MarkedCode; - Seen = true; - } - return Seen; -} - -TEST(SelectionTest, CommonAncestor) { - struct Case { - // Selection is between ^marks^. - // common ancestor marked with a [[range]]. - const char *Code; - const char *CommonAncestorKind; - }; - Case Cases[] = { - { - R"cpp( - struct AAA { struct BBB { static int ccc(); };}; - int x = AAA::[[B^B^B]]::ccc(); - )cpp", - "TypeLoc", - }, - { - R"cpp( - struct AAA { struct BBB { static int ccc(); };}; - int x = AAA::[[B^BB^]]::ccc(); - )cpp", - "TypeLoc", - }, - { - R"cpp( - struct AAA { struct BBB { static int ccc(); };}; - int x = [[AAA::BBB::c^c^c]](); - )cpp", - "DeclRefExpr", - }, - { - R"cpp( - struct AAA { struct BBB { static int ccc(); };}; - int x = [[AAA::BBB::cc^c(^)]]; - )cpp", - "CallExpr", - }, - - { - R"cpp( - void foo() { [[if (1^11) { return; } else {^ }]] } - )cpp", - "IfStmt", - }, - { - R"cpp( - void foo(); - #define CALL_FUNCTION(X) X() - void bar() { CALL_FUNCTION([[f^o^o]]); } - )cpp", - "DeclRefExpr", - }, - { - R"cpp( - void foo(); - #define CALL_FUNCTION(X) X() - void bar() { CALL_FUNC^TION([[fo^o]]); } - )cpp", - "DeclRefExpr", - }, - { - R"cpp( - void foo(); - #define CALL_FUNCTION(X) X() - void bar() [[{ C^ALL_FUNC^TION(foo); }]] - )cpp", - "CompoundStmt", - }, - { - R"cpp( - void foo(); - #define CALL_FUNCTION(X) X^()^ - void bar() { CALL_FUNCTION(foo); } - )cpp", - nullptr, - }, - - // Point selections. - {"void foo() { [[^foo]](); }", "DeclRefExpr"}, - {"void foo() { [[f^oo]](); }", "DeclRefExpr"}, - {"void foo() { [[fo^o]](); }", "DeclRefExpr"}, - {"void foo() { [[foo^()]]; }", "CallExpr"}, - {"void foo() { [[foo^]] (); }", "DeclRefExpr"}, - {"int bar; void foo() [[{ foo (); }]]^", "CompoundStmt"}, - {"[[^void]] foo();", "TypeLoc"}, - {"^", nullptr}, - {"void foo() { [[foo^^]] (); }", "DeclRefExpr"}, - - // FIXME: Ideally we'd get a declstmt or the VarDecl itself here. - // This doesn't happen now; the RAV doesn't traverse a node containing ;. - {"int x = 42;^", nullptr}, - {"int x = 42^;", nullptr}, - - // Node types that have caused problems in the past. - {"template <typename T> void foo() { [[^T]] t; }", "TypeLoc"}, - - // No crash - { - R"cpp( - template <class T> struct Foo {}; - template <[[template<class> class /*cursor here*/^U]]> - struct Foo<U<int>*> {}; - )cpp", - "TemplateTemplateParmDecl" - }, - }; - for (const Case &C : Cases) { - Annotations Test(C.Code); - auto AST = TestTU::withCode(Test.code()).build(); - auto T = makeSelectionTree(C.Code, AST); - - if (Test.ranges().empty()) { - // If no [[range]] is marked in the example, there should be no selection. - EXPECT_FALSE(T.commonAncestor()) << C.Code << "\n" << T; - EXPECT_FALSE(T.root()) << C.Code << "\n" << T; - } else { - // If there is an expected selection, both common ancestor and root - // should exist with the appropriate node types in them. - EXPECT_EQ(C.CommonAncestorKind, nodeKind(T.commonAncestor())) - << C.Code << "\n" - << T; - EXPECT_EQ("TranslationUnitDecl", nodeKind(T.root())) << C.Code; - // Convert the reported common ancestor to a range and verify it. - EXPECT_EQ(nodeRange(T.commonAncestor(), AST), Test.range()) - << C.Code << "\n" - << T; - - // Check that common ancestor is reachable on exactly one path from root, - // and no nodes outside it are selected. - EXPECT_TRUE(verifyCommonAncestor(T.root(), T.commonAncestor(), C.Code)) - << C.Code; - } - } -} - -TEST(SelectionTest, Selected) { - // Selection with ^marks^. - // Partially selected nodes marked with a [[range]]. - // Completely selected nodes marked with a $C[[range]]. - const char *Cases[] = { - R"cpp( int abc, xyz = [[^ab^c]]; )cpp", - R"cpp( int abc, xyz = [[a^bc^]]; )cpp", - R"cpp( int abc, xyz = $C[[^abc^]]; )cpp", - R"cpp( - void foo() { - [[if ([[1^11]]) $C[[{ - $C[[return]]; - }]] else [[{^ - }]]]] - } - )cpp", - R"cpp( - template <class T> - struct unique_ptr {}; - void foo(^$C[[unique_ptr<unique_ptr<$C[[int]]>>]]^ a) {} - )cpp", - }; - for (const char *C : Cases) { - Annotations Test(C); - auto AST = TestTU::withCode(Test.code()).build(); - auto T = makeSelectionTree(C, AST); - - std::vector<Range> Complete, Partial; - for (const SelectionTree::Node *N : allNodes(T)) - if (N->Selected == SelectionTree::Complete) - Complete.push_back(nodeRange(N, AST)); - else if (N->Selected == SelectionTree::Partial) - Partial.push_back(nodeRange(N, AST)); - EXPECT_THAT(Complete, UnorderedElementsAreArray(Test.ranges("C"))) << C; - EXPECT_THAT(Partial, UnorderedElementsAreArray(Test.ranges())) << C; - } -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/SerializationTests.cpp b/clang-tools-extra/unittests/clangd/SerializationTests.cpp deleted file mode 100644 index 3260ac68019..00000000000 --- a/clang-tools-extra/unittests/clangd/SerializationTests.cpp +++ /dev/null @@ -1,219 +0,0 @@ -//===-- SerializationTests.cpp - Binary and YAML serialization unit tests -===// -// -// 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 "index/Index.h" -#include "index/Serialization.h" -#include "llvm/Support/SHA1.h" -#include "llvm/Support/ScopedPrinter.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -using testing::_; -using testing::AllOf; -using testing::Pair; -using testing::UnorderedElementsAre; -using testing::UnorderedElementsAreArray; - -namespace clang { -namespace clangd { -namespace { - -const char *YAML = R"( ---- -!Symbol -ID: 057557CEBF6E6B2D -Name: 'Foo1' -Scope: 'clang::' -SymInfo: - Kind: Function - Lang: Cpp -CanonicalDeclaration: - FileURI: file:///path/foo.h - Start: - Line: 1 - Column: 0 - End: - Line: 1 - Column: 1 -Origin: 4 -Flags: 1 -Documentation: 'Foo doc' -ReturnType: 'int' -IncludeHeaders: - - Header: 'include1' - References: 7 - - Header: 'include2' - References: 3 -... ---- -!Symbol -ID: 057557CEBF6E6B2E -Name: 'Foo2' -Scope: 'clang::' -SymInfo: - Kind: Function - Lang: Cpp -CanonicalDeclaration: - FileURI: file:///path/bar.h - Start: - Line: 1 - Column: 0 - End: - Line: 1 - Column: 1 -Flags: 2 -Signature: '-sig' -CompletionSnippetSuffix: '-snippet' -... -!Refs -ID: 057557CEBF6E6B2D -References: - - Kind: 4 - Location: - FileURI: file:///path/foo.cc - Start: - Line: 5 - Column: 3 - End: - Line: 5 - Column: 8 -)"; - -MATCHER_P(ID, I, "") { return arg.ID == cantFail(SymbolID::fromStr(I)); } -MATCHER_P(QName, Name, "") { return (arg.Scope + arg.Name).str() == Name; } -MATCHER_P2(IncludeHeaderWithRef, IncludeHeader, References, "") { - return (arg.IncludeHeader == IncludeHeader) && (arg.References == References); -} - -TEST(SerializationTest, NoCrashOnEmptyYAML) { - EXPECT_TRUE(bool(readIndexFile(""))); -} - -TEST(SerializationTest, YAMLConversions) { - auto In = readIndexFile(YAML); - EXPECT_TRUE(bool(In)) << In.takeError(); - - auto ParsedYAML = readIndexFile(YAML); - ASSERT_TRUE(bool(ParsedYAML)) << ParsedYAML.takeError(); - ASSERT_TRUE(bool(ParsedYAML->Symbols)); - EXPECT_THAT( - *ParsedYAML->Symbols, - UnorderedElementsAre(ID("057557CEBF6E6B2D"), ID("057557CEBF6E6B2E"))); - - auto Sym1 = *ParsedYAML->Symbols->find( - cantFail(SymbolID::fromStr("057557CEBF6E6B2D"))); - auto Sym2 = *ParsedYAML->Symbols->find( - cantFail(SymbolID::fromStr("057557CEBF6E6B2E"))); - - EXPECT_THAT(Sym1, QName("clang::Foo1")); - EXPECT_EQ(Sym1.Signature, ""); - EXPECT_EQ(Sym1.Documentation, "Foo doc"); - EXPECT_EQ(Sym1.ReturnType, "int"); - EXPECT_EQ(StringRef(Sym1.CanonicalDeclaration.FileURI), "file:///path/foo.h"); - EXPECT_EQ(Sym1.Origin, SymbolOrigin::Static); - EXPECT_TRUE(Sym1.Flags & Symbol::IndexedForCodeCompletion); - EXPECT_FALSE(Sym1.Flags & Symbol::Deprecated); - EXPECT_THAT(Sym1.IncludeHeaders, - UnorderedElementsAre(IncludeHeaderWithRef("include1", 7u), - IncludeHeaderWithRef("include2", 3u))); - - EXPECT_THAT(Sym2, QName("clang::Foo2")); - EXPECT_EQ(Sym2.Signature, "-sig"); - EXPECT_EQ(Sym2.ReturnType, ""); - EXPECT_EQ(llvm::StringRef(Sym2.CanonicalDeclaration.FileURI), - "file:///path/bar.h"); - EXPECT_FALSE(Sym2.Flags & Symbol::IndexedForCodeCompletion); - EXPECT_TRUE(Sym2.Flags & Symbol::Deprecated); - - ASSERT_TRUE(bool(ParsedYAML->Refs)); - EXPECT_THAT( - *ParsedYAML->Refs, - UnorderedElementsAre(Pair(cantFail(SymbolID::fromStr("057557CEBF6E6B2D")), - testing::SizeIs(1)))); - auto Ref1 = ParsedYAML->Refs->begin()->second.front(); - EXPECT_EQ(Ref1.Kind, RefKind::Reference); - EXPECT_EQ(StringRef(Ref1.Location.FileURI), "file:///path/foo.cc"); -} - -std::vector<std::string> YAMLFromSymbols(const SymbolSlab &Slab) { - std::vector<std::string> Result; - for (const auto &Sym : Slab) - Result.push_back(toYAML(Sym)); - return Result; -} -std::vector<std::string> YAMLFromRefs(const RefSlab &Slab) { - std::vector<std::string> Result; - for (const auto &Sym : Slab) - Result.push_back(toYAML(Sym)); - return Result; -} - -TEST(SerializationTest, BinaryConversions) { - auto In = readIndexFile(YAML); - EXPECT_TRUE(bool(In)) << In.takeError(); - - // Write to binary format, and parse again. - IndexFileOut Out(*In); - Out.Format = IndexFileFormat::RIFF; - std::string Serialized = llvm::to_string(Out); - - auto In2 = readIndexFile(Serialized); - ASSERT_TRUE(bool(In2)) << In.takeError(); - ASSERT_TRUE(In2->Symbols); - ASSERT_TRUE(In2->Refs); - - // Assert the YAML serializations match, for nice comparisons and diffs. - EXPECT_THAT(YAMLFromSymbols(*In2->Symbols), - UnorderedElementsAreArray(YAMLFromSymbols(*In->Symbols))); - EXPECT_THAT(YAMLFromRefs(*In2->Refs), - UnorderedElementsAreArray(YAMLFromRefs(*In->Refs))); -} - -TEST(SerializationTest, SrcsTest) { - auto In = readIndexFile(YAML); - EXPECT_TRUE(bool(In)) << In.takeError(); - - std::string TestContent("TestContent"); - IncludeGraphNode IGN; - IGN.Digest = - llvm::SHA1::hash({reinterpret_cast<const uint8_t *>(TestContent.data()), - TestContent.size()}); - IGN.DirectIncludes = {"inc1", "inc2"}; - IGN.URI = "URI"; - IGN.IsTU = true; - IncludeGraph Sources; - Sources[IGN.URI] = IGN; - // Write to binary format, and parse again. - IndexFileOut Out(*In); - Out.Format = IndexFileFormat::RIFF; - Out.Sources = &Sources; - { - std::string Serialized = llvm::to_string(Out); - - auto In = readIndexFile(Serialized); - ASSERT_TRUE(bool(In)) << In.takeError(); - ASSERT_TRUE(In->Symbols); - ASSERT_TRUE(In->Refs); - ASSERT_TRUE(In->Sources); - ASSERT_TRUE(In->Sources->count(IGN.URI)); - // Assert the YAML serializations match, for nice comparisons and diffs. - EXPECT_THAT(YAMLFromSymbols(*In->Symbols), - UnorderedElementsAreArray(YAMLFromSymbols(*In->Symbols))); - EXPECT_THAT(YAMLFromRefs(*In->Refs), - UnorderedElementsAreArray(YAMLFromRefs(*In->Refs))); - auto IGNDeserialized = In->Sources->lookup(IGN.URI); - EXPECT_EQ(IGNDeserialized.Digest, IGN.Digest); - EXPECT_EQ(IGNDeserialized.DirectIncludes, IGN.DirectIncludes); - EXPECT_EQ(IGNDeserialized.URI, IGN.URI); - EXPECT_EQ(IGNDeserialized.IsTU, IGN.IsTU); - } -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/SourceCodeTests.cpp b/clang-tools-extra/unittests/clangd/SourceCodeTests.cpp deleted file mode 100644 index e9f4c00d35a..00000000000 --- a/clang-tools-extra/unittests/clangd/SourceCodeTests.cpp +++ /dev/null @@ -1,395 +0,0 @@ -//===-- SourceCodeTests.cpp ------------------------------------*- C++ -*-===// -// -// 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 "Annotations.h" -#include "Context.h" -#include "Protocol.h" -#include "SourceCode.h" -#include "clang/Format/Format.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/raw_os_ostream.h" -#include "llvm/Testing/Support/Error.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -using llvm::Failed; -using llvm::HasValue; - -MATCHER_P2(Pos, Line, Col, "") { - return arg.line == int(Line) && arg.character == int(Col); -} - -/// A helper to make tests easier to read. -Position position(int line, int character) { - Position Pos; - Pos.line = line; - Pos.character = character; - return Pos; -} - -Range range(const std::pair<int, int> p1, const std::pair<int, int> p2) { - Range range; - range.start = position(p1.first, p1.second); - range.end = position(p2.first, p2.second); - return range; -} - -TEST(SourceCodeTests, lspLength) { - EXPECT_EQ(lspLength(""), 0UL); - EXPECT_EQ(lspLength("ascii"), 5UL); - // BMP - EXPECT_EQ(lspLength("↓"), 1UL); - EXPECT_EQ(lspLength("¥"), 1UL); - // astral - EXPECT_EQ(lspLength("😂"), 2UL); - - WithContextValue UTF8(kCurrentOffsetEncoding, OffsetEncoding::UTF8); - EXPECT_EQ(lspLength(""), 0UL); - EXPECT_EQ(lspLength("ascii"), 5UL); - // BMP - EXPECT_EQ(lspLength("↓"), 3UL); - EXPECT_EQ(lspLength("¥"), 2UL); - // astral - EXPECT_EQ(lspLength("😂"), 4UL); - - WithContextValue UTF32(kCurrentOffsetEncoding, OffsetEncoding::UTF32); - EXPECT_EQ(lspLength(""), 0UL); - EXPECT_EQ(lspLength("ascii"), 5UL); - // BMP - EXPECT_EQ(lspLength("↓"), 1UL); - EXPECT_EQ(lspLength("¥"), 1UL); - // astral - EXPECT_EQ(lspLength("😂"), 1UL); -} - -// The = → 🡆 below are ASCII (1 byte), BMP (3 bytes), and astral (4 bytes). -const char File[] = R"(0:0 = 0 -1:0 → 8 -2:0 🡆 18)"; -struct Line { - unsigned Number; - unsigned Offset; - unsigned Length; -}; -Line FileLines[] = {Line{0, 0, 7}, Line{1, 8, 9}, Line{2, 18, 11}}; - -TEST(SourceCodeTests, PositionToOffset) { - // line out of bounds - EXPECT_THAT_EXPECTED(positionToOffset(File, position(-1, 2)), llvm::Failed()); - // first line - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, -1)), - llvm::Failed()); // out of range - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 0)), - llvm::HasValue(0)); // first character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 3)), - llvm::HasValue(3)); // middle character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 6)), - llvm::HasValue(6)); // last character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 7)), - llvm::HasValue(7)); // the newline itself - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 7), false), - llvm::HasValue(7)); - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 8)), - llvm::HasValue(7)); // out of range - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 8), false), - llvm::Failed()); // out of range - // middle line - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, -1)), - llvm::Failed()); // out of range - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 0)), - llvm::HasValue(8)); // first character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 3)), - llvm::HasValue(11)); // middle character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 3), false), - llvm::HasValue(11)); - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 6)), - llvm::HasValue(16)); // last character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 7)), - llvm::HasValue(17)); // the newline itself - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 8)), - llvm::HasValue(17)); // out of range - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 8), false), - llvm::Failed()); // out of range - // last line - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, -1)), - llvm::Failed()); // out of range - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 0)), - llvm::HasValue(18)); // first character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 3)), - llvm::HasValue(21)); // middle character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 5), false), - llvm::Failed()); // middle of surrogate pair - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 5)), - llvm::HasValue(26)); // middle of surrogate pair - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 6), false), - llvm::HasValue(26)); // end of surrogate pair - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 8)), - llvm::HasValue(28)); // last character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 9)), - llvm::HasValue(29)); // EOF - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 10), false), - llvm::Failed()); // out of range - // line out of bounds - EXPECT_THAT_EXPECTED(positionToOffset(File, position(3, 0)), llvm::Failed()); - EXPECT_THAT_EXPECTED(positionToOffset(File, position(3, 1)), llvm::Failed()); - - // Codepoints are similar, except near astral characters. - WithContextValue UTF32(kCurrentOffsetEncoding, OffsetEncoding::UTF32); - // line out of bounds - EXPECT_THAT_EXPECTED(positionToOffset(File, position(-1, 2)), llvm::Failed()); - // first line - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, -1)), - llvm::Failed()); // out of range - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 0)), - llvm::HasValue(0)); // first character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 3)), - llvm::HasValue(3)); // middle character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 6)), - llvm::HasValue(6)); // last character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 7)), - llvm::HasValue(7)); // the newline itself - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 7), false), - llvm::HasValue(7)); - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 8)), - llvm::HasValue(7)); // out of range - EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 8), false), - llvm::Failed()); // out of range - // middle line - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, -1)), - llvm::Failed()); // out of range - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 0)), - llvm::HasValue(8)); // first character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 3)), - llvm::HasValue(11)); // middle character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 3), false), - llvm::HasValue(11)); - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 6)), - llvm::HasValue(16)); // last character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 7)), - llvm::HasValue(17)); // the newline itself - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 8)), - llvm::HasValue(17)); // out of range - EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 8), false), - llvm::Failed()); // out of range - // last line - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, -1)), - llvm::Failed()); // out of range - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 0)), - llvm::HasValue(18)); // first character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 4)), - llvm::HasValue(22)); // Before astral character. - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 5), false), - llvm::HasValue(26)); // after astral character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 7)), - llvm::HasValue(28)); // last character - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 8)), - llvm::HasValue(29)); // EOF - EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 9), false), - llvm::Failed()); // out of range - // line out of bounds - EXPECT_THAT_EXPECTED(positionToOffset(File, position(3, 0)), llvm::Failed()); - EXPECT_THAT_EXPECTED(positionToOffset(File, position(3, 1)), llvm::Failed()); - - // Test UTF-8, where transformations are trivial. - WithContextValue UTF8(kCurrentOffsetEncoding, OffsetEncoding::UTF8); - EXPECT_THAT_EXPECTED(positionToOffset(File, position(-1, 2)), llvm::Failed()); - EXPECT_THAT_EXPECTED(positionToOffset(File, position(3, 0)), llvm::Failed()); - for (Line L : FileLines) { - EXPECT_THAT_EXPECTED(positionToOffset(File, position(L.Number, -1)), - llvm::Failed()); // out of range - for (unsigned I = 0; I <= L.Length; ++I) - EXPECT_THAT_EXPECTED(positionToOffset(File, position(L.Number, I)), - llvm::HasValue(L.Offset + I)); - EXPECT_THAT_EXPECTED(positionToOffset(File, position(L.Number, L.Length+1)), - llvm::HasValue(L.Offset + L.Length)); - EXPECT_THAT_EXPECTED( - positionToOffset(File, position(L.Number, L.Length + 1), false), - llvm::Failed()); // out of range - } -} - -TEST(SourceCodeTests, OffsetToPosition) { - EXPECT_THAT(offsetToPosition(File, 0), Pos(0, 0)) << "start of file"; - EXPECT_THAT(offsetToPosition(File, 3), Pos(0, 3)) << "in first line"; - EXPECT_THAT(offsetToPosition(File, 6), Pos(0, 6)) << "end of first line"; - EXPECT_THAT(offsetToPosition(File, 7), Pos(0, 7)) << "first newline"; - EXPECT_THAT(offsetToPosition(File, 8), Pos(1, 0)) << "start of second line"; - EXPECT_THAT(offsetToPosition(File, 12), Pos(1, 4)) << "before BMP char"; - EXPECT_THAT(offsetToPosition(File, 13), Pos(1, 5)) << "in BMP char"; - EXPECT_THAT(offsetToPosition(File, 15), Pos(1, 5)) << "after BMP char"; - EXPECT_THAT(offsetToPosition(File, 16), Pos(1, 6)) << "end of second line"; - EXPECT_THAT(offsetToPosition(File, 17), Pos(1, 7)) << "second newline"; - EXPECT_THAT(offsetToPosition(File, 18), Pos(2, 0)) << "start of last line"; - EXPECT_THAT(offsetToPosition(File, 21), Pos(2, 3)) << "in last line"; - EXPECT_THAT(offsetToPosition(File, 22), Pos(2, 4)) << "before astral char"; - EXPECT_THAT(offsetToPosition(File, 24), Pos(2, 6)) << "in astral char"; - EXPECT_THAT(offsetToPosition(File, 26), Pos(2, 6)) << "after astral char"; - EXPECT_THAT(offsetToPosition(File, 28), Pos(2, 8)) << "end of last line"; - EXPECT_THAT(offsetToPosition(File, 29), Pos(2, 9)) << "EOF"; - EXPECT_THAT(offsetToPosition(File, 30), Pos(2, 9)) << "out of bounds"; - - // Codepoints are similar, except near astral characters. - WithContextValue UTF32(kCurrentOffsetEncoding, OffsetEncoding::UTF32); - EXPECT_THAT(offsetToPosition(File, 0), Pos(0, 0)) << "start of file"; - EXPECT_THAT(offsetToPosition(File, 3), Pos(0, 3)) << "in first line"; - EXPECT_THAT(offsetToPosition(File, 6), Pos(0, 6)) << "end of first line"; - EXPECT_THAT(offsetToPosition(File, 7), Pos(0, 7)) << "first newline"; - EXPECT_THAT(offsetToPosition(File, 8), Pos(1, 0)) << "start of second line"; - EXPECT_THAT(offsetToPosition(File, 12), Pos(1, 4)) << "before BMP char"; - EXPECT_THAT(offsetToPosition(File, 13), Pos(1, 5)) << "in BMP char"; - EXPECT_THAT(offsetToPosition(File, 15), Pos(1, 5)) << "after BMP char"; - EXPECT_THAT(offsetToPosition(File, 16), Pos(1, 6)) << "end of second line"; - EXPECT_THAT(offsetToPosition(File, 17), Pos(1, 7)) << "second newline"; - EXPECT_THAT(offsetToPosition(File, 18), Pos(2, 0)) << "start of last line"; - EXPECT_THAT(offsetToPosition(File, 21), Pos(2, 3)) << "in last line"; - EXPECT_THAT(offsetToPosition(File, 22), Pos(2, 4)) << "before astral char"; - EXPECT_THAT(offsetToPosition(File, 24), Pos(2, 5)) << "in astral char"; - EXPECT_THAT(offsetToPosition(File, 26), Pos(2, 5)) << "after astral char"; - EXPECT_THAT(offsetToPosition(File, 28), Pos(2, 7)) << "end of last line"; - EXPECT_THAT(offsetToPosition(File, 29), Pos(2, 8)) << "EOF"; - EXPECT_THAT(offsetToPosition(File, 30), Pos(2, 8)) << "out of bounds"; - - WithContextValue UTF8(kCurrentOffsetEncoding, OffsetEncoding::UTF8); - for (Line L : FileLines) { - for (unsigned I = 0; I <= L.Length; ++I) - EXPECT_THAT(offsetToPosition(File, L.Offset + I), Pos(L.Number, I)); - } - EXPECT_THAT(offsetToPosition(File, 30), Pos(2, 11)) << "out of bounds"; -} - -TEST(SourceCodeTests, IsRangeConsecutive) { - EXPECT_TRUE(isRangeConsecutive(range({2, 2}, {2, 3}), range({2, 3}, {2, 4}))); - EXPECT_FALSE( - isRangeConsecutive(range({0, 2}, {0, 3}), range({2, 3}, {2, 4}))); - EXPECT_FALSE( - isRangeConsecutive(range({2, 2}, {2, 3}), range({2, 4}, {2, 5}))); -} - -TEST(SourceCodeTests, SourceLocationInMainFile) { - Annotations Source(R"cpp( - ^in^t ^foo - ^bar - ^baz ^() {} {} {} {} { }^ -)cpp"); - - SourceManagerForFile Owner("foo.cpp", Source.code()); - SourceManager &SM = Owner.get(); - - SourceLocation StartOfFile = SM.getLocForStartOfFile(SM.getMainFileID()); - EXPECT_THAT_EXPECTED(sourceLocationInMainFile(SM, position(0, 0)), - HasValue(StartOfFile)); - // End of file. - EXPECT_THAT_EXPECTED( - sourceLocationInMainFile(SM, position(4, 0)), - HasValue(StartOfFile.getLocWithOffset(Source.code().size()))); - // Column number is too large. - EXPECT_THAT_EXPECTED(sourceLocationInMainFile(SM, position(0, 1)), Failed()); - EXPECT_THAT_EXPECTED(sourceLocationInMainFile(SM, position(0, 100)), - Failed()); - EXPECT_THAT_EXPECTED(sourceLocationInMainFile(SM, position(4, 1)), Failed()); - // Line number is too large. - EXPECT_THAT_EXPECTED(sourceLocationInMainFile(SM, position(5, 0)), Failed()); - // Check all positions mentioned in the test return valid results. - for (auto P : Source.points()) { - size_t Offset = llvm::cantFail(positionToOffset(Source.code(), P)); - EXPECT_THAT_EXPECTED(sourceLocationInMainFile(SM, P), - HasValue(StartOfFile.getLocWithOffset(Offset))); - } -} - -TEST(SourceCodeTests, CollectIdentifiers) { - auto Style = format::getLLVMStyle(); - auto IDs = collectIdentifiers(R"cpp( - #include "a.h" - void foo() { int xyz; int abc = xyz; return foo(); } - )cpp", - Style); - EXPECT_EQ(IDs.size(), 7u); - EXPECT_EQ(IDs["include"], 1u); - EXPECT_EQ(IDs["void"], 1u); - EXPECT_EQ(IDs["int"], 2u); - EXPECT_EQ(IDs["xyz"], 2u); - EXPECT_EQ(IDs["abc"], 1u); - EXPECT_EQ(IDs["return"], 1u); - EXPECT_EQ(IDs["foo"], 2u); -} - -TEST(SourceCodeTests, VisibleNamespaces) { - std::vector<std::pair<const char *, std::vector<std::string>>> Cases = { - { - R"cpp( - // Using directive resolved against enclosing namespaces. - using namespace foo; - namespace ns { - using namespace bar; - )cpp", - {"ns", "", "bar", "foo", "ns::bar"}, - }, - { - R"cpp( - // Don't include namespaces we've closed, ignore namespace aliases. - using namespace clang; - using std::swap; - namespace clang { - namespace clangd {} - namespace ll = ::llvm; - } - namespace clang { - )cpp", - {"clang", ""}, - }, - { - R"cpp( - // Using directives visible even if a namespace is reopened. - // Ignore anonymous namespaces. - namespace foo{ using namespace bar; } - namespace foo{ namespace { - )cpp", - {"foo", "", "bar", "foo::bar"}, - }, - { - R"cpp( - // Mismatched braces - namespace foo{} - }}} - namespace bar{ - )cpp", - {"bar", ""}, - }, - { - R"cpp( - // Namespaces with multiple chunks. - namespace a::b { - using namespace c::d; - namespace e::f { - )cpp", - { - "a::b::e::f", - "", - "a", - "a::b", - "a::b::c::d", - "a::b::e", - "a::c::d", - "c::d", - }, - }, - }; - for (const auto& Case : Cases) { - EXPECT_EQ(Case.second, - visibleNamespaces(Case.first, format::getLLVMStyle())) - << Case.first; - } -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/SymbolCollectorTests.cpp b/clang-tools-extra/unittests/clangd/SymbolCollectorTests.cpp deleted file mode 100644 index 3d8766a0a39..00000000000 --- a/clang-tools-extra/unittests/clangd/SymbolCollectorTests.cpp +++ /dev/null @@ -1,1272 +0,0 @@ -//===-- SymbolCollectorTests.cpp -------------------------------*- C++ -*-===// -// -// 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 "Annotations.h" -#include "TestFS.h" -#include "TestTU.h" -#include "index/SymbolCollector.h" -#include "clang/Basic/FileManager.h" -#include "clang/Basic/FileSystemOptions.h" -#include "clang/Frontend/CompilerInstance.h" -#include "clang/Index/IndexingAction.h" -#include "clang/Tooling/Tooling.h" -#include "llvm/ADT/IntrusiveRefCntPtr.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/VirtualFileSystem.h" -#include "gmock/gmock-more-matchers.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -#include <memory> -#include <string> - -namespace clang { -namespace clangd { -namespace { - -using testing::_; -using testing::AllOf; -using testing::Contains; -using testing::ElementsAre; -using testing::Field; -using testing::IsEmpty; -using testing::Not; -using testing::Pair; -using testing::UnorderedElementsAre; -using testing::UnorderedElementsAreArray; - -// GMock helpers for matching Symbol. -MATCHER_P(Labeled, Label, "") { - return (arg.Name + arg.Signature).str() == Label; -} -MATCHER_P(ReturnType, D, "") { return arg.ReturnType == D; } -MATCHER_P(Doc, D, "") { return arg.Documentation == D; } -MATCHER_P(Snippet, S, "") { - return (arg.Name + arg.CompletionSnippetSuffix).str() == S; -} -MATCHER_P(QName, Name, "") { return (arg.Scope + arg.Name).str() == Name; } -MATCHER_P(TemplateArgs, TemplArgs, "") { - return arg.TemplateSpecializationArgs == TemplArgs; -} -MATCHER_P(DeclURI, P, "") { - return StringRef(arg.CanonicalDeclaration.FileURI) == P; -} -MATCHER_P(DefURI, P, "") { return StringRef(arg.Definition.FileURI) == P; } -MATCHER(IncludeHeader, "") { return !arg.IncludeHeaders.empty(); } -MATCHER_P(IncludeHeader, P, "") { - return (arg.IncludeHeaders.size() == 1) && - (arg.IncludeHeaders.begin()->IncludeHeader == P); -} -MATCHER_P2(IncludeHeaderWithRef, IncludeHeader, References, "") { - return (arg.IncludeHeader == IncludeHeader) && (arg.References == References); -} -MATCHER_P(DeclRange, Pos, "") { - return std::make_tuple(arg.CanonicalDeclaration.Start.line(), - arg.CanonicalDeclaration.Start.column(), - arg.CanonicalDeclaration.End.line(), - arg.CanonicalDeclaration.End.column()) == - std::make_tuple(Pos.start.line, Pos.start.character, Pos.end.line, - Pos.end.character); -} -MATCHER_P(DefRange, Pos, "") { - return std::make_tuple( - arg.Definition.Start.line(), arg.Definition.Start.column(), - arg.Definition.End.line(), arg.Definition.End.column()) == - std::make_tuple(Pos.start.line, Pos.start.character, Pos.end.line, - Pos.end.character); -} -MATCHER_P(RefCount, R, "") { return int(arg.References) == R; } -MATCHER_P(ForCodeCompletion, IsIndexedForCodeCompletion, "") { - return static_cast<bool>(arg.Flags & Symbol::IndexedForCodeCompletion) == - IsIndexedForCodeCompletion; -} -MATCHER(Deprecated, "") { return arg.Flags & Symbol::Deprecated; } -MATCHER(ImplementationDetail, "") { - return arg.Flags & Symbol::ImplementationDetail; -} -MATCHER(VisibleOutsideFile, "") { - return static_cast<bool>(arg.Flags & Symbol::VisibleOutsideFile); -} -MATCHER(RefRange, "") { - const Ref &Pos = testing::get<0>(arg); - const Range &Range = testing::get<1>(arg); - return std::make_tuple(Pos.Location.Start.line(), Pos.Location.Start.column(), - Pos.Location.End.line(), Pos.Location.End.column()) == - std::make_tuple(Range.start.line, Range.start.character, - Range.end.line, Range.end.character); -} -testing::Matcher<const std::vector<Ref> &> -HaveRanges(const std::vector<Range> Ranges) { - return testing::UnorderedPointwise(RefRange(), Ranges); -} - -class ShouldCollectSymbolTest : public ::testing::Test { -public: - void build(llvm::StringRef HeaderCode, llvm::StringRef Code = "") { - File.HeaderFilename = HeaderName; - File.Filename = FileName; - File.HeaderCode = HeaderCode; - File.Code = Code; - AST = File.build(); - } - - // build() must have been called. - bool shouldCollect(llvm::StringRef Name, bool Qualified = true) { - assert(AST.hasValue()); - const NamedDecl& ND = Qualified ? findDecl(*AST, Name) - : findUnqualifiedDecl(*AST, Name); - ASTContext& Ctx = AST->getASTContext(); - const SourceManager& SM = Ctx.getSourceManager(); - bool MainFile = SM.isWrittenInMainFile(SM.getExpansionLoc(ND.getBeginLoc())); - return SymbolCollector::shouldCollectSymbol( - ND, Ctx, SymbolCollector::Options(), MainFile); - } - -protected: - std::string HeaderName = "f.h"; - std::string FileName = "f.cpp"; - TestTU File; - llvm::Optional<ParsedAST> AST; // Initialized after build. -}; - -TEST_F(ShouldCollectSymbolTest, ShouldCollectSymbol) { - build(R"( - namespace nx { - class X{}; - auto f() { int Local; } // auto ensures function body is parsed. - struct { int x; } var; - } - )", - R"( - class InMain {}; - namespace { class InAnonymous {}; } - static void g(); - )"); - auto AST = File.build(); - EXPECT_TRUE(shouldCollect("nx")); - EXPECT_TRUE(shouldCollect("nx::X")); - EXPECT_TRUE(shouldCollect("nx::f")); - EXPECT_TRUE(shouldCollect("InMain")); - EXPECT_TRUE(shouldCollect("InAnonymous", /*Qualified=*/false)); - EXPECT_TRUE(shouldCollect("g")); - - EXPECT_FALSE(shouldCollect("Local", /*Qualified=*/false)); -} - -TEST_F(ShouldCollectSymbolTest, NoPrivateProtoSymbol) { - HeaderName = "f.proto.h"; - build( - R"(// Generated by the protocol buffer compiler. DO NOT EDIT! - namespace nx { - class Top_Level {}; - class TopLevel {}; - enum Kind { - KIND_OK, - Kind_Not_Ok, - }; - })"); - EXPECT_TRUE(shouldCollect("nx::TopLevel")); - EXPECT_TRUE(shouldCollect("nx::Kind::KIND_OK")); - EXPECT_TRUE(shouldCollect("nx::Kind")); - - EXPECT_FALSE(shouldCollect("nx::Top_Level")); - EXPECT_FALSE(shouldCollect("nx::Kind::Kind_Not_Ok")); -} - -TEST_F(ShouldCollectSymbolTest, DoubleCheckProtoHeaderComment) { - HeaderName = "f.proto.h"; - build(R"( - namespace nx { - class Top_Level {}; - enum Kind { - Kind_Fine - }; - } - )"); - EXPECT_TRUE(shouldCollect("nx::Top_Level")); - EXPECT_TRUE(shouldCollect("nx::Kind_Fine")); -} - -class SymbolIndexActionFactory : public tooling::FrontendActionFactory { -public: - SymbolIndexActionFactory(SymbolCollector::Options COpts, - CommentHandler *PragmaHandler) - : COpts(std::move(COpts)), PragmaHandler(PragmaHandler) {} - - clang::FrontendAction *create() override { - class WrappedIndexAction : public WrapperFrontendAction { - public: - WrappedIndexAction(std::shared_ptr<SymbolCollector> C, - const index::IndexingOptions &Opts, - CommentHandler *PragmaHandler) - : WrapperFrontendAction( - index::createIndexingAction(C, Opts, nullptr)), - PragmaHandler(PragmaHandler) {} - - std::unique_ptr<ASTConsumer> - CreateASTConsumer(CompilerInstance &CI, llvm::StringRef InFile) override { - if (PragmaHandler) - CI.getPreprocessor().addCommentHandler(PragmaHandler); - return WrapperFrontendAction::CreateASTConsumer(CI, InFile); - } - - bool BeginInvocation(CompilerInstance &CI) override { - // Make the compiler parse all comments. - CI.getLangOpts().CommentOpts.ParseAllComments = true; - return WrapperFrontendAction::BeginInvocation(CI); - } - - private: - index::IndexingOptions IndexOpts; - CommentHandler *PragmaHandler; - }; - index::IndexingOptions IndexOpts; - IndexOpts.SystemSymbolFilter = - index::IndexingOptions::SystemSymbolFilterKind::All; - IndexOpts.IndexFunctionLocals = false; - Collector = std::make_shared<SymbolCollector>(COpts); - return new WrappedIndexAction(Collector, std::move(IndexOpts), - PragmaHandler); - } - - std::shared_ptr<SymbolCollector> Collector; - SymbolCollector::Options COpts; - CommentHandler *PragmaHandler; -}; - -class SymbolCollectorTest : public ::testing::Test { -public: - SymbolCollectorTest() - : InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem), - TestHeaderName(testPath("symbol.h")), - TestFileName(testPath("symbol.cc")) { - TestHeaderURI = URI::create(TestHeaderName).toString(); - TestFileURI = URI::create(TestFileName).toString(); - } - - // Note that unlike TestTU, no automatic header guard is added. - // HeaderCode should start with #pragma once to be treated as modular. - bool runSymbolCollector(llvm::StringRef HeaderCode, llvm::StringRef MainCode, - const std::vector<std::string> &ExtraArgs = {}) { - llvm::IntrusiveRefCntPtr<FileManager> Files( - new FileManager(FileSystemOptions(), InMemoryFileSystem)); - - auto Factory = llvm::make_unique<SymbolIndexActionFactory>( - CollectorOpts, PragmaHandler.get()); - - std::vector<std::string> Args = {"symbol_collector", "-fsyntax-only", - "-xc++", "-include", TestHeaderName}; - Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); - // This allows to override the "-xc++" with something else, i.e. - // -xobjective-c++. - Args.push_back(TestFileName); - - tooling::ToolInvocation Invocation( - Args, Factory->create(), Files.get(), - std::make_shared<PCHContainerOperations>()); - - InMemoryFileSystem->addFile( - TestHeaderName, 0, llvm::MemoryBuffer::getMemBuffer(HeaderCode)); - InMemoryFileSystem->addFile(TestFileName, 0, - llvm::MemoryBuffer::getMemBuffer(MainCode)); - Invocation.run(); - Symbols = Factory->Collector->takeSymbols(); - Refs = Factory->Collector->takeRefs(); - return true; - } - -protected: - llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem; - std::string TestHeaderName; - std::string TestHeaderURI; - std::string TestFileName; - std::string TestFileURI; - SymbolSlab Symbols; - RefSlab Refs; - SymbolCollector::Options CollectorOpts; - std::unique_ptr<CommentHandler> PragmaHandler; -}; - -TEST_F(SymbolCollectorTest, CollectSymbols) { - const std::string Header = R"( - class Foo { - Foo() {} - Foo(int a) {} - void f(); - friend void f1(); - friend class Friend; - Foo& operator=(const Foo&); - ~Foo(); - class Nested { - void f(); - }; - }; - class Friend { - }; - - void f1(); - inline void f2() {} - static const int KInt = 2; - const char* kStr = "123"; - - namespace { - void ff() {} // ignore - } - - void f1() {} - - namespace foo { - // Type alias - typedef int int32; - using int32_t = int32; - - // Variable - int v1; - - // Namespace - namespace bar { - int v2; - } - // Namespace alias - namespace baz = bar; - - using bar::v2; - } // namespace foo - )"; - runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, - UnorderedElementsAreArray( - {AllOf(QName("Foo"), ForCodeCompletion(true)), - AllOf(QName("Foo::Foo"), ForCodeCompletion(false)), - AllOf(QName("Foo::Foo"), ForCodeCompletion(false)), - AllOf(QName("Foo::f"), ForCodeCompletion(false)), - AllOf(QName("Foo::~Foo"), ForCodeCompletion(false)), - AllOf(QName("Foo::operator="), ForCodeCompletion(false)), - AllOf(QName("Foo::Nested"), ForCodeCompletion(false)), - AllOf(QName("Foo::Nested::f"), ForCodeCompletion(false)), - - AllOf(QName("Friend"), ForCodeCompletion(true)), - AllOf(QName("f1"), ForCodeCompletion(true)), - AllOf(QName("f2"), ForCodeCompletion(true)), - AllOf(QName("KInt"), ForCodeCompletion(true)), - AllOf(QName("kStr"), ForCodeCompletion(true)), - AllOf(QName("foo"), ForCodeCompletion(true)), - AllOf(QName("foo::bar"), ForCodeCompletion(true)), - AllOf(QName("foo::int32"), ForCodeCompletion(true)), - AllOf(QName("foo::int32_t"), ForCodeCompletion(true)), - AllOf(QName("foo::v1"), ForCodeCompletion(true)), - AllOf(QName("foo::bar::v2"), ForCodeCompletion(true)), - AllOf(QName("foo::v2"), ForCodeCompletion(true)), - AllOf(QName("foo::baz"), ForCodeCompletion(true))})); -} - -TEST_F(SymbolCollectorTest, FileLocal) { - const std::string Header = R"( - class Foo {}; - namespace { - class Ignored {}; - } - void bar(); - )"; - const std::string Main = R"( - class ForwardDecl; - void bar() {} - static void a(); - class B {}; - namespace { - void c(); - } - )"; - runSymbolCollector(Header, Main); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - AllOf(QName("Foo"), VisibleOutsideFile()), - AllOf(QName("bar"), VisibleOutsideFile()), - AllOf(QName("a"), Not(VisibleOutsideFile())), - AllOf(QName("B"), Not(VisibleOutsideFile())), - AllOf(QName("c"), Not(VisibleOutsideFile())), - // FIXME: ForwardDecl likely *is* visible outside. - AllOf(QName("ForwardDecl"), Not(VisibleOutsideFile())))); -} - -TEST_F(SymbolCollectorTest, Template) { - Annotations Header(R"( - // Primary template and explicit specialization are indexed, instantiation - // is not. - template <class T, class U> struct [[Tmpl]] {T $xdecl[[x]] = 0;}; - template <> struct $specdecl[[Tmpl]]<int, bool> {}; - template <class U> struct $partspecdecl[[Tmpl]]<bool, U> {}; - extern template struct Tmpl<float, bool>; - template struct Tmpl<double, bool>; - )"); - runSymbolCollector(Header.code(), /*Main=*/""); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - AllOf(QName("Tmpl"), DeclRange(Header.range()), - ForCodeCompletion(true)), - AllOf(QName("Tmpl"), DeclRange(Header.range("specdecl")), - ForCodeCompletion(false)), - AllOf(QName("Tmpl"), DeclRange(Header.range("partspecdecl")), - ForCodeCompletion(false)), - AllOf(QName("Tmpl::x"), DeclRange(Header.range("xdecl")), - ForCodeCompletion(false)))); -} - -TEST_F(SymbolCollectorTest, TemplateArgs) { - Annotations Header(R"( - template <class X> class $barclasstemp[[Bar]] {}; - template <class T, class U, template<typename> class Z, int Q> - struct [[Tmpl]] { T $xdecl[[x]] = 0; }; - - // template-template, non-type and type full spec - template <> struct $specdecl[[Tmpl]]<int, bool, Bar, 3> {}; - - // template-template, non-type and type partial spec - template <class U, int T> struct $partspecdecl[[Tmpl]]<bool, U, Bar, T> {}; - // instantiation - extern template struct Tmpl<float, bool, Bar, 8>; - // instantiation - template struct Tmpl<double, bool, Bar, 2>; - - template <typename ...> class $fooclasstemp[[Foo]] {}; - // parameter-packs full spec - template<> class $parampack[[Foo]]<Bar<int>, int, double> {}; - // parameter-packs partial spec - template<class T> class $parampackpartial[[Foo]]<T, T> {}; - - template <int ...> class $bazclasstemp[[Baz]] {}; - // non-type parameter-packs full spec - template<> class $parampacknontype[[Baz]]<3, 5, 8> {}; - // non-type parameter-packs partial spec - template<int T> class $parampacknontypepartial[[Baz]]<T, T> {}; - - template <template <class> class ...> class $fozclasstemp[[Foz]] {}; - // template-template parameter-packs full spec - template<> class $parampacktempltempl[[Foz]]<Bar, Bar> {}; - // template-template parameter-packs partial spec - template<template <class> class T> - class $parampacktempltemplpartial[[Foz]]<T, T> {}; - )"); - runSymbolCollector(Header.code(), /*Main=*/""); - EXPECT_THAT( - Symbols, - AllOf( - Contains(AllOf(QName("Tmpl"), TemplateArgs("<int, bool, Bar, 3>"), - DeclRange(Header.range("specdecl")), - ForCodeCompletion(false))), - Contains(AllOf(QName("Tmpl"), TemplateArgs("<bool, U, Bar, T>"), - DeclRange(Header.range("partspecdecl")), - ForCodeCompletion(false))), - Contains(AllOf(QName("Foo"), TemplateArgs("<Bar<int>, int, double>"), - DeclRange(Header.range("parampack")), - ForCodeCompletion(false))), - Contains(AllOf(QName("Foo"), TemplateArgs("<T, T>"), - DeclRange(Header.range("parampackpartial")), - ForCodeCompletion(false))), - Contains(AllOf(QName("Baz"), TemplateArgs("<3, 5, 8>"), - DeclRange(Header.range("parampacknontype")), - ForCodeCompletion(false))), - Contains(AllOf(QName("Baz"), TemplateArgs("<T, T>"), - DeclRange(Header.range("parampacknontypepartial")), - ForCodeCompletion(false))), - Contains(AllOf(QName("Foz"), TemplateArgs("<Bar, Bar>"), - DeclRange(Header.range("parampacktempltempl")), - ForCodeCompletion(false))), - Contains(AllOf(QName("Foz"), TemplateArgs("<T, T>"), - DeclRange(Header.range("parampacktempltemplpartial")), - ForCodeCompletion(false))))); -} - -TEST_F(SymbolCollectorTest, ObjCSymbols) { - const std::string Header = R"( - @interface Person - - (void)someMethodName:(void*)name1 lastName:(void*)lName; - @end - - @implementation Person - - (void)someMethodName:(void*)name1 lastName:(void*)lName{ - int foo; - ^(int param){ int bar; }; - } - @end - - @interface Person (MyCategory) - - (void)someMethodName2:(void*)name2; - @end - - @implementation Person (MyCategory) - - (void)someMethodName2:(void*)name2 { - int foo2; - } - @end - - @protocol MyProtocol - - (void)someMethodName3:(void*)name3; - @end - )"; - TestFileName = testPath("test.m"); - runSymbolCollector(Header, /*Main=*/"", {"-fblocks", "-xobjective-c++"}); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - QName("Person"), QName("Person::someMethodName:lastName:"), - QName("MyCategory"), QName("Person::someMethodName2:"), - QName("MyProtocol"), QName("MyProtocol::someMethodName3:"))); -} - -TEST_F(SymbolCollectorTest, ObjCPropertyImpl) { - const std::string Header = R"( - @interface Container - @property(nonatomic) int magic; - @end - - @implementation Container - @end - )"; - TestFileName = testPath("test.m"); - runSymbolCollector(Header, /*Main=*/"", {"-xobjective-c++"}); - EXPECT_THAT(Symbols, Contains(QName("Container"))); - EXPECT_THAT(Symbols, Contains(QName("Container::magic"))); - // FIXME: Results also contain Container::_magic on some platforms. - // Figure out why it's platform-dependent. -} - -TEST_F(SymbolCollectorTest, Locations) { - Annotations Header(R"cpp( - // Declared in header, defined in main. - extern int $xdecl[[X]]; - class $clsdecl[[Cls]]; - void $printdecl[[print]](); - - // Declared in header, defined nowhere. - extern int $zdecl[[Z]]; - - void $foodecl[[fo\ -o]](); - )cpp"); - Annotations Main(R"cpp( - int $xdef[[X]] = 42; - class $clsdef[[Cls]] {}; - void $printdef[[print]]() {} - - // Declared/defined in main only. - int $ydecl[[Y]]; - )cpp"); - runSymbolCollector(Header.code(), Main.code()); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - AllOf(QName("X"), DeclRange(Header.range("xdecl")), - DefRange(Main.range("xdef"))), - AllOf(QName("Cls"), DeclRange(Header.range("clsdecl")), - DefRange(Main.range("clsdef"))), - AllOf(QName("print"), DeclRange(Header.range("printdecl")), - DefRange(Main.range("printdef"))), - AllOf(QName("Z"), DeclRange(Header.range("zdecl"))), - AllOf(QName("foo"), DeclRange(Header.range("foodecl"))), - AllOf(QName("Y"), DeclRange(Main.range("ydecl"))))); -} - -TEST_F(SymbolCollectorTest, Refs) { - Annotations Header(R"( - class $foo[[Foo]] { - public: - $foo[[Foo]]() {} - $foo[[Foo]](int); - }; - class $bar[[Bar]]; - void $func[[func]](); - - namespace $ns[[NS]] {} // namespace ref is ignored - )"); - Annotations Main(R"( - class $bar[[Bar]] {}; - - void $func[[func]](); - - void fff() { - $foo[[Foo]] foo; - $bar[[Bar]] bar; - $func[[func]](); - int abc = 0; - $foo[[Foo]] foo2 = abc; - } - )"); - Annotations SymbolsOnlyInMainCode(R"( - int a; - void b() {} - static const int c = 0; - class d {}; - )"); - CollectorOpts.RefFilter = RefKind::All; - runSymbolCollector(Header.code(), - (Main.code() + SymbolsOnlyInMainCode.code()).str()); - auto HeaderSymbols = TestTU::withHeaderCode(Header.code()).headerSymbols(); - - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo").ID, - HaveRanges(Main.ranges("foo"))))); - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Bar").ID, - HaveRanges(Main.ranges("bar"))))); - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "func").ID, - HaveRanges(Main.ranges("func"))))); - EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(Symbols, "NS").ID, _)))); - // Symbols *only* in the main file (a, b, c) had no refs collected. - auto MainSymbols = - TestTU::withHeaderCode(SymbolsOnlyInMainCode.code()).headerSymbols(); - EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(MainSymbols, "a").ID, _)))); - EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(MainSymbols, "b").ID, _)))); - EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(MainSymbols, "c").ID, _)))); -} - -TEST_F(SymbolCollectorTest, RefsInHeaders) { - CollectorOpts.RefFilter = RefKind::All; - CollectorOpts.RefsInHeaders = true; - Annotations Header(R"( - class [[Foo]] {}; - )"); - runSymbolCollector(Header.code(), ""); - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo").ID, - HaveRanges(Header.ranges())))); -} - -TEST_F(SymbolCollectorTest, References) { - const std::string Header = R"( - class W; - class X {}; - class Y; - class Z {}; // not used anywhere - Y* y = nullptr; // used in header doesn't count - #define GLOBAL_Z(name) Z name; - )"; - const std::string Main = R"( - W* w = nullptr; - W* w2 = nullptr; // only one usage counts - X x(); - class V; - class Y{}; // definition doesn't count as a reference - V* v = nullptr; - GLOBAL_Z(z); // Not a reference to Z, we don't spell the type. - )"; - CollectorOpts.CountReferences = true; - runSymbolCollector(Header, Main); - EXPECT_THAT(Symbols, - UnorderedElementsAreArray( - {AllOf(QName("W"), RefCount(1)), - AllOf(QName("X"), RefCount(1)), - AllOf(QName("Y"), RefCount(0)), - AllOf(QName("Z"), RefCount(0)), - AllOf(QName("y"), RefCount(0)), - AllOf(QName("z"), RefCount(0)), - AllOf(QName("x"), RefCount(0)), - AllOf(QName("w"), RefCount(0)), - AllOf(QName("w2"), RefCount(0)), - AllOf(QName("V"), RefCount(1)), - AllOf(QName("v"), RefCount(0))})); -} - -TEST_F(SymbolCollectorTest, SymbolRelativeNoFallback) { - runSymbolCollector("class Foo {};", /*Main=*/""); - EXPECT_THAT(Symbols, UnorderedElementsAre( - AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); -} - -TEST_F(SymbolCollectorTest, SymbolRelativeWithFallback) { - TestHeaderName = "x.h"; - TestFileName = "x.cpp"; - TestHeaderURI = URI::create(testPath(TestHeaderName)).toString(); - CollectorOpts.FallbackDir = testRoot(); - runSymbolCollector("class Foo {};", /*Main=*/""); - EXPECT_THAT(Symbols, UnorderedElementsAre( - AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); -} - -TEST_F(SymbolCollectorTest, UnittestURIScheme) { - // Use test URI scheme from URITests.cpp - TestHeaderName = testPath("x.h"); - TestFileName = testPath("x.cpp"); - runSymbolCollector("class Foo {};", /*Main=*/""); - EXPECT_THAT(Symbols, UnorderedElementsAre( - AllOf(QName("Foo"), DeclURI("unittest:///x.h")))); -} - -TEST_F(SymbolCollectorTest, IncludeEnums) { - const std::string Header = R"( - enum { - Red - }; - enum Color { - Green - }; - enum class Color2 { - Yellow - }; - namespace ns { - enum { - Black - }; - } - )"; - runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - AllOf(QName("Red"), ForCodeCompletion(true)), - AllOf(QName("Color"), ForCodeCompletion(true)), - AllOf(QName("Green"), ForCodeCompletion(true)), - AllOf(QName("Color2"), ForCodeCompletion(true)), - AllOf(QName("Color2::Yellow"), ForCodeCompletion(false)), - AllOf(QName("ns"), ForCodeCompletion(true)), - AllOf(QName("ns::Black"), ForCodeCompletion(true)))); -} - -TEST_F(SymbolCollectorTest, NamelessSymbols) { - const std::string Header = R"( - struct { - int a; - } Foo; - )"; - runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Foo"), - QName("(anonymous struct)::a"))); -} - -TEST_F(SymbolCollectorTest, SymbolFormedFromRegisteredSchemeFromMacro) { - - Annotations Header(R"( - #define FF(name) \ - class name##_Test {}; - - $expansion[[FF]](abc); - - #define FF2() \ - class $spelling[[Test]] {}; - - FF2(); - )"); - - runSymbolCollector(Header.code(), /*Main=*/""); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - AllOf(QName("abc_Test"), DeclRange(Header.range("expansion")), - DeclURI(TestHeaderURI)), - AllOf(QName("Test"), DeclRange(Header.range("spelling")), - DeclURI(TestHeaderURI)))); -} - -TEST_F(SymbolCollectorTest, SymbolFormedByCLI) { - Annotations Header(R"( - #ifdef NAME - class $expansion[[NAME]] {}; - #endif - )"); - runSymbolCollector(Header.code(), /*Main=*/"", /*ExtraArgs=*/{"-DNAME=name"}); - EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( - QName("name"), DeclRange(Header.range("expansion")), - DeclURI(TestHeaderURI)))); -} - -TEST_F(SymbolCollectorTest, SymbolsInMainFile) { - const std::string Main = R"( - class Foo {}; - void f1(); - inline void f2() {} - - namespace { - void ff() {} - } - namespace foo { - namespace { - class Bar {}; - } - } - void main_f() {} - void f1() {} - )"; - runSymbolCollector(/*Header=*/"", Main); - EXPECT_THAT(Symbols, - UnorderedElementsAre(QName("Foo"), QName("f1"), QName("f2"), - QName("ff"), QName("foo"), QName("foo::Bar"), - QName("main_f"))); -} - -TEST_F(SymbolCollectorTest, Documentation) { - const std::string Header = R"( - // Doc Foo - class Foo { - // Doc f - int f(); - }; - )"; - CollectorOpts.StoreAllDocumentation = false; - runSymbolCollector(Header, /* Main */ ""); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - AllOf(QName("Foo"), Doc("Doc Foo"), ForCodeCompletion(true)), - AllOf(QName("Foo::f"), Doc(""), ReturnType(""), - ForCodeCompletion(false)))); - - CollectorOpts.StoreAllDocumentation = true; - runSymbolCollector(Header, /* Main */ ""); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - AllOf(QName("Foo"), Doc("Doc Foo"), ForCodeCompletion(true)), - AllOf(QName("Foo::f"), Doc("Doc f"), ReturnType(""), - ForCodeCompletion(false)))); -} - -TEST_F(SymbolCollectorTest, ClassMembers) { - const std::string Header = R"( - class Foo { - void f() {} - void g(); - static void sf() {} - static void ssf(); - static int x; - }; - )"; - const std::string Main = R"( - void Foo::g() {} - void Foo::ssf() {} - )"; - runSymbolCollector(Header, Main); - EXPECT_THAT( - Symbols, - UnorderedElementsAre( - QName("Foo"), - AllOf(QName("Foo::f"), ReturnType(""), ForCodeCompletion(false)), - AllOf(QName("Foo::g"), ReturnType(""), ForCodeCompletion(false)), - AllOf(QName("Foo::sf"), ReturnType(""), ForCodeCompletion(false)), - AllOf(QName("Foo::ssf"), ReturnType(""), ForCodeCompletion(false)), - AllOf(QName("Foo::x"), ReturnType(""), ForCodeCompletion(false)))); -} - -TEST_F(SymbolCollectorTest, Scopes) { - const std::string Header = R"( - namespace na { - class Foo {}; - namespace nb { - class Bar {}; - } - } - )"; - runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, - UnorderedElementsAre(QName("na"), QName("na::nb"), - QName("na::Foo"), QName("na::nb::Bar"))); -} - -TEST_F(SymbolCollectorTest, ExternC) { - const std::string Header = R"( - extern "C" { class Foo {}; } - namespace na { - extern "C" { class Bar {}; } - } - )"; - runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, UnorderedElementsAre(QName("na"), QName("Foo"), - QName("na::Bar"))); -} - -TEST_F(SymbolCollectorTest, SkipInlineNamespace) { - const std::string Header = R"( - namespace na { - inline namespace nb { - class Foo {}; - } - } - namespace na { - // This is still inlined. - namespace nb { - class Bar {}; - } - } - )"; - runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, - UnorderedElementsAre(QName("na"), QName("na::nb"), - QName("na::Foo"), QName("na::Bar"))); -} - -TEST_F(SymbolCollectorTest, SymbolWithDocumentation) { - const std::string Header = R"( - namespace nx { - /// Foo comment. - int ff(int x, double y) { return 0; } - } - )"; - runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT( - Symbols, - UnorderedElementsAre( - QName("nx"), AllOf(QName("nx::ff"), Labeled("ff(int x, double y)"), - ReturnType("int"), Doc("Foo comment.")))); -} - -TEST_F(SymbolCollectorTest, Snippet) { - const std::string Header = R"( - namespace nx { - void f() {} - int ff(int x, double y) { return 0; } - } - )"; - runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - QName("nx"), - AllOf(QName("nx::f"), Labeled("f()"), Snippet("f()")), - AllOf(QName("nx::ff"), Labeled("ff(int x, double y)"), - Snippet("ff(${1:int x}, ${2:double y})")))); -} - -TEST_F(SymbolCollectorTest, IncludeHeaderSameAsFileURI) { - CollectorOpts.CollectIncludePath = true; - runSymbolCollector("#pragma once\nclass Foo {};", /*Main=*/""); - EXPECT_THAT(Symbols, UnorderedElementsAre( - AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); - EXPECT_THAT(Symbols.begin()->IncludeHeaders, - UnorderedElementsAre(IncludeHeaderWithRef(TestHeaderURI, 1u))); -} - -#ifndef _WIN32 -TEST_F(SymbolCollectorTest, CanonicalSTLHeader) { - CollectorOpts.CollectIncludePath = true; - CanonicalIncludes Includes; - addSystemHeadersMapping(&Includes); - CollectorOpts.Includes = &Includes; - // bits/basic_string.h$ should be mapped to <string> - TestHeaderName = "/nasty/bits/basic_string.h"; - TestFileName = "/nasty/bits/basic_string.cpp"; - TestHeaderURI = URI::create(TestHeaderName).toString(); - runSymbolCollector("class string {};", /*Main=*/""); - EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("string"), - DeclURI(TestHeaderURI), - IncludeHeader("<string>")))); -} -#endif - -TEST_F(SymbolCollectorTest, STLiosfwd) { - CollectorOpts.CollectIncludePath = true; - CanonicalIncludes Includes; - addSystemHeadersMapping(&Includes); - CollectorOpts.Includes = &Includes; - // Symbols from <iosfwd> should be mapped individually. - TestHeaderName = testPath("iosfwd"); - TestFileName = testPath("iosfwd.cpp"); - std::string Header = R"( - namespace std { - class no_map {}; - class ios {}; - class ostream {}; - class filebuf {}; - } // namespace std - )"; - runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - QName("std"), - AllOf(QName("std::no_map"), IncludeHeader("<iosfwd>")), - AllOf(QName("std::ios"), IncludeHeader("<ios>")), - AllOf(QName("std::ostream"), IncludeHeader("<ostream>")), - AllOf(QName("std::filebuf"), IncludeHeader("<fstream>")))); -} - -TEST_F(SymbolCollectorTest, IWYUPragma) { - CollectorOpts.CollectIncludePath = true; - CanonicalIncludes Includes; - PragmaHandler = collectIWYUHeaderMaps(&Includes); - CollectorOpts.Includes = &Includes; - const std::string Header = R"( - // IWYU pragma: private, include the/good/header.h - class Foo {}; - )"; - runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, UnorderedElementsAre( - AllOf(QName("Foo"), DeclURI(TestHeaderURI), - IncludeHeader("\"the/good/header.h\"")))); -} - -TEST_F(SymbolCollectorTest, IWYUPragmaWithDoubleQuotes) { - CollectorOpts.CollectIncludePath = true; - CanonicalIncludes Includes; - PragmaHandler = collectIWYUHeaderMaps(&Includes); - CollectorOpts.Includes = &Includes; - const std::string Header = R"( - // IWYU pragma: private, include "the/good/header.h" - class Foo {}; - )"; - runSymbolCollector(Header, /*Main=*/""); - EXPECT_THAT(Symbols, UnorderedElementsAre( - AllOf(QName("Foo"), DeclURI(TestHeaderURI), - IncludeHeader("\"the/good/header.h\"")))); -} - -TEST_F(SymbolCollectorTest, SkipIncFileWhenCanonicalizeHeaders) { - CollectorOpts.CollectIncludePath = true; - CanonicalIncludes Includes; - Includes.addMapping(TestHeaderName, "<canonical>"); - CollectorOpts.Includes = &Includes; - auto IncFile = testPath("test.inc"); - auto IncURI = URI::create(IncFile).toString(); - InMemoryFileSystem->addFile(IncFile, 0, - llvm::MemoryBuffer::getMemBuffer("class X {};")); - runSymbolCollector("#include \"test.inc\"\nclass Y {};", /*Main=*/"", - /*ExtraArgs=*/{"-I", testRoot()}); - EXPECT_THAT(Symbols, - UnorderedElementsAre(AllOf(QName("X"), DeclURI(IncURI), - IncludeHeader("<canonical>")), - AllOf(QName("Y"), DeclURI(TestHeaderURI), - IncludeHeader("<canonical>")))); -} - -TEST_F(SymbolCollectorTest, MainFileIsHeaderWhenSkipIncFile) { - CollectorOpts.CollectIncludePath = true; - // To make this case as hard as possible, we won't tell clang main is a - // header. No extension, no -x c++-header. - TestFileName = testPath("no_ext_main"); - TestFileURI = URI::create(TestFileName).toString(); - auto IncFile = testPath("test.inc"); - auto IncURI = URI::create(IncFile).toString(); - InMemoryFileSystem->addFile(IncFile, 0, - llvm::MemoryBuffer::getMemBuffer("class X {};")); - runSymbolCollector("", R"cpp( - // Can't use #pragma once in a main file clang doesn't think is a header. - #ifndef MAIN_H_ - #define MAIN_H_ - #include "test.inc" - #endif - )cpp", - /*ExtraArgs=*/{"-I", testRoot()}); - EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), DeclURI(IncURI), - IncludeHeader(TestFileURI)))); -} - -TEST_F(SymbolCollectorTest, IncFileInNonHeader) { - CollectorOpts.CollectIncludePath = true; - TestFileName = testPath("main.cc"); - TestFileURI = URI::create(TestFileName).toString(); - auto IncFile = testPath("test.inc"); - auto IncURI = URI::create(IncFile).toString(); - InMemoryFileSystem->addFile(IncFile, 0, - llvm::MemoryBuffer::getMemBuffer("class X {};")); - runSymbolCollector("", R"cpp( - #include "test.inc" - )cpp", - /*ExtraArgs=*/{"-I", testRoot()}); - EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), DeclURI(IncURI), - Not(IncludeHeader())))); -} - -TEST_F(SymbolCollectorTest, NonModularHeader) { - auto TU = TestTU::withHeaderCode("int x();"); - EXPECT_THAT(TU.headerSymbols(), ElementsAre(IncludeHeader())); - - // Files missing include guards aren't eligible for insertion. - TU.ImplicitHeaderGuard = false; - EXPECT_THAT(TU.headerSymbols(), ElementsAre(Not(IncludeHeader()))); - - // We recognize some patterns of trying to prevent insertion. - TU = TestTU::withHeaderCode(R"cpp( -#ifndef SECRET -#error "This file isn't safe to include directly" -#endif - int x(); - )cpp"); - TU.ExtraArgs.push_back("-DSECRET"); // *we're* able to include it. - EXPECT_THAT(TU.headerSymbols(), ElementsAre(Not(IncludeHeader()))); -} - -TEST_F(SymbolCollectorTest, AvoidUsingFwdDeclsAsCanonicalDecls) { - CollectorOpts.CollectIncludePath = true; - Annotations Header(R"( - #pragma once - // Forward declarations of TagDecls. - class C; - struct S; - union U; - - // Canonical declarations. - class $cdecl[[C]] {}; - struct $sdecl[[S]] {}; - union $udecl[[U]] {int $xdecl[[x]]; bool $ydecl[[y]];}; - )"); - runSymbolCollector(Header.code(), /*Main=*/""); - EXPECT_THAT( - Symbols, - UnorderedElementsAre( - AllOf(QName("C"), DeclURI(TestHeaderURI), - DeclRange(Header.range("cdecl")), IncludeHeader(TestHeaderURI), - DefURI(TestHeaderURI), DefRange(Header.range("cdecl"))), - AllOf(QName("S"), DeclURI(TestHeaderURI), - DeclRange(Header.range("sdecl")), IncludeHeader(TestHeaderURI), - DefURI(TestHeaderURI), DefRange(Header.range("sdecl"))), - AllOf(QName("U"), DeclURI(TestHeaderURI), - DeclRange(Header.range("udecl")), IncludeHeader(TestHeaderURI), - DefURI(TestHeaderURI), DefRange(Header.range("udecl"))), - AllOf(QName("U::x"), DeclURI(TestHeaderURI), - DeclRange(Header.range("xdecl")), DefURI(TestHeaderURI), - DefRange(Header.range("xdecl"))), - AllOf(QName("U::y"), DeclURI(TestHeaderURI), - DeclRange(Header.range("ydecl")), DefURI(TestHeaderURI), - DefRange(Header.range("ydecl"))))); -} - -TEST_F(SymbolCollectorTest, ClassForwardDeclarationIsCanonical) { - CollectorOpts.CollectIncludePath = true; - runSymbolCollector(/*Header=*/"#pragma once\nclass X;", - /*Main=*/"class X {};"); - EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( - QName("X"), DeclURI(TestHeaderURI), - IncludeHeader(TestHeaderURI), DefURI(TestFileURI)))); -} - -TEST_F(SymbolCollectorTest, UTF16Character) { - // ö is 2-bytes. - Annotations Header(/*Header=*/"class [[pörk]] {};"); - runSymbolCollector(Header.code(), /*Main=*/""); - EXPECT_THAT(Symbols, UnorderedElementsAre( - AllOf(QName("pörk"), DeclRange(Header.range())))); -} - -TEST_F(SymbolCollectorTest, DoNotIndexSymbolsInFriendDecl) { - Annotations Header(R"( - namespace nx { - class $z[[Z]] {}; - class X { - friend class Y; - friend class Z; - friend void foo(); - friend void $bar[[bar]]() {} - }; - class $y[[Y]] {}; - void $foo[[foo]](); - } - )"); - runSymbolCollector(Header.code(), /*Main=*/""); - - EXPECT_THAT(Symbols, - UnorderedElementsAre( - QName("nx"), QName("nx::X"), - AllOf(QName("nx::Y"), DeclRange(Header.range("y"))), - AllOf(QName("nx::Z"), DeclRange(Header.range("z"))), - AllOf(QName("nx::foo"), DeclRange(Header.range("foo"))), - AllOf(QName("nx::bar"), DeclRange(Header.range("bar"))))); -} - -TEST_F(SymbolCollectorTest, ReferencesInFriendDecl) { - const std::string Header = R"( - class X; - class Y; - )"; - const std::string Main = R"( - class C { - friend ::X; - friend class Y; - }; - )"; - CollectorOpts.CountReferences = true; - runSymbolCollector(Header, Main); - EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), RefCount(1)), - AllOf(QName("Y"), RefCount(1)), - AllOf(QName("C"), RefCount(0)))); -} - -TEST_F(SymbolCollectorTest, Origin) { - CollectorOpts.Origin = SymbolOrigin::Static; - runSymbolCollector("class Foo {};", /*Main=*/""); - EXPECT_THAT(Symbols, UnorderedElementsAre( - Field(&Symbol::Origin, SymbolOrigin::Static))); -} - -TEST_F(SymbolCollectorTest, CollectMacros) { - CollectorOpts.CollectIncludePath = true; - Annotations Header(R"( - #pragma once - #define X 1 - #define $mac[[MAC]](x) int x - #define $used[[USED]](y) float y; - - MAC(p); - )"); - - Annotations Main(R"( - #define $main[[MAIN]] 1 - USED(t); - )"); - CollectorOpts.CountReferences = true; - CollectorOpts.CollectMacro = true; - runSymbolCollector(Header.code(), Main.code()); - EXPECT_THAT( - Symbols, - UnorderedElementsAre( - QName("p"), QName("t"), - AllOf(QName("X"), DeclURI(TestHeaderURI), - IncludeHeader(TestHeaderURI)), - AllOf(Labeled("MAC(x)"), RefCount(0), - - DeclRange(Header.range("mac")), VisibleOutsideFile()), - AllOf(Labeled("USED(y)"), RefCount(1), - DeclRange(Header.range("used")), VisibleOutsideFile()), - AllOf(Labeled("MAIN"), RefCount(0), DeclRange(Main.range("main")), - Not(VisibleOutsideFile())))); -} - -TEST_F(SymbolCollectorTest, DeprecatedSymbols) { - const std::string Header = R"( - void TestClangc() __attribute__((deprecated("", ""))); - void TestClangd(); - )"; - runSymbolCollector(Header, /**/ ""); - EXPECT_THAT(Symbols, UnorderedElementsAre( - AllOf(QName("TestClangc"), Deprecated()), - AllOf(QName("TestClangd"), Not(Deprecated())))); -} - -TEST_F(SymbolCollectorTest, ImplementationDetail) { - const std::string Header = R"( - #define DECL_NAME(x, y) x##_##y##_Decl - #define DECL(x, y) class DECL_NAME(x, y) {}; - DECL(X, Y); // X_Y_Decl - - class Public {}; - )"; - runSymbolCollector(Header, /**/ ""); - EXPECT_THAT(Symbols, - UnorderedElementsAre( - AllOf(QName("X_Y_Decl"), ImplementationDetail()), - AllOf(QName("Public"), Not(ImplementationDetail())))); -} - -TEST_F(SymbolCollectorTest, UsingDecl) { - const char *Header = R"( - void foo(); - namespace std { - using ::foo; - })"; - runSymbolCollector(Header, /**/ ""); - EXPECT_THAT(Symbols, Contains(QName("std::foo"))); -} - -TEST_F(SymbolCollectorTest, CBuiltins) { - // In C, printf in stdio.h is a redecl of an implicit builtin. - const char *Header = R"( - extern int printf(const char*, ...); - )"; - runSymbolCollector(Header, /**/ "", {"-xc"}); - EXPECT_THAT(Symbols, Contains(QName("printf"))); -} - -TEST_F(SymbolCollectorTest, InvalidSourceLoc) { - const char *Header = R"( - void operator delete(void*) - __attribute__((__externally_visible__));)"; - runSymbolCollector(Header, /**/ ""); - EXPECT_THAT(Symbols, Contains(QName("operator delete"))); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/SymbolInfoTests.cpp b/clang-tools-extra/unittests/clangd/SymbolInfoTests.cpp deleted file mode 100644 index e0a9ecdc1f8..00000000000 --- a/clang-tools-extra/unittests/clangd/SymbolInfoTests.cpp +++ /dev/null @@ -1,339 +0,0 @@ -//===-- SymbolInfoTests.cpp -----------------------*- C++ -*--------------===// -// -// 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 "Annotations.h" -#include "ClangdUnit.h" -#include "Compiler.h" -#include "Matchers.h" -#include "SyncAPI.h" -#include "TestFS.h" -#include "TestTU.h" -#include "XRefs.h" -#include "index/FileIndex.h" -#include "index/SymbolCollector.h" -#include "clang/Index/IndexingAction.h" -#include "llvm/Support/Path.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -using testing::ElementsAreArray; - -auto CreateExpectedSymbolDetails = [](const std::string &name, - const std::string &container, - const std::string &USR) { - return SymbolDetails{name, container, USR, SymbolID(USR)}; -}; - -TEST(SymbolInfoTests, All) { - std::pair<const char *, std::vector<SymbolDetails>> - TestInputExpectedOutput[] = { - { - R"cpp( // Simple function reference - declaration - void foo(); - int bar() { - fo^o(); - } - )cpp", - {CreateExpectedSymbolDetails("foo", "", "c:@F@foo#")}}, - { - R"cpp( // Simple function reference - definition - void foo() {} - int bar() { - fo^o(); - } - )cpp", - {CreateExpectedSymbolDetails("foo", "", "c:@F@foo#")}}, - { - R"cpp( // Function in namespace reference - namespace bar { - void foo(); - int baz() { - fo^o(); - } - } - )cpp", - {CreateExpectedSymbolDetails("foo", "bar::", "c:@N@bar@F@foo#")}}, - { - R"cpp( // Function in different namespace reference - namespace bar { - void foo(); - } - namespace barbar { - int baz() { - bar::fo^o(); - } - } - )cpp", - {CreateExpectedSymbolDetails("foo", "bar::", "c:@N@bar@F@foo#")}}, - { - R"cpp( // Function in global namespace reference - void foo(); - namespace Nbar { - namespace Nbaz { - int baz() { - ::fo^o(); - } - } - } - )cpp", - {CreateExpectedSymbolDetails("foo", "", "c:@F@foo#")}}, - { - R"cpp( // Function in anonymous namespace reference - namespace { - void foo(); - } - namespace barbar { - int baz() { - fo^o(); - } - } - )cpp", - {CreateExpectedSymbolDetails("foo", "(anonymous)", - "c:TestTU.cpp@aN@F@foo#")}}, - { - R"cpp( // Function reference - ADL - namespace bar { - struct BarType {}; - void foo(const BarType&); - } - namespace barbar { - int baz() { - bar::BarType b; - fo^o(b); - } - } - )cpp", - {CreateExpectedSymbolDetails( - "foo", "bar::", "c:@N@bar@F@foo#&1$@N@bar@S@BarType#")}}, - { - R"cpp( // Global value reference - int value; - void foo(int) { } - void bar() { - foo(val^ue); - } - )cpp", - {CreateExpectedSymbolDetails("value", "", "c:@value")}}, - { - R"cpp( // Local value reference - void foo() { int aaa; int bbb = aa^a; } - )cpp", - {CreateExpectedSymbolDetails("aaa", "foo", - "c:TestTU.cpp@49@F@foo#@aaa")}}, - { - R"cpp( // Function param - void bar(int aaa) { - int bbb = a^aa; - } - )cpp", - {CreateExpectedSymbolDetails("aaa", "bar", - "c:TestTU.cpp@38@F@bar#I#@aaa")}}, - { - R"cpp( // Lambda capture - int ii; - auto lam = [ii]() { - return i^i; - }; - )cpp", - {CreateExpectedSymbolDetails("ii", "", "c:@ii")}}, - { - R"cpp( // Macro reference - #define MACRO 5\nint i = MAC^RO; - )cpp", - {CreateExpectedSymbolDetails("MACRO", "", - "c:TestTU.cpp@38@macro@MACRO")}}, - { - R"cpp( // Macro reference - #define MACRO 5\nint i = MACRO^; - )cpp", - {CreateExpectedSymbolDetails("MACRO", "", - "c:TestTU.cpp@38@macro@MACRO")}}, - { - R"cpp( // Multiple symbols returned - using overloaded function name - void foo() {} - void foo(bool) {} - void foo(int) {} - namespace bar { - using ::fo^o; - } - )cpp", - {CreateExpectedSymbolDetails("foo", "", "c:@F@foo#"), - CreateExpectedSymbolDetails("foo", "", "c:@F@foo#b#"), - CreateExpectedSymbolDetails("foo", "", "c:@F@foo#I#"), - CreateExpectedSymbolDetails("foo", "bar::", "c:@N@bar@UD@foo")}}, - { - R"cpp( // Multiple symbols returned - implicit conversion - struct foo {}; - struct bar { - bar(const foo&) {} - }; - void func_baz1(bar) {} - void func_baz2() { - foo ff; - func_baz1(f^f); - } - )cpp", - {CreateExpectedSymbolDetails( - "ff", "func_baz2", "c:TestTU.cpp@218@F@func_baz2#@ff")}}, - { - R"cpp( // Type reference - declaration - struct foo; - void bar(fo^o*); - )cpp", - {CreateExpectedSymbolDetails("foo", "", "c:@S@foo")}}, - { - R"cpp( // Type reference - definition - struct foo {}; - void bar(fo^o*); - )cpp", - {CreateExpectedSymbolDetails("foo", "", "c:@S@foo")}}, - { - R"cpp( // Type Reference - template argumen - struct foo {}; - template<class T> struct bar {}; - void baz() { - bar<fo^o> b; - } - )cpp", - {CreateExpectedSymbolDetails("foo", "", "c:@S@foo")}}, - { - R"cpp( // Template parameter reference - type param - template<class TT> struct bar { - T^T t; - }; - )cpp", - {CreateExpectedSymbolDetails("TT", "bar::", "c:TestTU.cpp@65")}}, - { - R"cpp( // Template parameter reference - type param - template<int NN> struct bar { - int a = N^N; - }; - )cpp", - {CreateExpectedSymbolDetails("NN", "bar::", "c:TestTU.cpp@65")}}, - { - R"cpp( // Class member reference - objec - struct foo { - int aa; - }; - void bar() { - foo f; - f.a^a; - } - )cpp", - {CreateExpectedSymbolDetails("aa", "foo::", "c:@S@foo@FI@aa")}}, - { - R"cpp( // Class member reference - pointer - struct foo { - int aa; - }; - void bar() { - &foo::a^a; - } - )cpp", - {CreateExpectedSymbolDetails("aa", "foo::", "c:@S@foo@FI@aa")}}, - { - R"cpp( // Class method reference - objec - struct foo { - void aa() {} - }; - void bar() { - foo f; - f.a^a(); - } - )cpp", - {CreateExpectedSymbolDetails("aa", "foo::", "c:@S@foo@F@aa#")}}, - { - R"cpp( // Class method reference - pointer - struct foo { - void aa() {} - }; - void bar() { - &foo::a^a; - } - )cpp", - {CreateExpectedSymbolDetails("aa", "foo::", "c:@S@foo@F@aa#")}}, - { - R"cpp( // Typedef - typedef int foo; - void bar() { - fo^o a; - } - )cpp", - {CreateExpectedSymbolDetails("foo", "", "c:TestTU.cpp@T@foo")}}, - { - R"cpp( // Type alias - using foo = int; - void bar() { - fo^o a; - } - )cpp", - {CreateExpectedSymbolDetails("foo", "", "c:@foo")}}, - { - R"cpp( // Namespace reference - namespace foo {} - using namespace fo^o; - )cpp", - {CreateExpectedSymbolDetails("foo", "", "c:@N@foo")}}, - { - R"cpp( // Enum value reference - enum foo { bar, baz }; - void f() { - foo fff = ba^r; - } - )cpp", - {CreateExpectedSymbolDetails("bar", "foo", "c:@E@foo@bar")}}, - { - R"cpp( // Enum class value reference - enum class foo { bar, baz }; - void f() { - foo fff = foo::ba^r; - } - )cpp", - {CreateExpectedSymbolDetails("bar", "foo::", "c:@E@foo@bar")}}, - { - R"cpp( // Parameters in declarations - void foo(int ba^r); - )cpp", - {CreateExpectedSymbolDetails("bar", "foo", - "c:TestTU.cpp@50@F@foo#I#@bar")}}, - { - R"cpp( // Type inferrence with auto keyword - struct foo {}; - foo getfoo() { return foo{}; } - void f() { - au^to a = getfoo(); - } - )cpp", - {/* not implemented */}}, - { - R"cpp( // decltype - struct foo {}; - void f() { - foo f; - declt^ype(f); - } - )cpp", - {/* not implemented */}}, - }; - - for (const auto &T : TestInputExpectedOutput) { - Annotations TestInput(T.first); - auto AST = TestTU::withCode(TestInput.code()).build(); - - EXPECT_THAT(getSymbolInfo(AST, TestInput.point()), - ElementsAreArray(T.second)) - << T.first; - } -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/SyncAPI.cpp b/clang-tools-extra/unittests/clangd/SyncAPI.cpp deleted file mode 100644 index 102cecb55c7..00000000000 --- a/clang-tools-extra/unittests/clangd/SyncAPI.cpp +++ /dev/null @@ -1,151 +0,0 @@ -//===--- SyncAPI.cpp - Sync version of ClangdServer's API --------*- C++-*-===// -// -// 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 "SyncAPI.h" -#include "index/Index.h" - -namespace clang { -namespace clangd { - -void runAddDocument(ClangdServer &Server, PathRef File, - llvm::StringRef Contents, WantDiagnostics WantDiags) { - Server.addDocument(File, Contents, WantDiags); - if (!Server.blockUntilIdleForTest()) - llvm_unreachable("not idle after addDocument"); -} - -namespace { -/// A helper that waits for async callbacks to fire and exposes their result in -/// the output variable. Intended to be used in the following way: -/// T Result; -/// someAsyncFunc(Param1, Param2, /*Callback=*/capture(Result)); -template <typename T> struct CaptureProxy { - CaptureProxy(llvm::Optional<T> &Target) : Target(&Target) { - assert(!Target.hasValue()); - } - - CaptureProxy(const CaptureProxy &) = delete; - CaptureProxy &operator=(const CaptureProxy &) = delete; - // We need move ctor to return a value from the 'capture' helper. - CaptureProxy(CaptureProxy &&Other) : Target(Other.Target) { - Other.Target = nullptr; - } - CaptureProxy &operator=(CaptureProxy &&) = delete; - - operator llvm::unique_function<void(T)>() && { - assert(!Future.valid() && "conversion to callback called multiple times"); - Future = Promise.get_future(); - return Bind( - [](std::promise<std::shared_ptr<T>> Promise, T Value) { - Promise.set_value(std::make_shared<T>(std::move(Value))); - }, - std::move(Promise)); - } - - ~CaptureProxy() { - if (!Target) - return; - assert(Future.valid() && "conversion to callback was not called"); - assert(!Target->hasValue()); - Target->emplace(std::move(*Future.get())); - } - -private: - llvm::Optional<T> *Target; - // Using shared_ptr to workaround compilation errors with MSVC. - // MSVC only allows default-construcitble and copyable objects as future<> - // arguments. - std::promise<std::shared_ptr<T>> Promise; - std::future<std::shared_ptr<T>> Future; -}; - -template <typename T> CaptureProxy<T> capture(llvm::Optional<T> &Target) { - return CaptureProxy<T>(Target); -} -} // namespace - -llvm::Expected<CodeCompleteResult> -runCodeComplete(ClangdServer &Server, PathRef File, Position Pos, - clangd::CodeCompleteOptions Opts) { - llvm::Optional<llvm::Expected<CodeCompleteResult>> Result; - Server.codeComplete(File, Pos, Opts, capture(Result)); - return std::move(*Result); -} - -llvm::Expected<SignatureHelp> runSignatureHelp(ClangdServer &Server, - PathRef File, Position Pos) { - llvm::Optional<llvm::Expected<SignatureHelp>> Result; - Server.signatureHelp(File, Pos, capture(Result)); - return std::move(*Result); -} - -llvm::Expected<std::vector<LocatedSymbol>> -runLocateSymbolAt(ClangdServer &Server, PathRef File, Position Pos) { - llvm::Optional<llvm::Expected<std::vector<LocatedSymbol>>> Result; - Server.locateSymbolAt(File, Pos, capture(Result)); - return std::move(*Result); -} - -llvm::Expected<std::vector<DocumentHighlight>> -runFindDocumentHighlights(ClangdServer &Server, PathRef File, Position Pos) { - llvm::Optional<llvm::Expected<std::vector<DocumentHighlight>>> Result; - Server.findDocumentHighlights(File, Pos, capture(Result)); - return std::move(*Result); -} - -llvm::Expected<std::vector<TextEdit>> runRename(ClangdServer &Server, - PathRef File, Position Pos, - llvm::StringRef NewName) { - llvm::Optional<llvm::Expected<std::vector<TextEdit>>> Result; - Server.rename(File, Pos, NewName, capture(Result)); - return std::move(*Result); -} - -std::string runDumpAST(ClangdServer &Server, PathRef File) { - llvm::Optional<std::string> Result; - Server.dumpAST(File, capture(Result)); - return std::move(*Result); -} - -llvm::Expected<std::vector<SymbolInformation>> -runWorkspaceSymbols(ClangdServer &Server, llvm::StringRef Query, int Limit) { - llvm::Optional<llvm::Expected<std::vector<SymbolInformation>>> Result; - Server.workspaceSymbols(Query, Limit, capture(Result)); - return std::move(*Result); -} - -llvm::Expected<std::vector<DocumentSymbol>> -runDocumentSymbols(ClangdServer &Server, PathRef File) { - llvm::Optional<llvm::Expected<std::vector<DocumentSymbol>>> Result; - Server.documentSymbols(File, capture(Result)); - return std::move(*Result); -} - -SymbolSlab runFuzzyFind(const SymbolIndex &Index, llvm::StringRef Query) { - FuzzyFindRequest Req; - Req.Query = Query; - Req.AnyScope = true; - return runFuzzyFind(Index, Req); -} - -SymbolSlab runFuzzyFind(const SymbolIndex &Index, const FuzzyFindRequest &Req) { - SymbolSlab::Builder Builder; - Index.fuzzyFind(Req, [&](const Symbol &Sym) { Builder.insert(Sym); }); - return std::move(Builder).build(); -} - -RefSlab getRefs(const SymbolIndex &Index, SymbolID ID) { - RefsRequest Req; - Req.IDs = {ID}; - RefSlab::Builder Slab; - Index.refs(Req, [&](const Ref &S) { Slab.insert(ID, S); }); - return std::move(Slab).build(); -} - -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/SyncAPI.h b/clang-tools-extra/unittests/clangd/SyncAPI.h deleted file mode 100644 index c1416524153..00000000000 --- a/clang-tools-extra/unittests/clangd/SyncAPI.h +++ /dev/null @@ -1,59 +0,0 @@ -//===--- SyncAPI.h - Sync version of ClangdServer's API ----------*- C++-*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// -// -// This file contains synchronous versions of ClangdServer's async API. We -// deliberately don't expose the sync API outside tests to encourage using the -// async versions in clangd code. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_SYNCAPI_H -#define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_SYNCAPI_H - -#include "ClangdServer.h" -#include "index/Index.h" - -namespace clang { -namespace clangd { - -// Calls addDocument and then blockUntilIdleForTest. -void runAddDocument(ClangdServer &Server, PathRef File, StringRef Contents, - WantDiagnostics WantDiags = WantDiagnostics::Auto); - -llvm::Expected<CodeCompleteResult> -runCodeComplete(ClangdServer &Server, PathRef File, Position Pos, - clangd::CodeCompleteOptions Opts); - -llvm::Expected<SignatureHelp> runSignatureHelp(ClangdServer &Server, - PathRef File, Position Pos); - -llvm::Expected<std::vector<LocatedSymbol>> -runLocateSymbolAt(ClangdServer &Server, PathRef File, Position Pos); - -llvm::Expected<std::vector<DocumentHighlight>> -runFindDocumentHighlights(ClangdServer &Server, PathRef File, Position Pos); - -llvm::Expected<std::vector<TextEdit>> -runRename(ClangdServer &Server, PathRef File, Position Pos, StringRef NewName); - -std::string runDumpAST(ClangdServer &Server, PathRef File); - -llvm::Expected<std::vector<SymbolInformation>> -runWorkspaceSymbols(ClangdServer &Server, StringRef Query, int Limit); - -Expected<std::vector<DocumentSymbol>> runDocumentSymbols(ClangdServer &Server, - PathRef File); - -SymbolSlab runFuzzyFind(const SymbolIndex &Index, StringRef Query); -SymbolSlab runFuzzyFind(const SymbolIndex &Index, const FuzzyFindRequest &Req); -RefSlab getRefs(const SymbolIndex &Index, SymbolID ID); - -} // namespace clangd -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_SYNCAPI_H diff --git a/clang-tools-extra/unittests/clangd/TUSchedulerTests.cpp b/clang-tools-extra/unittests/clangd/TUSchedulerTests.cpp deleted file mode 100644 index a7d032ccc57..00000000000 --- a/clang-tools-extra/unittests/clangd/TUSchedulerTests.cpp +++ /dev/null @@ -1,710 +0,0 @@ -//===-- TUSchedulerTests.cpp ------------------------------------*- C++ -*-===// -// -// 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 "Annotations.h" -#include "Context.h" -#include "Matchers.h" -#include "TUScheduler.h" -#include "TestFS.h" -#include "llvm/ADT/ScopeExit.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include <algorithm> -#include <utility> - -namespace clang { -namespace clangd { -namespace { - -using ::testing::AnyOf; -using ::testing::Each; -using ::testing::ElementsAre; -using ::testing::Pointee; -using ::testing::UnorderedElementsAre; - -MATCHER_P2(TUState, State, ActionName, "") { - return arg.Action.S == State && arg.Action.Name == ActionName; -} - -class TUSchedulerTests : public ::testing::Test { -protected: - ParseInputs getInputs(PathRef File, std::string Contents) { - ParseInputs Inputs; - Inputs.CompileCommand = *CDB.getCompileCommand(File); - Inputs.FS = buildTestFS(Files, Timestamps); - Inputs.Contents = std::move(Contents); - Inputs.Opts = ParseOptions(); - return Inputs; - } - - void updateWithCallback(TUScheduler &S, PathRef File, - llvm::StringRef Contents, WantDiagnostics WD, - llvm::unique_function<void()> CB) { - WithContextValue Ctx(llvm::make_scope_exit(std::move(CB))); - S.update(File, getInputs(File, Contents), WD); - } - - static Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>> - DiagsCallbackKey; - - /// A diagnostics callback that should be passed to TUScheduler when it's used - /// in updateWithDiags. - static std::unique_ptr<ParsingCallbacks> captureDiags() { - class CaptureDiags : public ParsingCallbacks { - void onDiagnostics(PathRef File, std::vector<Diag> Diags) override { - auto D = Context::current().get(DiagsCallbackKey); - if (!D) - return; - const_cast<llvm::unique_function<void(PathRef, std::vector<Diag>)> &> ( - *D)(File, Diags); - } - }; - return llvm::make_unique<CaptureDiags>(); - } - - /// Schedule an update and call \p CB with the diagnostics it produces, if - /// any. The TUScheduler should be created with captureDiags as a - /// DiagsCallback for this to work. - void updateWithDiags(TUScheduler &S, PathRef File, ParseInputs Inputs, - WantDiagnostics WD, - llvm::unique_function<void(std::vector<Diag>)> CB) { - Path OrigFile = File.str(); - WithContextValue Ctx( - DiagsCallbackKey, - Bind( - [OrigFile](decltype(CB) CB, PathRef File, std::vector<Diag> Diags) { - assert(File == OrigFile); - CB(std::move(Diags)); - }, - std::move(CB))); - S.update(File, std::move(Inputs), WD); - } - - void updateWithDiags(TUScheduler &S, PathRef File, llvm::StringRef Contents, - WantDiagnostics WD, - llvm::unique_function<void(std::vector<Diag>)> CB) { - return updateWithDiags(S, File, getInputs(File, Contents), WD, - std::move(CB)); - } - - llvm::StringMap<std::string> Files; - llvm::StringMap<time_t> Timestamps; - MockCompilationDatabase CDB; -}; - -Key<llvm::unique_function<void(PathRef File, std::vector<Diag>)>> - TUSchedulerTests::DiagsCallbackKey; - -TEST_F(TUSchedulerTests, MissingFiles) { - TUScheduler S(CDB, getDefaultAsyncThreadsCount(), - /*StorePreamblesInMemory=*/true, /*ASTCallbacks=*/nullptr, - /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(), - ASTRetentionPolicy()); - - auto Added = testPath("added.cpp"); - Files[Added] = ""; - - auto Missing = testPath("missing.cpp"); - Files[Missing] = ""; - - S.update(Added, getInputs(Added, ""), WantDiagnostics::No); - - // Assert each operation for missing file is an error (even if it's available - // in VFS). - S.runWithAST("", Missing, - [&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); }); - S.runWithPreamble( - "", Missing, TUScheduler::Stale, - [&](Expected<InputsAndPreamble> Preamble) { EXPECT_ERROR(Preamble); }); - // remove() shouldn't crash on missing files. - S.remove(Missing); - - // Assert there aren't any errors for added file. - S.runWithAST("", Added, - [&](Expected<InputsAndAST> AST) { EXPECT_TRUE(bool(AST)); }); - S.runWithPreamble("", Added, TUScheduler::Stale, - [&](Expected<InputsAndPreamble> Preamble) { - EXPECT_TRUE(bool(Preamble)); - }); - S.remove(Added); - - // Assert that all operations fail after removing the file. - S.runWithAST("", Added, - [&](Expected<InputsAndAST> AST) { EXPECT_ERROR(AST); }); - S.runWithPreamble("", Added, TUScheduler::Stale, - [&](Expected<InputsAndPreamble> Preamble) { - ASSERT_FALSE(bool(Preamble)); - llvm::consumeError(Preamble.takeError()); - }); - // remove() shouldn't crash on missing files. - S.remove(Added); -} - -TEST_F(TUSchedulerTests, WantDiagnostics) { - std::atomic<int> CallbackCount(0); - { - // To avoid a racy test, don't allow tasks to actualy run on the worker - // thread until we've scheduled them all. - Notification Ready; - TUScheduler S( - CDB, getDefaultAsyncThreadsCount(), - /*StorePreamblesInMemory=*/true, captureDiags(), - /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(), - ASTRetentionPolicy()); - auto Path = testPath("foo.cpp"); - updateWithDiags(S, Path, "", WantDiagnostics::Yes, - [&](std::vector<Diag>) { Ready.wait(); }); - updateWithDiags(S, Path, "request diags", WantDiagnostics::Yes, - [&](std::vector<Diag>) { ++CallbackCount; }); - updateWithDiags(S, Path, "auto (clobbered)", WantDiagnostics::Auto, - [&](std::vector<Diag>) { - ADD_FAILURE() - << "auto should have been cancelled by auto"; - }); - updateWithDiags(S, Path, "request no diags", WantDiagnostics::No, - [&](std::vector<Diag>) { - ADD_FAILURE() << "no diags should not be called back"; - }); - updateWithDiags(S, Path, "auto (produces)", WantDiagnostics::Auto, - [&](std::vector<Diag>) { ++CallbackCount; }); - Ready.notify(); - - ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); - } - EXPECT_EQ(2, CallbackCount); -} - -TEST_F(TUSchedulerTests, Debounce) { - std::atomic<int> CallbackCount(0); - { - TUScheduler S(CDB, getDefaultAsyncThreadsCount(), - /*StorePreamblesInMemory=*/true, captureDiags(), - /*UpdateDebounce=*/std::chrono::seconds(1), - ASTRetentionPolicy()); - // FIXME: we could probably use timeouts lower than 1 second here. - auto Path = testPath("foo.cpp"); - updateWithDiags(S, Path, "auto (debounced)", WantDiagnostics::Auto, - [&](std::vector<Diag>) { - ADD_FAILURE() - << "auto should have been debounced and canceled"; - }); - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - updateWithDiags(S, Path, "auto (timed out)", WantDiagnostics::Auto, - [&](std::vector<Diag>) { ++CallbackCount; }); - std::this_thread::sleep_for(std::chrono::seconds(2)); - updateWithDiags(S, Path, "auto (shut down)", WantDiagnostics::Auto, - [&](std::vector<Diag>) { ++CallbackCount; }); - - ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); - } - EXPECT_EQ(2, CallbackCount); -} - -static std::vector<std::string> includes(const PreambleData *Preamble) { - std::vector<std::string> Result; - if (Preamble) - for (const auto &Inclusion : Preamble->Includes.MainFileIncludes) - Result.push_back(Inclusion.Written); - return Result; -} - -TEST_F(TUSchedulerTests, PreambleConsistency) { - std::atomic<int> CallbackCount(0); - { - Notification InconsistentReadDone; // Must live longest. - TUScheduler S( - CDB, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true, - /*ASTCallbacks=*/nullptr, - /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(), - ASTRetentionPolicy()); - auto Path = testPath("foo.cpp"); - // Schedule two updates (A, B) and two preamble reads (stale, consistent). - // The stale read should see A, and the consistent read should see B. - // (We recognize the preambles by their included files). - updateWithCallback(S, Path, "#include <A>", WantDiagnostics::Yes, [&]() { - // This callback runs in between the two preamble updates. - - // This blocks update B, preventing it from winning the race - // against the stale read. - // If the first read was instead consistent, this would deadlock. - InconsistentReadDone.wait(); - // This delays update B, preventing it from winning a race - // against the consistent read. The consistent read sees B - // only because it waits for it. - // If the second read was stale, it would usually see A. - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - }); - S.update(Path, getInputs(Path, "#include <B>"), WantDiagnostics::Yes); - - S.runWithPreamble("StaleRead", Path, TUScheduler::Stale, - [&](Expected<InputsAndPreamble> Pre) { - ASSERT_TRUE(bool(Pre)); - assert(bool(Pre)); - EXPECT_THAT(includes(Pre->Preamble), - ElementsAre("<A>")); - InconsistentReadDone.notify(); - ++CallbackCount; - }); - S.runWithPreamble("ConsistentRead", Path, TUScheduler::Consistent, - [&](Expected<InputsAndPreamble> Pre) { - ASSERT_TRUE(bool(Pre)); - EXPECT_THAT(includes(Pre->Preamble), - ElementsAre("<B>")); - ++CallbackCount; - }); - } - EXPECT_EQ(2, CallbackCount); -} - -TEST_F(TUSchedulerTests, Cancellation) { - // We have the following update/read sequence - // U0 - // U1(WantDiags=Yes) <-- cancelled - // R1 <-- cancelled - // U2(WantDiags=Yes) <-- cancelled - // R2A <-- cancelled - // R2B - // U3(WantDiags=Yes) - // R3 <-- cancelled - std::vector<std::string> DiagsSeen, ReadsSeen, ReadsCanceled; - { - Notification Proceed; // Ensure we schedule everything. - TUScheduler S( - CDB, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true, - /*ASTCallbacks=*/captureDiags(), - /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(), - ASTRetentionPolicy()); - auto Path = testPath("foo.cpp"); - // Helper to schedule a named update and return a function to cancel it. - auto Update = [&](std::string ID) -> Canceler { - auto T = cancelableTask(); - WithContext C(std::move(T.first)); - updateWithDiags( - S, Path, "//" + ID, WantDiagnostics::Yes, - [&, ID](std::vector<Diag> Diags) { DiagsSeen.push_back(ID); }); - return std::move(T.second); - }; - // Helper to schedule a named read and return a function to cancel it. - auto Read = [&](std::string ID) -> Canceler { - auto T = cancelableTask(); - WithContext C(std::move(T.first)); - S.runWithAST(ID, Path, [&, ID](llvm::Expected<InputsAndAST> E) { - if (auto Err = E.takeError()) { - if (Err.isA<CancelledError>()) { - ReadsCanceled.push_back(ID); - consumeError(std::move(Err)); - } else { - ADD_FAILURE() << "Non-cancelled error for " << ID << ": " - << llvm::toString(std::move(Err)); - } - } else { - ReadsSeen.push_back(ID); - } - }); - return std::move(T.second); - }; - - updateWithCallback(S, Path, "", WantDiagnostics::Yes, - [&]() { Proceed.wait(); }); - // The second parens indicate cancellation, where present. - Update("U1")(); - Read("R1")(); - Update("U2")(); - Read("R2A")(); - Read("R2B"); - Update("U3"); - Read("R3")(); - Proceed.notify(); - - ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); - } - EXPECT_THAT(DiagsSeen, ElementsAre("U2", "U3")) - << "U1 and all dependent reads were cancelled. " - "U2 has a dependent read R2A. " - "U3 was not cancelled."; - EXPECT_THAT(ReadsSeen, ElementsAre("R2B")) - << "All reads other than R2B were cancelled"; - EXPECT_THAT(ReadsCanceled, ElementsAre("R1", "R2A", "R3")) - << "All reads other than R2B were cancelled"; -} - -TEST_F(TUSchedulerTests, ManyUpdates) { - const int FilesCount = 3; - const int UpdatesPerFile = 10; - - std::mutex Mut; - int TotalASTReads = 0; - int TotalPreambleReads = 0; - int TotalUpdates = 0; - - // Run TUScheduler and collect some stats. - { - TUScheduler S(CDB, getDefaultAsyncThreadsCount(), - /*StorePreamblesInMemory=*/true, captureDiags(), - /*UpdateDebounce=*/std::chrono::milliseconds(50), - ASTRetentionPolicy()); - - std::vector<std::string> Files; - for (int I = 0; I < FilesCount; ++I) { - std::string Name = "foo" + std::to_string(I) + ".cpp"; - Files.push_back(testPath(Name)); - this->Files[Files.back()] = ""; - } - - StringRef Contents1 = R"cpp(int a;)cpp"; - StringRef Contents2 = R"cpp(int main() { return 1; })cpp"; - StringRef Contents3 = R"cpp(int a; int b; int sum() { return a + b; })cpp"; - - StringRef AllContents[] = {Contents1, Contents2, Contents3}; - const int AllContentsSize = 3; - - // Scheduler may run tasks asynchronously, but should propagate the context. - // We stash a nonce in the context, and verify it in the task. - static Key<int> NonceKey; - int Nonce = 0; - - for (int FileI = 0; FileI < FilesCount; ++FileI) { - for (int UpdateI = 0; UpdateI < UpdatesPerFile; ++UpdateI) { - auto Contents = AllContents[(FileI + UpdateI) % AllContentsSize]; - - auto File = Files[FileI]; - auto Inputs = getInputs(File, Contents.str()); - { - WithContextValue WithNonce(NonceKey, ++Nonce); - updateWithDiags( - S, File, Inputs, WantDiagnostics::Auto, - [File, Nonce, &Mut, &TotalUpdates](std::vector<Diag>) { - EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce)); - - std::lock_guard<std::mutex> Lock(Mut); - ++TotalUpdates; - EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext()); - }); - } - { - WithContextValue WithNonce(NonceKey, ++Nonce); - S.runWithAST( - "CheckAST", File, - [File, Inputs, Nonce, &Mut, - &TotalASTReads](Expected<InputsAndAST> AST) { - EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce)); - - ASSERT_TRUE((bool)AST); - EXPECT_EQ(AST->Inputs.FS, Inputs.FS); - EXPECT_EQ(AST->Inputs.Contents, Inputs.Contents); - - std::lock_guard<std::mutex> Lock(Mut); - ++TotalASTReads; - EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext()); - }); - } - - { - WithContextValue WithNonce(NonceKey, ++Nonce); - S.runWithPreamble( - "CheckPreamble", File, TUScheduler::Stale, - [File, Inputs, Nonce, &Mut, - &TotalPreambleReads](Expected<InputsAndPreamble> Preamble) { - EXPECT_THAT(Context::current().get(NonceKey), Pointee(Nonce)); - - ASSERT_TRUE((bool)Preamble); - EXPECT_EQ(Preamble->Contents, Inputs.Contents); - - std::lock_guard<std::mutex> Lock(Mut); - ++TotalPreambleReads; - EXPECT_EQ(File, *TUScheduler::getFileBeingProcessedInContext()); - }); - } - } - } - ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); - } // TUScheduler destructor waits for all operations to finish. - - std::lock_guard<std::mutex> Lock(Mut); - EXPECT_EQ(TotalUpdates, FilesCount * UpdatesPerFile); - EXPECT_EQ(TotalASTReads, FilesCount * UpdatesPerFile); - EXPECT_EQ(TotalPreambleReads, FilesCount * UpdatesPerFile); -} - -TEST_F(TUSchedulerTests, EvictedAST) { - std::atomic<int> BuiltASTCounter(0); - ASTRetentionPolicy Policy; - Policy.MaxRetainedASTs = 2; - TUScheduler S(CDB, - /*AsyncThreadsCount=*/1, /*StorePreambleInMemory=*/true, - /*ASTCallbacks=*/nullptr, - /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(), - Policy); - - llvm::StringLiteral SourceContents = R"cpp( - int* a; - double* b = a; - )cpp"; - llvm::StringLiteral OtherSourceContents = R"cpp( - int* a; - double* b = a + 0; - )cpp"; - - auto Foo = testPath("foo.cpp"); - auto Bar = testPath("bar.cpp"); - auto Baz = testPath("baz.cpp"); - - // Build one file in advance. We will not access it later, so it will be the - // one that the cache will evict. - updateWithCallback(S, Foo, SourceContents, WantDiagnostics::Yes, - [&BuiltASTCounter]() { ++BuiltASTCounter; }); - ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); - ASSERT_EQ(BuiltASTCounter.load(), 1); - - // Build two more files. Since we can retain only 2 ASTs, these should be the - // ones we see in the cache later. - updateWithCallback(S, Bar, SourceContents, WantDiagnostics::Yes, - [&BuiltASTCounter]() { ++BuiltASTCounter; }); - updateWithCallback(S, Baz, SourceContents, WantDiagnostics::Yes, - [&BuiltASTCounter]() { ++BuiltASTCounter; }); - ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); - ASSERT_EQ(BuiltASTCounter.load(), 3); - - // Check only the last two ASTs are retained. - ASSERT_THAT(S.getFilesWithCachedAST(), UnorderedElementsAre(Bar, Baz)); - - // Access the old file again. - updateWithCallback(S, Foo, OtherSourceContents, WantDiagnostics::Yes, - [&BuiltASTCounter]() { ++BuiltASTCounter; }); - ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); - ASSERT_EQ(BuiltASTCounter.load(), 4); - - // Check the AST for foo.cpp is retained now and one of the others got - // evicted. - EXPECT_THAT(S.getFilesWithCachedAST(), - UnorderedElementsAre(Foo, AnyOf(Bar, Baz))); -} - -TEST_F(TUSchedulerTests, EmptyPreamble) { - TUScheduler S(CDB, - /*AsyncThreadsCount=*/4, /*StorePreambleInMemory=*/true, - /*ASTCallbacks=*/nullptr, - /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(), - ASTRetentionPolicy()); - - auto Foo = testPath("foo.cpp"); - auto Header = testPath("foo.h"); - - Files[Header] = "void foo()"; - Timestamps[Header] = time_t(0); - auto WithPreamble = R"cpp( - #include "foo.h" - int main() {} - )cpp"; - auto WithEmptyPreamble = R"cpp(int main() {})cpp"; - S.update(Foo, getInputs(Foo, WithPreamble), WantDiagnostics::Auto); - S.runWithPreamble( - "getNonEmptyPreamble", Foo, TUScheduler::Stale, - [&](Expected<InputsAndPreamble> Preamble) { - // We expect to get a non-empty preamble. - EXPECT_GT( - cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size, - 0u); - }); - // Wait for the preamble is being built. - ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); - - // Update the file which results in an empty preamble. - S.update(Foo, getInputs(Foo, WithEmptyPreamble), WantDiagnostics::Auto); - // Wait for the preamble is being built. - ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); - S.runWithPreamble( - "getEmptyPreamble", Foo, TUScheduler::Stale, - [&](Expected<InputsAndPreamble> Preamble) { - // We expect to get an empty preamble. - EXPECT_EQ( - cantFail(std::move(Preamble)).Preamble->Preamble.getBounds().Size, - 0u); - }); -} - -TEST_F(TUSchedulerTests, RunWaitsForPreamble) { - // Testing strategy: we update the file and schedule a few preamble reads at - // the same time. All reads should get the same non-null preamble. - TUScheduler S(CDB, - /*AsyncThreadsCount=*/4, /*StorePreambleInMemory=*/true, - /*ASTCallbacks=*/nullptr, - /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(), - ASTRetentionPolicy()); - auto Foo = testPath("foo.cpp"); - auto NonEmptyPreamble = R"cpp( - #define FOO 1 - #define BAR 2 - - int main() {} - )cpp"; - constexpr int ReadsToSchedule = 10; - std::mutex PreamblesMut; - std::vector<const void *> Preambles(ReadsToSchedule, nullptr); - S.update(Foo, getInputs(Foo, NonEmptyPreamble), WantDiagnostics::Auto); - for (int I = 0; I < ReadsToSchedule; ++I) { - S.runWithPreamble( - "test", Foo, TUScheduler::Stale, - [I, &PreamblesMut, &Preambles](Expected<InputsAndPreamble> IP) { - std::lock_guard<std::mutex> Lock(PreamblesMut); - Preambles[I] = cantFail(std::move(IP)).Preamble; - }); - } - ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); - // Check all actions got the same non-null preamble. - std::lock_guard<std::mutex> Lock(PreamblesMut); - ASSERT_NE(Preambles[0], nullptr); - ASSERT_THAT(Preambles, Each(Preambles[0])); -} - -TEST_F(TUSchedulerTests, NoopOnEmptyChanges) { - TUScheduler S(CDB, - /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(), - /*StorePreambleInMemory=*/true, captureDiags(), - /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(), - ASTRetentionPolicy()); - - auto Source = testPath("foo.cpp"); - auto Header = testPath("foo.h"); - - Files[Header] = "int a;"; - Timestamps[Header] = time_t(0); - - auto SourceContents = R"cpp( - #include "foo.h" - int b = a; - )cpp"; - - // Return value indicates if the updated callback was received. - auto DoUpdate = [&](std::string Contents) -> bool { - std::atomic<bool> Updated(false); - Updated = false; - updateWithDiags(S, Source, Contents, WantDiagnostics::Yes, - [&Updated](std::vector<Diag>) { Updated = true; }); - bool UpdateFinished = S.blockUntilIdle(timeoutSeconds(10)); - if (!UpdateFinished) - ADD_FAILURE() << "Updated has not finished in one second. Threading bug?"; - return Updated; - }; - - // Test that subsequent updates with the same inputs do not cause rebuilds. - ASSERT_TRUE(DoUpdate(SourceContents)); - ASSERT_FALSE(DoUpdate(SourceContents)); - - // Update to a header should cause a rebuild, though. - Timestamps[Header] = time_t(1); - ASSERT_TRUE(DoUpdate(SourceContents)); - ASSERT_FALSE(DoUpdate(SourceContents)); - - // Update to the contents should cause a rebuild. - auto OtherSourceContents = R"cpp( - #include "foo.h" - int c = d; - )cpp"; - ASSERT_TRUE(DoUpdate(OtherSourceContents)); - ASSERT_FALSE(DoUpdate(OtherSourceContents)); - - // Update to the compile commands should also cause a rebuild. - CDB.ExtraClangFlags.push_back("-DSOMETHING"); - ASSERT_TRUE(DoUpdate(OtherSourceContents)); - ASSERT_FALSE(DoUpdate(OtherSourceContents)); -} - -TEST_F(TUSchedulerTests, NoChangeDiags) { - TUScheduler S(CDB, - /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(), - /*StorePreambleInMemory=*/true, captureDiags(), - /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(), - ASTRetentionPolicy()); - - auto FooCpp = testPath("foo.cpp"); - auto Contents = "int a; int b;"; - - updateWithDiags( - S, FooCpp, Contents, WantDiagnostics::No, - [](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; }); - S.runWithAST("touchAST", FooCpp, [](Expected<InputsAndAST> IA) { - // Make sure the AST was actually built. - cantFail(std::move(IA)); - }); - ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); - - // Even though the inputs didn't change and AST can be reused, we need to - // report the diagnostics, as they were not reported previously. - std::atomic<bool> SeenDiags(false); - updateWithDiags(S, FooCpp, Contents, WantDiagnostics::Auto, - [&](std::vector<Diag>) { SeenDiags = true; }); - ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); - ASSERT_TRUE(SeenDiags); - - // Subsequent request does not get any diagnostics callback because the same - // diags have previously been reported and the inputs didn't change. - updateWithDiags( - S, FooCpp, Contents, WantDiagnostics::Auto, - [&](std::vector<Diag>) { ADD_FAILURE() << "Should not be called."; }); - ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); -} - -TEST_F(TUSchedulerTests, Run) { - TUScheduler S(CDB, /*AsyncThreadsCount=*/getDefaultAsyncThreadsCount(), - /*StorePreambleInMemory=*/true, /*ASTCallbacks=*/nullptr, - /*UpdateDebounce=*/std::chrono::steady_clock::duration::zero(), - ASTRetentionPolicy()); - std::atomic<int> Counter(0); - S.run("add 1", [&] { ++Counter; }); - S.run("add 2", [&] { Counter += 2; }); - ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); - EXPECT_EQ(Counter.load(), 3); -} - -TEST_F(TUSchedulerTests, TUStatus) { - class CaptureTUStatus : public DiagnosticsConsumer { - public: - void onDiagnosticsReady(PathRef File, - std::vector<Diag> Diagnostics) override {} - - void onFileUpdated(PathRef File, const TUStatus &Status) override { - std::lock_guard<std::mutex> Lock(Mutex); - AllStatus.push_back(Status); - } - - std::vector<TUStatus> AllStatus; - - private: - std::mutex Mutex; - } CaptureTUStatus; - MockFSProvider FS; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, CaptureTUStatus, ClangdServer::optsForTest()); - Annotations Code("int m^ain () {}"); - - // We schedule the following tasks in the queue: - // [Update] [GoToDefinition] - Server.addDocument(testPath("foo.cpp"), Code.code(), WantDiagnostics::Yes); - Server.locateSymbolAt(testPath("foo.cpp"), Code.point(), - [](Expected<std::vector<LocatedSymbol>> Result) { - ASSERT_TRUE((bool)Result); - }); - - ASSERT_TRUE(Server.blockUntilIdleForTest()); - - EXPECT_THAT(CaptureTUStatus.AllStatus, - ElementsAre( - // Statuses of "Update" action. - TUState(TUAction::RunningAction, "Update"), - TUState(TUAction::BuildingPreamble, "Update"), - TUState(TUAction::BuildingFile, "Update"), - - // Statuses of "Definitions" action - TUState(TUAction::RunningAction, "Definitions"), - TUState(TUAction::Idle, /*No action*/ ""))); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/TestFS.cpp b/clang-tools-extra/unittests/clangd/TestFS.cpp deleted file mode 100644 index c5b2613f759..00000000000 --- a/clang-tools-extra/unittests/clangd/TestFS.cpp +++ /dev/null @@ -1,129 +0,0 @@ -//===-- TestFS.cpp ----------------------------------------------*- C++ -*-===// -// -// 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 "TestFS.h" -#include "URI.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/Path.h" - -namespace clang { -namespace clangd { - -llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> -buildTestFS(llvm::StringMap<std::string> const &Files, - llvm::StringMap<time_t> const &Timestamps) { - llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> MemFS( - new llvm::vfs::InMemoryFileSystem); - MemFS->setCurrentWorkingDirectory(testRoot()); - for (auto &FileAndContents : Files) { - llvm::StringRef File = FileAndContents.first(); - MemFS->addFile( - File, Timestamps.lookup(File), - llvm::MemoryBuffer::getMemBufferCopy(FileAndContents.second, File)); - } - return MemFS; -} - -MockCompilationDatabase::MockCompilationDatabase(llvm::StringRef Directory, - llvm::StringRef RelPathPrefix) - : ExtraClangFlags({"-ffreestanding"}), Directory(Directory), - RelPathPrefix(RelPathPrefix) { - // -ffreestanding avoids implicit stdc-predef.h. -} - -llvm::Optional<tooling::CompileCommand> -MockCompilationDatabase::getCompileCommand(PathRef File, - ProjectInfo *Project) const { - if (ExtraClangFlags.empty()) - return None; - - auto FileName = llvm::sys::path::filename(File); - - // Build the compile command. - auto CommandLine = ExtraClangFlags; - CommandLine.insert(CommandLine.begin(), "clang"); - if (RelPathPrefix.empty()) { - // Use the absolute path in the compile command. - CommandLine.push_back(File); - } else { - // Build a relative path using RelPathPrefix. - llvm::SmallString<32> RelativeFilePath(RelPathPrefix); - llvm::sys::path::append(RelativeFilePath, FileName); - CommandLine.push_back(RelativeFilePath.str()); - } - - if (Project) - Project->SourceRoot = Directory; - return {tooling::CompileCommand(Directory != llvm::StringRef() - ? Directory - : llvm::sys::path::parent_path(File), - FileName, std::move(CommandLine), "")}; -} - -const char *testRoot() { -#ifdef _WIN32 - return "C:\\clangd-test"; -#else - return "/clangd-test"; -#endif -} - -std::string testPath(PathRef File) { - assert(llvm::sys::path::is_relative(File) && "FileName should be relative"); - - llvm::SmallString<32> NativeFile = File; - llvm::sys::path::native(NativeFile); - llvm::SmallString<32> Path; - llvm::sys::path::append(Path, testRoot(), NativeFile); - return Path.str(); -} - -/// unittest: is a scheme that refers to files relative to testRoot(). -/// URI body is a path relative to testRoot() e.g. unittest:///x.h for -/// /clangd-test/x.h. -class TestScheme : public URIScheme { -public: - static const char *Scheme; - - llvm::Expected<std::string> - getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body, - llvm::StringRef HintPath) const override { - if (!HintPath.startswith(testRoot())) - return llvm::make_error<llvm::StringError>( - "Hint path doesn't start with test root: " + HintPath, - llvm::inconvertibleErrorCode()); - if (!Body.consume_front("/")) - return llvm::make_error<llvm::StringError>( - "Body of an unittest: URI must start with '/'", - llvm::inconvertibleErrorCode()); - llvm::SmallString<16> Path(Body.begin(), Body.end()); - llvm::sys::path::native(Path); - return testPath(Path); - } - - llvm::Expected<URI> - uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override { - llvm::StringRef Body = AbsolutePath; - if (!Body.consume_front(testRoot())) - return llvm::make_error<llvm::StringError>( - AbsolutePath + "does not start with " + testRoot(), - llvm::inconvertibleErrorCode()); - - return URI(Scheme, /*Authority=*/"", - llvm::sys::path::convert_to_slash(Body)); - } -}; - -const char *TestScheme::Scheme = "unittest"; - -static URISchemeRegistry::Add<TestScheme> X(TestScheme::Scheme, "Test schema"); - -volatile int UnittestSchemeAnchorSource = 0; - -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/TestFS.h b/clang-tools-extra/unittests/clangd/TestFS.h deleted file mode 100644 index eabdddf70ac..00000000000 --- a/clang-tools-extra/unittests/clangd/TestFS.h +++ /dev/null @@ -1,73 +0,0 @@ -//===-- TestFS.h ------------------------------------------------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// -// -// Allows setting up fake filesystem environments for tests. -// -//===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_TESTFS_H -#define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_TESTFS_H -#include "ClangdServer.h" -#include "llvm/ADT/IntrusiveRefCntPtr.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/VirtualFileSystem.h" - -namespace clang { -namespace clangd { - -// Builds a VFS that provides access to the provided files, plus temporary -// directories. -llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> -buildTestFS(llvm::StringMap<std::string> const &Files, - llvm::StringMap<time_t> const &Timestamps = {}); - -// A VFS provider that returns TestFSes containing a provided set of files. -class MockFSProvider : public FileSystemProvider { -public: - IntrusiveRefCntPtr<llvm::vfs::FileSystem> getFileSystem() const override { - return buildTestFS(Files); - } - - // If relative paths are used, they are resolved with testPath(). - llvm::StringMap<std::string> Files; -}; - -// A Compilation database that returns a fixed set of compile flags. -class MockCompilationDatabase : public GlobalCompilationDatabase { -public: - /// If \p Directory is not empty, use that as the Directory field of the - /// CompileCommand, and as project SourceRoot. - /// - /// If \p RelPathPrefix is not empty, use that as a prefix in front of the - /// source file name, instead of using an absolute path. - MockCompilationDatabase(StringRef Directory = StringRef(), - StringRef RelPathPrefix = StringRef()); - - llvm::Optional<tooling::CompileCommand> - getCompileCommand(PathRef File, ProjectInfo * = nullptr) const override; - - std::vector<std::string> ExtraClangFlags; - -private: - StringRef Directory; - StringRef RelPathPrefix; -}; - -// Returns an absolute (fake) test directory for this OS. -const char *testRoot(); - -// Returns a suitable absolute path for this OS. -std::string testPath(PathRef File); - -// unittest: is a scheme that refers to files relative to testRoot() -// This anchor is used to force the linker to link in the generated object file -// and thus register unittest: URI scheme plugin. -extern volatile int UnittestSchemeAnchorSource; - -} // namespace clangd -} // namespace clang -#endif diff --git a/clang-tools-extra/unittests/clangd/TestIndex.cpp b/clang-tools-extra/unittests/clangd/TestIndex.cpp deleted file mode 100644 index 11ac4239df1..00000000000 --- a/clang-tools-extra/unittests/clangd/TestIndex.cpp +++ /dev/null @@ -1,118 +0,0 @@ -//===-- TestIndex.cpp -------------------------------------------*- C++ -*-===// -// -// 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 "TestIndex.h" -#include "clang/Index/IndexSymbol.h" -#include "llvm/Support/Regex.h" - -namespace clang { -namespace clangd { - -Symbol symbol(llvm::StringRef QName) { - Symbol Sym; - Sym.ID = SymbolID(QName.str()); - size_t Pos = QName.rfind("::"); - if (Pos == llvm::StringRef::npos) { - Sym.Name = QName; - Sym.Scope = ""; - } else { - Sym.Name = QName.substr(Pos + 2); - Sym.Scope = QName.substr(0, Pos + 2); - } - return Sym; -} - -static std::string replace(llvm::StringRef Haystack, llvm::StringRef Needle, - llvm::StringRef Repl) { - llvm::SmallVector<llvm::StringRef, 8> Parts; - Haystack.split(Parts, Needle); - return llvm::join(Parts, Repl); -} - -// Helpers to produce fake index symbols for memIndex() or completions(). -// USRFormat is a regex replacement string for the unqualified part of the USR. -Symbol sym(llvm::StringRef QName, index::SymbolKind Kind, - llvm::StringRef USRFormat) { - Symbol Sym; - std::string USR = "c:"; // We synthesize a few simple cases of USRs by hand! - size_t Pos = QName.rfind("::"); - if (Pos == llvm::StringRef::npos) { - Sym.Name = QName; - Sym.Scope = ""; - } else { - Sym.Name = QName.substr(Pos + 2); - Sym.Scope = QName.substr(0, Pos + 2); - USR += "@N@" + replace(QName.substr(0, Pos), "::", "@N@"); // ns:: -> @N@ns - } - USR += llvm::Regex("^.*$").sub(USRFormat, Sym.Name); // e.g. func -> @F@func# - Sym.ID = SymbolID(USR); - Sym.SymInfo.Kind = Kind; - Sym.Flags |= Symbol::IndexedForCodeCompletion; - Sym.Origin = SymbolOrigin::Static; - return Sym; -} - -Symbol func(llvm::StringRef Name) { // Assumes the function has no args. - return sym(Name, index::SymbolKind::Function, "@F@\\0#"); // no args -} - -Symbol cls(llvm::StringRef Name) { - return sym(Name, index::SymbolKind::Class, "@S@\\0"); -} - -Symbol var(llvm::StringRef Name) { - return sym(Name, index::SymbolKind::Variable, "@\\0"); -} - -Symbol ns(llvm::StringRef Name) { - return sym(Name, index::SymbolKind::Namespace, "@N@\\0"); -} - -SymbolSlab generateSymbols(std::vector<std::string> QualifiedNames) { - SymbolSlab::Builder Slab; - for (llvm::StringRef QName : QualifiedNames) - Slab.insert(symbol(QName)); - return std::move(Slab).build(); -} - -SymbolSlab generateNumSymbols(int Begin, int End) { - std::vector<std::string> Names; - for (int i = Begin; i <= End; i++) - Names.push_back(std::to_string(i)); - return generateSymbols(Names); -} - -std::string getQualifiedName(const Symbol &Sym) { - return (Sym.Scope + Sym.Name + Sym.TemplateSpecializationArgs).str(); -} - -std::vector<std::string> match(const SymbolIndex &I, - const FuzzyFindRequest &Req, bool *Incomplete) { - std::vector<std::string> Matches; - bool IsIncomplete = I.fuzzyFind(Req, [&](const Symbol &Sym) { - Matches.push_back(clang::clangd::getQualifiedName(Sym)); - }); - if (Incomplete) - *Incomplete = IsIncomplete; - return Matches; -} - -// Returns qualified names of symbols with any of IDs in the index. -std::vector<std::string> lookup(const SymbolIndex &I, - llvm::ArrayRef<SymbolID> IDs) { - LookupRequest Req; - Req.IDs.insert(IDs.begin(), IDs.end()); - std::vector<std::string> Results; - I.lookup(Req, [&](const Symbol &Sym) { - Results.push_back(getQualifiedName(Sym)); - }); - return Results; -} - -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/TestIndex.h b/clang-tools-extra/unittests/clangd/TestIndex.h deleted file mode 100644 index 01de089eabf..00000000000 --- a/clang-tools-extra/unittests/clangd/TestIndex.h +++ /dev/null @@ -1,57 +0,0 @@ -//===-- IndexHelpers.h ------------------------------------------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_INDEXTESTCOMMON_H -#define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_INDEXTESTCOMMON_H - -#include "index/Index.h" -#include "index/Merge.h" - -namespace clang { -namespace clangd { - -// Creates Symbol instance and sets SymbolID to given QualifiedName. -Symbol symbol(llvm::StringRef QName); - -// Helpers to produce fake index symbols with proper SymbolID. -// USRFormat is a regex replacement string for the unqualified part of the USR. -Symbol sym(llvm::StringRef QName, index::SymbolKind Kind, - llvm::StringRef USRFormat); -// Creats a function symbol assuming no function arg. -Symbol func(llvm::StringRef Name); -// Creates a class symbol. -Symbol cls(llvm::StringRef Name); -// Creates a variable symbol. -Symbol var(llvm::StringRef Name); -// Creates a namespace symbol. -Symbol ns(llvm::StringRef Name); - -// Create a slab of symbols with the given qualified names as IDs and names. -SymbolSlab generateSymbols(std::vector<std::string> QualifiedNames); - -// Create a slab of symbols with IDs and names [Begin, End]. -SymbolSlab generateNumSymbols(int Begin, int End); - -// Returns fully-qualified name out of given symbol. -std::string getQualifiedName(const Symbol &Sym); - -// Performs fuzzy matching-based symbol lookup given a query and an index. -// Incomplete is set true if more items than requested can be retrieved, false -// otherwise. -std::vector<std::string> match(const SymbolIndex &I, - const FuzzyFindRequest &Req, - bool *Incomplete = nullptr); - -// Returns qualified names of symbols with any of IDs in the index. -std::vector<std::string> lookup(const SymbolIndex &I, - llvm::ArrayRef<SymbolID> IDs); - -} // namespace clangd -} // namespace clang - -#endif diff --git a/clang-tools-extra/unittests/clangd/TestScheme.h b/clang-tools-extra/unittests/clangd/TestScheme.h deleted file mode 100644 index e69de29bb2d..00000000000 --- a/clang-tools-extra/unittests/clangd/TestScheme.h +++ /dev/null diff --git a/clang-tools-extra/unittests/clangd/TestTU.cpp b/clang-tools-extra/unittests/clangd/TestTU.cpp deleted file mode 100644 index 05c7fbf8bf4..00000000000 --- a/clang-tools-extra/unittests/clangd/TestTU.cpp +++ /dev/null @@ -1,153 +0,0 @@ -//===--- TestTU.cpp - Scratch source files for testing --------------------===// -// -// 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 "TestTU.h" -#include "TestFS.h" -#include "index/FileIndex.h" -#include "index/MemIndex.h" -#include "clang/AST/RecursiveASTVisitor.h" -#include "clang/Frontend/CompilerInvocation.h" -#include "clang/Frontend/Utils.h" - -namespace clang { -namespace clangd { - -ParsedAST TestTU::build() const { - std::string FullFilename = testPath(Filename), - FullHeaderName = testPath(HeaderFilename), - ImportThunk = testPath("import_thunk.h"); - std::vector<const char *> Cmd = {"clang", FullFilename.c_str()}; - // We want to implicitly include HeaderFilename without messing up offsets. - // -include achieves this, but sometimes we want #import (to simulate a header - // guard without messing up offsets). In this case, use an intermediate file. - std::string ThunkContents = "#import \"" + FullHeaderName + "\"\n"; - // FIXME: this shouldn't need to be conditional, but it breaks a - // GoToDefinition test for some reason (getMacroArgExpandedLocation fails). - if (!HeaderCode.empty()) { - Cmd.push_back("-include"); - Cmd.push_back(ImplicitHeaderGuard ? ImportThunk.c_str() - : FullHeaderName.c_str()); - } - Cmd.insert(Cmd.end(), ExtraArgs.begin(), ExtraArgs.end()); - ParseInputs Inputs; - Inputs.CompileCommand.Filename = FullFilename; - Inputs.CompileCommand.CommandLine = {Cmd.begin(), Cmd.end()}; - Inputs.CompileCommand.Directory = testRoot(); - Inputs.Contents = Code; - Inputs.FS = buildTestFS({{FullFilename, Code}, - {FullHeaderName, HeaderCode}, - {ImportThunk, ThunkContents}}); - Inputs.Opts = ParseOptions(); - Inputs.Opts.ClangTidyOpts.Checks = ClangTidyChecks; - Inputs.Index = ExternalIndex; - if (Inputs.Index) - Inputs.Opts.SuggestMissingIncludes = true; - auto CI = buildCompilerInvocation(Inputs); - assert(CI && "Failed to build compilation invocation."); - auto Preamble = - buildPreamble(FullFilename, *CI, - /*OldPreamble=*/nullptr, - /*OldCompileCommand=*/Inputs.CompileCommand, Inputs, - /*StoreInMemory=*/true, /*PreambleCallback=*/nullptr); - auto AST = buildAST(FullFilename, createInvocationFromCommandLine(Cmd), - Inputs, Preamble); - if (!AST.hasValue()) { - ADD_FAILURE() << "Failed to build code:\n" << Code; - llvm_unreachable("Failed to build TestTU!"); - } - return std::move(*AST); -} - -SymbolSlab TestTU::headerSymbols() const { - auto AST = build(); - return indexHeaderSymbols(AST.getASTContext(), AST.getPreprocessorPtr(), - AST.getCanonicalIncludes()); -} - -std::unique_ptr<SymbolIndex> TestTU::index() const { - auto AST = build(); - auto Idx = llvm::make_unique<FileIndex>(/*UseDex=*/true); - Idx->updatePreamble(Filename, AST.getASTContext(), AST.getPreprocessorPtr(), - AST.getCanonicalIncludes()); - Idx->updateMain(Filename, AST); - return std::move(Idx); -} - -const Symbol &findSymbol(const SymbolSlab &Slab, llvm::StringRef QName) { - const Symbol *Result = nullptr; - for (const Symbol &S : Slab) { - if (QName != (S.Scope + S.Name).str()) - continue; - if (Result) { - ADD_FAILURE() << "Multiple symbols named " << QName << ":\n" - << *Result << "\n---\n" - << S; - assert(false && "QName is not unique"); - } - Result = &S; - } - if (!Result) { - ADD_FAILURE() << "No symbol named " << QName << " in " - << ::testing::PrintToString(Slab); - assert(false && "No symbol with QName"); - } - return *Result; -} - -const NamedDecl &findDecl(ParsedAST &AST, llvm::StringRef QName) { - llvm::SmallVector<llvm::StringRef, 4> Components; - QName.split(Components, "::"); - - auto &Ctx = AST.getASTContext(); - auto LookupDecl = [&Ctx](const DeclContext &Scope, - llvm::StringRef Name) -> const NamedDecl & { - auto LookupRes = Scope.lookup(DeclarationName(&Ctx.Idents.get(Name))); - assert(!LookupRes.empty() && "Lookup failed"); - assert(LookupRes.size() == 1 && "Lookup returned multiple results"); - return *LookupRes.front(); - }; - - const DeclContext *Scope = Ctx.getTranslationUnitDecl(); - for (auto NameIt = Components.begin(), End = Components.end() - 1; - NameIt != End; ++NameIt) { - Scope = &cast<DeclContext>(LookupDecl(*Scope, *NameIt)); - } - return LookupDecl(*Scope, Components.back()); -} - -const NamedDecl &findDecl(ParsedAST &AST, - std::function<bool(const NamedDecl &)> Filter) { - struct Visitor : RecursiveASTVisitor<Visitor> { - decltype(Filter) F; - llvm::SmallVector<const NamedDecl *, 1> Decls; - bool VisitNamedDecl(const NamedDecl *ND) { - if (F(*ND)) - Decls.push_back(ND); - return true; - } - } Visitor; - Visitor.F = Filter; - Visitor.TraverseDecl(AST.getASTContext().getTranslationUnitDecl()); - if (Visitor.Decls.size() != 1) { - ADD_FAILURE() << Visitor.Decls.size() << " symbols matched."; - assert(Visitor.Decls.size() == 1); - } - return *Visitor.Decls.front(); -} - -const NamedDecl &findUnqualifiedDecl(ParsedAST &AST, llvm::StringRef Name) { - return findDecl(AST, [Name](const NamedDecl &ND) { - if (auto *ID = ND.getIdentifier()) - if (ID->getName() == Name) - return true; - return false; - }); -} - -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/TestTU.h b/clang-tools-extra/unittests/clangd/TestTU.h deleted file mode 100644 index 0f5951695f7..00000000000 --- a/clang-tools-extra/unittests/clangd/TestTU.h +++ /dev/null @@ -1,76 +0,0 @@ -//===--- TestTU.h - Scratch source files for testing -------------*- C++-*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// -// -// Many tests for indexing, code completion etc are most naturally expressed -// using code examples. -// TestTU lets test define these examples in a common way without dealing with -// the mechanics of VFS and compiler interactions, and then easily grab the -// AST, particular symbols, etc. -// -//===---------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_TESTTU_H -#define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_TESTTU_H - -#include "ClangdUnit.h" -#include "index/Index.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { - -struct TestTU { - static TestTU withCode(llvm::StringRef Code) { - TestTU TU; - TU.Code = Code; - return TU; - } - - static TestTU withHeaderCode(llvm::StringRef HeaderCode) { - TestTU TU; - TU.HeaderCode = HeaderCode; - return TU; - } - - // The code to be compiled. - std::string Code; - std::string Filename = "TestTU.cpp"; - - // Define contents of a header which will be implicitly included by Code. - std::string HeaderCode; - std::string HeaderFilename = "TestTU.h"; - - // Extra arguments for the compiler invocation. - std::vector<const char *> ExtraArgs; - - llvm::Optional<std::string> ClangTidyChecks; - // Index to use when building AST. - const SymbolIndex *ExternalIndex = nullptr; - - // Simulate a header guard of the header (using an #import directive). - bool ImplicitHeaderGuard = true; - - ParsedAST build() const; - SymbolSlab headerSymbols() const; - std::unique_ptr<SymbolIndex> index() const; -}; - -// Look up an index symbol by qualified name, which must be unique. -const Symbol &findSymbol(const SymbolSlab &, llvm::StringRef QName); -// Look up an AST symbol by qualified name, which must be unique and top-level. -const NamedDecl &findDecl(ParsedAST &AST, llvm::StringRef QName); -// Look up an AST symbol that satisfies \p Filter. -const NamedDecl &findDecl(ParsedAST &AST, - std::function<bool(const NamedDecl &)> Filter); -// Look up an AST symbol by unqualified name, which must be unique. -const NamedDecl &findUnqualifiedDecl(ParsedAST &AST, llvm::StringRef Name); - -} // namespace clangd -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_TESTTU_H diff --git a/clang-tools-extra/unittests/clangd/ThreadingTests.cpp b/clang-tools-extra/unittests/clangd/ThreadingTests.cpp deleted file mode 100644 index 18b9146ef14..00000000000 --- a/clang-tools-extra/unittests/clangd/ThreadingTests.cpp +++ /dev/null @@ -1,64 +0,0 @@ -//===-- ThreadingTests.cpp --------------------------------------*- C++ -*-===// -// -// 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 "Threading.h" -#include "gtest/gtest.h" -#include <mutex> - -namespace clang { -namespace clangd { -class ThreadingTest : public ::testing::Test {}; - -TEST_F(ThreadingTest, TaskRunner) { - const int TasksCnt = 100; - // This should be const, but MSVC does not allow to use const vars in lambdas - // without capture. On the other hand, clang gives a warning that capture of - // const var is not required. - // Making it non-const makes both compilers happy. - int IncrementsPerTask = 1000; - - std::mutex Mutex; - int Counter(0); /* GUARDED_BY(Mutex) */ - { - AsyncTaskRunner Tasks; - auto scheduleIncrements = [&]() { - for (int TaskI = 0; TaskI < TasksCnt; ++TaskI) { - Tasks.runAsync("task", [&Counter, &Mutex, IncrementsPerTask]() { - for (int Increment = 0; Increment < IncrementsPerTask; ++Increment) { - std::lock_guard<std::mutex> Lock(Mutex); - ++Counter; - } - }); - } - }; - - { - // Make sure runAsync is not running tasks synchronously on the same - // thread by locking the Mutex used for increments. - std::lock_guard<std::mutex> Lock(Mutex); - scheduleIncrements(); - } - - Tasks.wait(); - { - std::lock_guard<std::mutex> Lock(Mutex); - ASSERT_EQ(Counter, TasksCnt * IncrementsPerTask); - } - - { - std::lock_guard<std::mutex> Lock(Mutex); - Counter = 0; - scheduleIncrements(); - } - } - // Check that destructor has waited for tasks to finish. - std::lock_guard<std::mutex> Lock(Mutex); - ASSERT_EQ(Counter, TasksCnt * IncrementsPerTask); -} -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/TraceTests.cpp b/clang-tools-extra/unittests/clangd/TraceTests.cpp deleted file mode 100644 index 1871e6acf10..00000000000 --- a/clang-tools-extra/unittests/clangd/TraceTests.cpp +++ /dev/null @@ -1,127 +0,0 @@ -//===-- TraceTests.cpp - Tracing unit tests ---------------------*- C++ -*-===// -// -// 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 "Trace.h" - -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/SmallString.h" -#include "llvm/Support/SourceMgr.h" -#include "llvm/Support/Threading.h" -#include "llvm/Support/YAMLParser.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -MATCHER_P(StringNode, Val, "") { - if (arg->getType() != llvm::yaml::Node::NK_Scalar) { - *result_listener << "is a " << arg->getVerbatimTag(); - return false; - } - llvm::SmallString<32> S; - return Val == static_cast<llvm::yaml::ScalarNode *>(arg)->getValue(S); -} - -// Checks that N is a Mapping (JS object) with the expected scalar properties. -// The object must have all the Expected properties, but may have others. -bool VerifyObject(llvm::yaml::Node &N, - std::map<std::string, std::string> Expected) { - auto *M = llvm::dyn_cast<llvm::yaml::MappingNode>(&N); - if (!M) { - ADD_FAILURE() << "Not an object"; - return false; - } - bool Match = true; - llvm::SmallString<32> Tmp; - for (auto &Prop : *M) { - auto *K = llvm::dyn_cast_or_null<llvm::yaml::ScalarNode>(Prop.getKey()); - if (!K) - continue; - std::string KS = K->getValue(Tmp).str(); - auto I = Expected.find(KS); - if (I == Expected.end()) - continue; // Ignore properties with no assertion. - - auto *V = llvm::dyn_cast_or_null<llvm::yaml::ScalarNode>(Prop.getValue()); - if (!V) { - ADD_FAILURE() << KS << " is not a string"; - Match = false; - } - std::string VS = V->getValue(Tmp).str(); - if (VS != I->second) { - ADD_FAILURE() << KS << " expected " << I->second << " but actual " << VS; - Match = false; - } - Expected.erase(I); - } - for (const auto &P : Expected) { - ADD_FAILURE() << P.first << " missing, expected " << P.second; - Match = false; - } - return Match; -} - -TEST(TraceTest, SmokeTest) { - // Capture some events. - std::string JSON; - { - llvm::raw_string_ostream OS(JSON); - auto JSONTracer = trace::createJSONTracer(OS); - trace::Session Session(*JSONTracer); - { - trace::Span Tracer("A"); - trace::log("B"); - } - } - - // Get the root JSON object using the YAML parser. - llvm::SourceMgr SM; - llvm::yaml::Stream Stream(JSON, SM); - auto Doc = Stream.begin(); - ASSERT_NE(Doc, Stream.end()); - auto *Root = llvm::dyn_cast_or_null<llvm::yaml::MappingNode>(Doc->getRoot()); - ASSERT_NE(Root, nullptr) << "Root should be an object"; - - // Check whether we expect thread name events on this platform. - llvm::SmallString<32> ThreadName; - get_thread_name(ThreadName); - bool ThreadsHaveNames = !ThreadName.empty(); - - // We expect in order: - // displayTimeUnit: "ns" - // traceEvents: [process name, thread name, start span, log, end span] - // (The order doesn't matter, but the YAML parser is awkward to use otherwise) - auto Prop = Root->begin(); - ASSERT_NE(Prop, Root->end()) << "Expected displayTimeUnit property"; - ASSERT_THAT(Prop->getKey(), StringNode("displayTimeUnit")); - EXPECT_THAT(Prop->getValue(), StringNode("ns")); - ASSERT_NE(++Prop, Root->end()) << "Expected traceEvents property"; - EXPECT_THAT(Prop->getKey(), StringNode("traceEvents")); - auto *Events = - llvm::dyn_cast_or_null<llvm::yaml::SequenceNode>(Prop->getValue()); - ASSERT_NE(Events, nullptr) << "traceEvents should be an array"; - auto Event = Events->begin(); - ASSERT_NE(Event, Events->end()) << "Expected process name"; - EXPECT_TRUE(VerifyObject(*Event, {{"ph", "M"}, {"name", "process_name"}})); - if (ThreadsHaveNames) { - ASSERT_NE(++Event, Events->end()) << "Expected thread name"; - EXPECT_TRUE(VerifyObject(*Event, {{"ph", "M"}, {"name", "thread_name"}})); - } - ASSERT_NE(++Event, Events->end()) << "Expected log message"; - EXPECT_TRUE(VerifyObject(*Event, {{"ph", "i"}, {"name", "Log"}})); - ASSERT_NE(++Event, Events->end()) << "Expected span end"; - EXPECT_TRUE(VerifyObject(*Event, {{"ph", "X"}, {"name", "A"}})); - ASSERT_EQ(++Event, Events->end()); - ASSERT_EQ(++Prop, Root->end()); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/TweakTests.cpp b/clang-tools-extra/unittests/clangd/TweakTests.cpp deleted file mode 100644 index baa60292e3d..00000000000 --- a/clang-tools-extra/unittests/clangd/TweakTests.cpp +++ /dev/null @@ -1,190 +0,0 @@ -//===-- TweakTests.cpp ------------------------------------------*- C++ -*-===// -// -// 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 "Annotations.h" -#include "SourceCode.h" -#include "TestTU.h" -#include "refactor/Tweak.h" -#include "clang/AST/Expr.h" -#include "clang/Rewrite/Core/Rewriter.h" -#include "clang/Tooling/Core/Replacement.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Error.h" -#include "llvm/Testing/Support/Error.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include <cassert> - -using llvm::Failed; -using llvm::HasValue; -using llvm::Succeeded; - -namespace clang { -namespace clangd { -namespace { - -std::string markRange(llvm::StringRef Code, Range R) { - size_t Begin = llvm::cantFail(positionToOffset(Code, R.start)); - size_t End = llvm::cantFail(positionToOffset(Code, R.end)); - assert(Begin <= End); - if (Begin == End) // Mark a single point. - return (Code.substr(0, Begin) + "^" + Code.substr(Begin)).str(); - // Mark a range. - return (Code.substr(0, Begin) + "[[" + Code.substr(Begin, End - Begin) + - "]]" + Code.substr(End)) - .str(); -} - -void checkAvailable(StringRef ID, llvm::StringRef Input, bool Available) { - Annotations Code(Input); - ASSERT_TRUE(0 < Code.points().size() || 0 < Code.ranges().size()) - << "no points of interest specified"; - TestTU TU; - TU.Filename = "foo.cpp"; - TU.Code = Code.code(); - - ParsedAST AST = TU.build(); - - auto CheckOver = [&](Range Selection) { - unsigned Begin = cantFail(positionToOffset(Code.code(), Selection.start)); - unsigned End = cantFail(positionToOffset(Code.code(), Selection.end)); - auto T = prepareTweak(ID, Tweak::Selection(AST, Begin, End)); - if (Available) - EXPECT_THAT_EXPECTED(T, Succeeded()) - << "code is " << markRange(Code.code(), Selection); - else - EXPECT_THAT_EXPECTED(T, Failed()) - << "code is " << markRange(Code.code(), Selection); - }; - for (auto P : Code.points()) - CheckOver(Range{P, P}); - for (auto R : Code.ranges()) - CheckOver(R); -} - -/// Checks action is available at every point and range marked in \p Input. -void checkAvailable(StringRef ID, llvm::StringRef Input) { - return checkAvailable(ID, Input, /*Available=*/true); -} - -/// Same as checkAvailable, but checks the action is not available. -void checkNotAvailable(StringRef ID, llvm::StringRef Input) { - return checkAvailable(ID, Input, /*Available=*/false); -} -llvm::Expected<std::string> apply(StringRef ID, llvm::StringRef Input) { - Annotations Code(Input); - Range SelectionRng; - if (Code.points().size() != 0) { - assert(Code.ranges().size() == 0 && - "both a cursor point and a selection range were specified"); - SelectionRng = Range{Code.point(), Code.point()}; - } else { - SelectionRng = Code.range(); - } - TestTU TU; - TU.Filename = "foo.cpp"; - TU.Code = Code.code(); - - ParsedAST AST = TU.build(); - unsigned Begin = cantFail(positionToOffset(Code.code(), SelectionRng.start)); - unsigned End = cantFail(positionToOffset(Code.code(), SelectionRng.end)); - Tweak::Selection S(AST, Begin, End); - - auto T = prepareTweak(ID, S); - if (!T) - return T.takeError(); - auto Replacements = (*T)->apply(S); - if (!Replacements) - return Replacements.takeError(); - return applyAllReplacements(Code.code(), *Replacements); -} - -void checkTransform(llvm::StringRef ID, llvm::StringRef Input, - llvm::StringRef Output) { - EXPECT_THAT_EXPECTED(apply(ID, Input), HasValue(Output)) - << "action id is" << ID; -} - -TEST(TweakTest, SwapIfBranches) { - llvm::StringLiteral ID = "SwapIfBranches"; - - checkAvailable(ID, R"cpp( - void test() { - ^i^f^^(^t^r^u^e^) { return 100; } ^e^l^s^e^ { continue; } - } - )cpp"); - - checkNotAvailable(ID, R"cpp( - void test() { - if (true) {^return ^100;^ } else { ^continue^;^ } - } - )cpp"); - - llvm::StringLiteral Input = R"cpp( - void test() { - ^if (true) { return 100; } else { continue; } - } - )cpp"; - llvm::StringLiteral Output = R"cpp( - void test() { - if (true) { continue; } else { return 100; } - } - )cpp"; - checkTransform(ID, Input, Output); - - Input = R"cpp( - void test() { - ^if () { return 100; } else { continue; } - } - )cpp"; - Output = R"cpp( - void test() { - if () { continue; } else { return 100; } - } - )cpp"; - checkTransform(ID, Input, Output); - - // Available in subexpressions of the condition. - checkAvailable(ID, R"cpp( - void test() { - if(2 + [[2]] + 2) { return 2 + 2 + 2; } else { continue; } - } - )cpp"); - // But not as part of the branches. - checkNotAvailable(ID, R"cpp( - void test() { - if(2 + 2 + 2) { return 2 + [[2]] + 2; } else { continue; } - } - )cpp"); - // Range covers the "else" token, so available. - checkAvailable(ID, R"cpp( - void test() { - if(2 + 2 + 2) { return 2 + [[2 + 2; } else { continue;]] } - } - )cpp"); - // Not available in compound statements in condition. - checkNotAvailable(ID, R"cpp( - void test() { - if([]{return [[true]];}()) { return 2 + 2 + 2; } else { continue; } - } - )cpp"); - // Not available if both sides aren't braced. - checkNotAvailable(ID, R"cpp( - void test() { - ^if (1) return; else { return; } - } - )cpp"); - // Only one if statement is supported! - checkNotAvailable(ID, R"cpp( - [[if(1){}else{}if(2){}else{}]] - )cpp"); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/TypeHierarchyTests.cpp b/clang-tools-extra/unittests/clangd/TypeHierarchyTests.cpp deleted file mode 100644 index 79307e28da4..00000000000 --- a/clang-tools-extra/unittests/clangd/TypeHierarchyTests.cpp +++ /dev/null @@ -1,455 +0,0 @@ -//===-- TypeHierarchyTests.cpp ---------------------------*- C++ -*-------===// -// -// 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 "Annotations.h" -#include "ClangdUnit.h" -#include "Compiler.h" -#include "Matchers.h" -#include "SyncAPI.h" -#include "TestFS.h" -#include "TestTU.h" -#include "XRefs.h" -#include "index/FileIndex.h" -#include "index/SymbolCollector.h" -#include "clang/AST/DeclCXX.h" -#include "clang/AST/DeclTemplate.h" -#include "clang/Index/IndexingAction.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/ScopedPrinter.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -using testing::AllOf; -using testing::ElementsAre; -using testing::Eq; -using testing::Field; -using testing::IsEmpty; -using testing::Matcher; -using testing::Pointee; -using testing::UnorderedElementsAreArray; - -// GMock helpers for matching TypeHierarchyItem. -MATCHER_P(WithName, N, "") { return arg.name == N; } -MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; } -MATCHER_P(SelectionRangeIs, R, "") { return arg.selectionRange == R; } -template <class... ParentMatchers> -testing::Matcher<TypeHierarchyItem> Parents(ParentMatchers... ParentsM) { - return Field(&TypeHierarchyItem::parents, HasValue(ElementsAre(ParentsM...))); -} - -TEST(FindRecordTypeAt, TypeOrVariable) { - Annotations Source(R"cpp( -struct Ch^ild2 { - int c; -}; - -int main() { - Ch^ild2 ch^ild2; - ch^ild2.c = 1; -} -)cpp"); - - TestTU TU = TestTU::withCode(Source.code()); - auto AST = TU.build(); - - ASSERT_TRUE(AST.getDiagnostics().empty()); - - for (Position Pt : Source.points()) { - const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt); - EXPECT_EQ(&findDecl(AST, "Child2"), static_cast<const NamedDecl *>(RD)); - } -} - -TEST(FindRecordTypeAt, Method) { - Annotations Source(R"cpp( -struct Child2 { - void met^hod (); - void met^hod (int x); -}; - -int main() { - Child2 child2; - child2.met^hod(5); -} -)cpp"); - - TestTU TU = TestTU::withCode(Source.code()); - auto AST = TU.build(); - - ASSERT_TRUE(AST.getDiagnostics().empty()); - - for (Position Pt : Source.points()) { - const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt); - EXPECT_EQ(&findDecl(AST, "Child2"), static_cast<const NamedDecl *>(RD)); - } -} - -TEST(FindRecordTypeAt, Field) { - Annotations Source(R"cpp( -struct Child2 { - int fi^eld; -}; - -int main() { - Child2 child2; - child2.fi^eld = 5; -} -)cpp"); - - TestTU TU = TestTU::withCode(Source.code()); - auto AST = TU.build(); - - ASSERT_TRUE(AST.getDiagnostics().empty()); - - for (Position Pt : Source.points()) { - const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt); - // A field does not unambiguously specify a record type - // (possible associated reocrd types could be the field's type, - // or the type of the record that the field is a member of). - EXPECT_EQ(nullptr, RD); - } -} - -TEST(TypeParents, SimpleInheritance) { - Annotations Source(R"cpp( -struct Parent { - int a; -}; - -struct Child1 : Parent { - int b; -}; - -struct Child2 : Child1 { - int c; -}; -)cpp"); - - TestTU TU = TestTU::withCode(Source.code()); - auto AST = TU.build(); - - ASSERT_TRUE(AST.getDiagnostics().empty()); - - const CXXRecordDecl *Parent = - dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent")); - const CXXRecordDecl *Child1 = - dyn_cast<CXXRecordDecl>(&findDecl(AST, "Child1")); - const CXXRecordDecl *Child2 = - dyn_cast<CXXRecordDecl>(&findDecl(AST, "Child2")); - - EXPECT_THAT(typeParents(Parent), ElementsAre()); - EXPECT_THAT(typeParents(Child1), ElementsAre(Parent)); - EXPECT_THAT(typeParents(Child2), ElementsAre(Child1)); -} - -TEST(TypeParents, MultipleInheritance) { - Annotations Source(R"cpp( -struct Parent1 { - int a; -}; - -struct Parent2 { - int b; -}; - -struct Parent3 : Parent2 { - int c; -}; - -struct Child : Parent1, Parent3 { - int d; -}; -)cpp"); - - TestTU TU = TestTU::withCode(Source.code()); - auto AST = TU.build(); - - ASSERT_TRUE(AST.getDiagnostics().empty()); - - const CXXRecordDecl *Parent1 = - dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent1")); - const CXXRecordDecl *Parent2 = - dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent2")); - const CXXRecordDecl *Parent3 = - dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent3")); - const CXXRecordDecl *Child = dyn_cast<CXXRecordDecl>(&findDecl(AST, "Child")); - - EXPECT_THAT(typeParents(Parent1), ElementsAre()); - EXPECT_THAT(typeParents(Parent2), ElementsAre()); - EXPECT_THAT(typeParents(Parent3), ElementsAre(Parent2)); - EXPECT_THAT(typeParents(Child), ElementsAre(Parent1, Parent3)); -} - -TEST(TypeParents, ClassTemplate) { - Annotations Source(R"cpp( -struct Parent {}; - -template <typename T> -struct Child : Parent {}; -)cpp"); - - TestTU TU = TestTU::withCode(Source.code()); - auto AST = TU.build(); - - ASSERT_TRUE(AST.getDiagnostics().empty()); - - const CXXRecordDecl *Parent = - dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent")); - const CXXRecordDecl *Child = - dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child"))->getTemplatedDecl(); - - EXPECT_THAT(typeParents(Child), ElementsAre(Parent)); -} - -MATCHER_P(ImplicitSpecOf, ClassTemplate, "") { - const ClassTemplateSpecializationDecl *CTS = - dyn_cast<ClassTemplateSpecializationDecl>(arg); - return CTS && - CTS->getSpecializedTemplate()->getTemplatedDecl() == ClassTemplate && - CTS->getSpecializationKind() == TSK_ImplicitInstantiation; -} - -// This is similar to findDecl(AST, QName), but supports using -// a template-id as a query. -const NamedDecl &findDeclWithTemplateArgs(ParsedAST &AST, - llvm::StringRef Query) { - return findDecl(AST, [&Query](const NamedDecl &ND) { - std::string QName; - llvm::raw_string_ostream OS(QName); - PrintingPolicy Policy(ND.getASTContext().getLangOpts()); - // Use getNameForDiagnostic() which includes the template - // arguments in the printed name. - ND.getNameForDiagnostic(OS, Policy, /*Qualified=*/true); - OS.flush(); - return QName == Query; - }); -} - -TEST(TypeParents, TemplateSpec1) { - Annotations Source(R"cpp( -template <typename T> -struct Parent {}; - -template <> -struct Parent<int> {}; - -struct Child1 : Parent<float> {}; - -struct Child2 : Parent<int> {}; -)cpp"); - - TestTU TU = TestTU::withCode(Source.code()); - auto AST = TU.build(); - - ASSERT_TRUE(AST.getDiagnostics().empty()); - - const CXXRecordDecl *Parent = - dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Parent"))->getTemplatedDecl(); - const CXXRecordDecl *ParentSpec = - dyn_cast<CXXRecordDecl>(&findDeclWithTemplateArgs(AST, "Parent<int>")); - const CXXRecordDecl *Child1 = - dyn_cast<CXXRecordDecl>(&findDecl(AST, "Child1")); - const CXXRecordDecl *Child2 = - dyn_cast<CXXRecordDecl>(&findDecl(AST, "Child2")); - - EXPECT_THAT(typeParents(Child1), ElementsAre(ImplicitSpecOf(Parent))); - EXPECT_THAT(typeParents(Child2), ElementsAre(ParentSpec)); -} - -TEST(TypeParents, TemplateSpec2) { - Annotations Source(R"cpp( -struct Parent {}; - -template <typename T> -struct Child {}; - -template <> -struct Child<int> : Parent {}; -)cpp"); - - TestTU TU = TestTU::withCode(Source.code()); - auto AST = TU.build(); - - ASSERT_TRUE(AST.getDiagnostics().empty()); - - const CXXRecordDecl *Parent = - dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent")); - const CXXRecordDecl *Child = - dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child"))->getTemplatedDecl(); - const CXXRecordDecl *ChildSpec = - dyn_cast<CXXRecordDecl>(&findDeclWithTemplateArgs(AST, "Child<int>")); - - EXPECT_THAT(typeParents(Child), ElementsAre()); - EXPECT_THAT(typeParents(ChildSpec), ElementsAre(Parent)); -} - -TEST(TypeParents, DependentBase) { - Annotations Source(R"cpp( -template <typename T> -struct Parent {}; - -template <typename T> -struct Child1 : Parent<T> {}; - -template <typename T> -struct Child2 : Parent<T>::Type {}; - -template <typename T> -struct Child3 : T {}; -)cpp"); - - TestTU TU = TestTU::withCode(Source.code()); - auto AST = TU.build(); - - ASSERT_TRUE(AST.getDiagnostics().empty()); - - const CXXRecordDecl *Parent = - dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Parent"))->getTemplatedDecl(); - const CXXRecordDecl *Child1 = - dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child1"))->getTemplatedDecl(); - const CXXRecordDecl *Child2 = - dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child2"))->getTemplatedDecl(); - const CXXRecordDecl *Child3 = - dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child3"))->getTemplatedDecl(); - - // For "Parent<T>", use the primary template as a best-effort guess. - EXPECT_THAT(typeParents(Child1), ElementsAre(Parent)); - // For "Parent<T>::Type", there is nothing we can do. - EXPECT_THAT(typeParents(Child2), ElementsAre()); - // Likewise for "T". - EXPECT_THAT(typeParents(Child3), ElementsAre()); -} - -// Parts of getTypeHierarchy() are tested in more detail by the -// FindRecordTypeAt.* and TypeParents.* tests above. This test exercises the -// entire operation. -TEST(TypeHierarchy, Parents) { - Annotations Source(R"cpp( -struct $Parent1Def[[Parent1]] { - int a; -}; - -struct $Parent2Def[[Parent2]] { - int b; -}; - -struct $Parent3Def[[Parent3]] : Parent2 { - int c; -}; - -struct Ch^ild : Parent1, Parent3 { - int d; -}; - -int main() { - Ch^ild ch^ild; - - ch^ild.a = 1; -} -)cpp"); - - TestTU TU = TestTU::withCode(Source.code()); - auto AST = TU.build(); - - for (Position Pt : Source.points()) { - // Set ResolveLevels to 0 because it's only used for Children; - // for Parents, getTypeHierarchy() always returns all levels. - llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy( - AST, Pt, /*ResolveLevels=*/0, TypeHierarchyDirection::Parents); - ASSERT_TRUE(bool(Result)); - EXPECT_THAT( - *Result, - AllOf( - WithName("Child"), WithKind(SymbolKind::Struct), - Parents(AllOf(WithName("Parent1"), WithKind(SymbolKind::Struct), - SelectionRangeIs(Source.range("Parent1Def")), - Parents()), - AllOf(WithName("Parent3"), WithKind(SymbolKind::Struct), - SelectionRangeIs(Source.range("Parent3Def")), - Parents(AllOf( - WithName("Parent2"), WithKind(SymbolKind::Struct), - SelectionRangeIs(Source.range("Parent2Def")), - Parents())))))); - } -} - -TEST(TypeHierarchy, RecursiveHierarchyUnbounded) { - Annotations Source(R"cpp( - template <int N> - struct $SDef[[S]] : S<N + 1> {}; - - S^<0> s; - )cpp"); - - TestTU TU = TestTU::withCode(Source.code()); - auto AST = TU.build(); - - // The compiler should produce a diagnostic for hitting the - // template instantiation depth. - ASSERT_TRUE(!AST.getDiagnostics().empty()); - - // Make sure getTypeHierarchy() doesn't get into an infinite recursion. - // FIXME(nridge): It would be preferable if the type hierarchy gave us type - // names (e.g. "S<0>" for the child and "S<1>" for the parent) rather than - // template names (e.g. "S"). - llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy( - AST, Source.points()[0], 0, TypeHierarchyDirection::Parents); - ASSERT_TRUE(bool(Result)); - EXPECT_THAT( - *Result, - AllOf(WithName("S"), WithKind(SymbolKind::Struct), - Parents(AllOf(WithName("S"), WithKind(SymbolKind::Struct), - SelectionRangeIs(Source.range("SDef")), Parents())))); -} - -TEST(TypeHierarchy, RecursiveHierarchyBounded) { - Annotations Source(R"cpp( - template <int N> - struct $SDef[[S]] : S<N - 1> {}; - - template <> - struct S<0>{}; - - S$SRefConcrete^<2> s; - - template <int N> - struct Foo { - S$SRefDependent^<N> s; - };)cpp"); - - TestTU TU = TestTU::withCode(Source.code()); - auto AST = TU.build(); - - ASSERT_TRUE(AST.getDiagnostics().empty()); - - // Make sure getTypeHierarchy() doesn't get into an infinite recursion - // for either a concrete starting point or a dependent starting point. - llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy( - AST, Source.point("SRefConcrete"), 0, TypeHierarchyDirection::Parents); - ASSERT_TRUE(bool(Result)); - EXPECT_THAT( - *Result, - AllOf(WithName("S"), WithKind(SymbolKind::Struct), - Parents(AllOf(WithName("S"), WithKind(SymbolKind::Struct), - SelectionRangeIs(Source.range("SDef")), Parents())))); - Result = getTypeHierarchy(AST, Source.point("SRefDependent"), 0, - TypeHierarchyDirection::Parents); - ASSERT_TRUE(bool(Result)); - EXPECT_THAT( - *Result, - AllOf(WithName("S"), WithKind(SymbolKind::Struct), - Parents(AllOf(WithName("S"), WithKind(SymbolKind::Struct), - SelectionRangeIs(Source.range("SDef")), Parents())))); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/URITests.cpp b/clang-tools-extra/unittests/clangd/URITests.cpp deleted file mode 100644 index 52ca7b4447c..00000000000 --- a/clang-tools-extra/unittests/clangd/URITests.cpp +++ /dev/null @@ -1,187 +0,0 @@ -//===-- URITests.cpp ---------------------------------*- C++ -*-----------===// -// -// 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 "Matchers.h" -#include "TestFS.h" -#include "URI.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { - -// Force the unittest URI scheme to be linked, -static int LLVM_ATTRIBUTE_UNUSED UnittestSchemeAnchorDest = - UnittestSchemeAnchorSource; - -namespace { - -using ::testing::AllOf; - -MATCHER_P(Scheme, S, "") { return arg.scheme() == S; } -MATCHER_P(Authority, A, "") { return arg.authority() == A; } -MATCHER_P(Body, B, "") { return arg.body() == B; } - -std::string createOrDie(llvm::StringRef AbsolutePath, - llvm::StringRef Scheme = "file") { - auto Uri = URI::create(AbsolutePath, Scheme); - if (!Uri) - llvm_unreachable(toString(Uri.takeError()).c_str()); - return Uri->toString(); -} - -URI parseOrDie(llvm::StringRef Uri) { - auto U = URI::parse(Uri); - if (!U) - llvm_unreachable(toString(U.takeError()).c_str()); - return *U; -} - -TEST(PercentEncodingTest, Encode) { - EXPECT_EQ(URI("x", /*Authority=*/"", "a/b/c").toString(), "x:a/b/c"); - EXPECT_EQ(URI("x", /*Authority=*/"", "a!b;c~").toString(), "x:a%21b%3Bc~"); - EXPECT_EQ(URI("x", /*Authority=*/"", "a123b").toString(), "x:a123b"); - EXPECT_EQ(URI("x", /*Authority=*/"", "a:b;c").toString(), "x:a:b%3Bc"); -} - -TEST(PercentEncodingTest, Decode) { - EXPECT_EQ(parseOrDie("x:a/b/c").body(), "a/b/c"); - - EXPECT_EQ(parseOrDie("s%2b://%3a/%3").scheme(), "s+"); - EXPECT_EQ(parseOrDie("s%2b://%3a/%3").authority(), ":"); - EXPECT_EQ(parseOrDie("s%2b://%3a/%3").body(), "/%3"); - - EXPECT_EQ(parseOrDie("x:a%21b%3ac~").body(), "a!b:c~"); - EXPECT_EQ(parseOrDie("x:a:b%3bc").body(), "a:b;c"); -} - -std::string resolveOrDie(const URI &U, llvm::StringRef HintPath = "") { - auto Path = URI::resolve(U, HintPath); - if (!Path) - llvm_unreachable(toString(Path.takeError()).c_str()); - return *Path; -} - -TEST(URITest, Create) { -#ifdef _WIN32 - EXPECT_THAT(createOrDie("c:\\x\\y\\z"), "file:///c:/x/y/z"); -#else - EXPECT_THAT(createOrDie("/x/y/z"), "file:///x/y/z"); - EXPECT_THAT(createOrDie("/(x)/y/\\ z"), "file:///%28x%29/y/%5C%20z"); -#endif -} - -TEST(URITest, FailedCreate) { - EXPECT_ERROR(URI::create("/x/y/z", "no")); - // Path has to be absolute. - EXPECT_ERROR(URI::create("x/y/z", "file")); -} - -TEST(URITest, Parse) { - EXPECT_THAT(parseOrDie("file://auth/x/y/z"), - AllOf(Scheme("file"), Authority("auth"), Body("/x/y/z"))); - - EXPECT_THAT(parseOrDie("file://au%3dth/%28x%29/y/%5c%20z"), - AllOf(Scheme("file"), Authority("au=th"), Body("/(x)/y/\\ z"))); - - EXPECT_THAT(parseOrDie("file:///%28x%29/y/%5c%20z"), - AllOf(Scheme("file"), Authority(""), Body("/(x)/y/\\ z"))); - EXPECT_THAT(parseOrDie("file:///x/y/z"), - AllOf(Scheme("file"), Authority(""), Body("/x/y/z"))); - EXPECT_THAT(parseOrDie("file:"), - AllOf(Scheme("file"), Authority(""), Body(""))); - EXPECT_THAT(parseOrDie("file:///x/y/z%2"), - AllOf(Scheme("file"), Authority(""), Body("/x/y/z%2"))); - EXPECT_THAT(parseOrDie("http://llvm.org"), - AllOf(Scheme("http"), Authority("llvm.org"), Body(""))); - EXPECT_THAT(parseOrDie("http://llvm.org/"), - AllOf(Scheme("http"), Authority("llvm.org"), Body("/"))); - EXPECT_THAT(parseOrDie("http://llvm.org/D"), - AllOf(Scheme("http"), Authority("llvm.org"), Body("/D"))); - EXPECT_THAT(parseOrDie("http:/"), - AllOf(Scheme("http"), Authority(""), Body("/"))); - EXPECT_THAT(parseOrDie("urn:isbn:0451450523"), - AllOf(Scheme("urn"), Authority(""), Body("isbn:0451450523"))); - EXPECT_THAT( - parseOrDie("file:///c:/windows/system32/"), - AllOf(Scheme("file"), Authority(""), Body("/c:/windows/system32/"))); -} - -TEST(URITest, ParseFailed) { - // Expect ':' in URI. - EXPECT_ERROR(URI::parse("file//x/y/z")); - // Empty. - EXPECT_ERROR(URI::parse("")); - EXPECT_ERROR(URI::parse(":/a/b/c")); - EXPECT_ERROR(URI::parse("\"/a/b/c\" IWYU pragma: abc")); -} - -TEST(URITest, Resolve) { -#ifdef _WIN32 - EXPECT_THAT(resolveOrDie(parseOrDie("file:///c%3a/x/y/z")), "c:\\x\\y\\z"); - EXPECT_THAT(resolveOrDie(parseOrDie("file:///c:/x/y/z")), "c:\\x\\y\\z"); -#else - EXPECT_EQ(resolveOrDie(parseOrDie("file:/a/b/c")), "/a/b/c"); - EXPECT_EQ(resolveOrDie(parseOrDie("file://auth/a/b/c")), "/a/b/c"); - EXPECT_THAT(resolveOrDie(parseOrDie("file://au%3dth/%28x%29/y/%20z")), - "/(x)/y/ z"); - EXPECT_THAT(resolveOrDie(parseOrDie("file:///c:/x/y/z")), "c:/x/y/z"); -#endif - EXPECT_EQ(resolveOrDie(parseOrDie("unittest:///a"), testPath("x")), - testPath("a")); -} - -std::string resolvePathOrDie(llvm::StringRef AbsPath, - llvm::StringRef HintPath = "") { - auto Path = URI::resolvePath(AbsPath, HintPath); - if (!Path) - llvm_unreachable(toString(Path.takeError()).c_str()); - return *Path; -} - -TEST(URITest, ResolvePath) { - StringRef FilePath = -#ifdef _WIN32 - "c:\\x\\y\\z"; -#else - "/a/b/c"; -#endif - EXPECT_EQ(resolvePathOrDie(FilePath), FilePath); - EXPECT_EQ(resolvePathOrDie(testPath("x"), testPath("hint")), testPath("x")); - // HintPath is not in testRoot(); resolution fails. - auto Resolve = URI::resolvePath(testPath("x"), FilePath); - EXPECT_FALSE(Resolve); - llvm::consumeError(Resolve.takeError()); -} - -TEST(URITest, Platform) { - auto Path = testPath("x"); - auto U = URI::create(Path, "file"); - EXPECT_TRUE(static_cast<bool>(U)); - EXPECT_THAT(resolveOrDie(*U), Path); -} - -TEST(URITest, ResolveFailed) { - auto FailedResolve = [](StringRef Uri) { - auto Path = URI::resolve(parseOrDie(Uri)); - if (!Path) { - consumeError(Path.takeError()); - return true; - } - return false; - }; - - // Invalid scheme. - EXPECT_TRUE(FailedResolve("no:/a/b/c")); - // File path needs to be absolute. - EXPECT_TRUE(FailedResolve("file:a/b/c")); -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/XRefsTests.cpp b/clang-tools-extra/unittests/clangd/XRefsTests.cpp deleted file mode 100644 index a9fb5898435..00000000000 --- a/clang-tools-extra/unittests/clangd/XRefsTests.cpp +++ /dev/null @@ -1,1504 +0,0 @@ -//===-- XRefsTests.cpp ---------------------------*- C++ -*--------------===// -// -// 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 "Annotations.h" -#include "ClangdUnit.h" -#include "Compiler.h" -#include "Matchers.h" -#include "SyncAPI.h" -#include "TestFS.h" -#include "TestTU.h" -#include "XRefs.h" -#include "index/FileIndex.h" -#include "index/SymbolCollector.h" -#include "clang/Index/IndexingAction.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/ScopedPrinter.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -using testing::ElementsAre; -using testing::IsEmpty; -using testing::Matcher; -using testing::UnorderedElementsAreArray; - -class IgnoreDiagnostics : public DiagnosticsConsumer { - void onDiagnosticsReady(PathRef File, - std::vector<Diag> Diagnostics) override {} -}; - -MATCHER_P2(FileRange, File, Range, "") { - return Location{URIForFile::canonicalize(File, testRoot()), Range} == arg; -} - -// Extracts ranges from an annotated example, and constructs a matcher for a -// highlight set. Ranges should be named $read/$write as appropriate. -Matcher<const std::vector<DocumentHighlight> &> -HighlightsFrom(const Annotations &Test) { - std::vector<DocumentHighlight> Expected; - auto Add = [&](const Range &R, DocumentHighlightKind K) { - Expected.emplace_back(); - Expected.back().range = R; - Expected.back().kind = K; - }; - for (const auto &Range : Test.ranges()) - Add(Range, DocumentHighlightKind::Text); - for (const auto &Range : Test.ranges("read")) - Add(Range, DocumentHighlightKind::Read); - for (const auto &Range : Test.ranges("write")) - Add(Range, DocumentHighlightKind::Write); - return UnorderedElementsAreArray(Expected); -} - -TEST(HighlightsTest, All) { - const char *Tests[] = { - R"cpp(// Local variable - int main() { - int [[bonjour]]; - $write[[^bonjour]] = 2; - int test1 = $read[[bonjour]]; - } - )cpp", - - R"cpp(// Struct - namespace ns1 { - struct [[MyClass]] { - static void foo([[MyClass]]*) {} - }; - } // namespace ns1 - int main() { - ns1::[[My^Class]]* Params; - } - )cpp", - - R"cpp(// Function - int [[^foo]](int) {} - int main() { - [[foo]]([[foo]](42)); - auto *X = &[[foo]]; - } - )cpp", - - R"cpp(// Function parameter in decl - void foo(int [[^bar]]); - )cpp", - }; - for (const char *Test : Tests) { - Annotations T(Test); - auto AST = TestTU::withCode(T.code()).build(); - EXPECT_THAT(findDocumentHighlights(AST, T.point()), HighlightsFrom(T)) - << Test; - } -} - -MATCHER_P3(Sym, Name, Decl, DefOrNone, "") { - llvm::Optional<Range> Def = DefOrNone; - if (Name != arg.Name) { - *result_listener << "Name is " << arg.Name; - return false; - } - if (Decl != arg.PreferredDeclaration.range) { - *result_listener << "Declaration is " - << llvm::to_string(arg.PreferredDeclaration); - return false; - } - if (Def && !arg.Definition) { - *result_listener << "Has no definition"; - return false; - } - if (Def && arg.Definition->range != *Def) { - *result_listener << "Definition is " << llvm::to_string(arg.Definition); - return false; - } - return true; -} -testing::Matcher<LocatedSymbol> Sym(std::string Name, Range Decl) { - return Sym(Name, Decl, llvm::None); -} -MATCHER_P(Sym, Name, "") { return arg.Name == Name; } - -MATCHER_P(RangeIs, R, "") { return arg.range == R; } - -TEST(LocateSymbol, WithIndex) { - Annotations SymbolHeader(R"cpp( - class $forward[[Forward]]; - class $foo[[Foo]] {}; - - void $f1[[f1]](); - - inline void $f2[[f2]]() {} - )cpp"); - Annotations SymbolCpp(R"cpp( - class $forward[[forward]] {}; - void $f1[[f1]]() {} - )cpp"); - - TestTU TU; - TU.Code = SymbolCpp.code(); - TU.HeaderCode = SymbolHeader.code(); - auto Index = TU.index(); - auto LocateWithIndex = [&Index](const Annotations &Main) { - auto AST = TestTU::withCode(Main.code()).build(); - return clangd::locateSymbolAt(AST, Main.point(), Index.get()); - }; - - Annotations Test(R"cpp(// only declaration in AST. - void [[f1]](); - int main() { - ^f1(); - } - )cpp"); - EXPECT_THAT(LocateWithIndex(Test), - ElementsAre(Sym("f1", Test.range(), SymbolCpp.range("f1")))); - - Test = Annotations(R"cpp(// definition in AST. - void [[f1]]() {} - int main() { - ^f1(); - } - )cpp"); - EXPECT_THAT(LocateWithIndex(Test), - ElementsAre(Sym("f1", SymbolHeader.range("f1"), Test.range()))); - - Test = Annotations(R"cpp(// forward declaration in AST. - class [[Foo]]; - F^oo* create(); - )cpp"); - EXPECT_THAT(LocateWithIndex(Test), - ElementsAre(Sym("Foo", Test.range(), SymbolHeader.range("foo")))); - - Test = Annotations(R"cpp(// defintion in AST. - class [[Forward]] {}; - F^orward create(); - )cpp"); - EXPECT_THAT( - LocateWithIndex(Test), - ElementsAre(Sym("Forward", SymbolHeader.range("forward"), Test.range()))); -} - -TEST(LocateSymbol, WithIndexPreferredLocation) { - Annotations SymbolHeader(R"cpp( - class $[[Proto]] {}; - )cpp"); - TestTU TU; - TU.HeaderCode = SymbolHeader.code(); - TU.HeaderFilename = "x.proto"; // Prefer locations in codegen files. - auto Index = TU.index(); - - Annotations Test(R"cpp(// only declaration in AST. - // Shift to make range different. - class [[Proto]]; - P^roto* create(); - )cpp"); - - auto AST = TestTU::withCode(Test.code()).build(); - auto Locs = clangd::locateSymbolAt(AST, Test.point(), Index.get()); - EXPECT_THAT(Locs, ElementsAre(Sym("Proto", SymbolHeader.range()))); -} - -TEST(LocateSymbol, All) { - // Ranges in tests: - // $decl is the declaration location (if absent, no symbol is located) - // $def is the definition location (if absent, symbol has no definition) - // unnamed range becomes both $decl and $def. - const char *Tests[] = { - R"cpp(// Local variable - int main() { - int [[bonjour]]; - ^bonjour = 2; - int test1 = bonjour; - } - )cpp", - - R"cpp(// Struct - namespace ns1 { - struct [[MyClass]] {}; - } // namespace ns1 - int main() { - ns1::My^Class* Params; - } - )cpp", - - R"cpp(// Function definition via pointer - int [[foo]](int) {} - int main() { - auto *X = &^foo; - } - )cpp", - - R"cpp(// Function declaration via call - int $decl[[foo]](int); - int main() { - return ^foo(42); - } - )cpp", - - R"cpp(// Field - struct Foo { int [[x]]; }; - int main() { - Foo bar; - bar.^x; - } - )cpp", - - R"cpp(// Field, member initializer - struct Foo { - int [[x]]; - Foo() : ^x(0) {} - }; - )cpp", - - R"cpp(// Field, GNU old-style field designator - struct Foo { int [[x]]; }; - int main() { - Foo bar = { ^x : 1 }; - } - )cpp", - - R"cpp(// Field, field designator - struct Foo { int [[x]]; }; - int main() { - Foo bar = { .^x = 2 }; - } - )cpp", - - R"cpp(// Method call - struct Foo { int $decl[[x]](); }; - int main() { - Foo bar; - bar.^x(); - } - )cpp", - - R"cpp(// Typedef - typedef int $decl[[Foo]]; - int main() { - ^Foo bar; - } - )cpp", - - R"cpp(// Template type parameter - template <typename [[T]]> - void foo() { ^T t; } - )cpp", - - R"cpp(// Template template type parameter - template <template<typename> class [[T]]> - void foo() { ^T<int> t; } - )cpp", - - R"cpp(// Namespace - namespace $decl[[ns]] { - struct Foo { static void bar(); } - } // namespace ns - int main() { ^ns::Foo::bar(); } - )cpp", - - R"cpp(// Macro - #define MACRO 0 - #define [[MACRO]] 1 - int main() { return ^MACRO; } - #define MACRO 2 - #undef macro - )cpp", - - R"cpp(// Macro - class TTT { public: int a; }; - #define [[FF]](S) if (int b = S.a) {} - void f() { - TTT t; - F^F(t); - } - )cpp", - - R"cpp(// Macro argument - int [[i]]; - #define ADDRESSOF(X) &X; - int *j = ADDRESSOF(^i); - )cpp", - - R"cpp(// Symbol concatenated inside macro (not supported) - int *pi; - #define POINTER(X) p # X; - int i = *POINTER(^i); - )cpp", - - R"cpp(// Forward class declaration - class Foo; - class [[Foo]] {}; - F^oo* foo(); - )cpp", - - R"cpp(// Function declaration - void foo(); - void g() { f^oo(); } - void [[foo]]() {} - )cpp", - - R"cpp( - #define FF(name) class name##_Test {}; - [[FF]](my); - void f() { my^_Test a; } - )cpp", - - R"cpp( - #define FF() class [[Test]] {}; - FF(); - void f() { T^est a; } - )cpp", - - R"cpp(// explicit template specialization - template <typename T> - struct Foo { void bar() {} }; - - template <> - struct [[Foo]]<int> { void bar() {} }; - - void foo() { - Foo<char> abc; - Fo^o<int> b; - } - )cpp", - - R"cpp(// implicit template specialization - template <typename T> - struct [[Foo]] { void bar() {} }; - template <> - struct Foo<int> { void bar() {} }; - void foo() { - Fo^o<char> abc; - Foo<int> b; - } - )cpp", - - R"cpp(// partial template specialization - template <typename T> - struct Foo { void bar() {} }; - template <typename T> - struct [[Foo]]<T*> { void bar() {} }; - ^Foo<int*> x; - )cpp", - - R"cpp(// function template specializations - template <class T> - void foo(T) {} - template <> - void [[foo]](int) {} - void bar() { - fo^o(10); - } - )cpp", - - R"cpp(// variable template decls - template <class T> - T var = T(); - - template <> - double [[var]]<int> = 10; - - double y = va^r<int>; - )cpp", - - R"cpp(// No implicit constructors - class X { - X(X&& x) = default; - }; - X [[makeX]]() {} - void foo() { - auto x = m^akeX(); - } - )cpp", - }; - for (const char *Test : Tests) { - Annotations T(Test); - llvm::Optional<Range> WantDecl; - llvm::Optional<Range> WantDef; - if (!T.ranges().empty()) - WantDecl = WantDef = T.range(); - if (!T.ranges("decl").empty()) - WantDecl = T.range("decl"); - if (!T.ranges("def").empty()) - WantDef = T.range("def"); - - auto AST = TestTU::withCode(T.code()).build(); - auto Results = locateSymbolAt(AST, T.point()); - - if (!WantDecl) { - EXPECT_THAT(Results, IsEmpty()) << Test; - } else { - ASSERT_THAT(Results, ::testing::SizeIs(1)) << Test; - EXPECT_EQ(Results[0].PreferredDeclaration.range, *WantDecl) << Test; - llvm::Optional<Range> GotDef; - if (Results[0].Definition) - GotDef = Results[0].Definition->range; - EXPECT_EQ(WantDef, GotDef) << Test; - } - } -} - -TEST(LocateSymbol, Ambiguous) { - auto T = Annotations(R"cpp( - struct Foo { - Foo(); - Foo(Foo&&); - Foo(const char*); - }; - - Foo f(); - - void g(Foo foo); - - void call() { - const char* str = "123"; - Foo a = $1^str; - Foo b = Foo($2^str); - Foo c = $3^f(); - $4^g($5^f()); - g($6^str); - Foo ab$7^c; - Foo ab$8^cd("asdf"); - Foo foox = Fo$9^o("asdf"); - } - )cpp"); - auto AST = TestTU::withCode(T.code()).build(); - // Ordered assertions are deliberate: we expect a predictable order. - EXPECT_THAT(locateSymbolAt(AST, T.point("1")), ElementsAre(Sym("str"))); - EXPECT_THAT(locateSymbolAt(AST, T.point("2")), ElementsAre(Sym("str"))); - EXPECT_THAT(locateSymbolAt(AST, T.point("3")), ElementsAre(Sym("f"))); - EXPECT_THAT(locateSymbolAt(AST, T.point("4")), ElementsAre(Sym("g"))); - EXPECT_THAT(locateSymbolAt(AST, T.point("5")), ElementsAre(Sym("f"))); - EXPECT_THAT(locateSymbolAt(AST, T.point("6")), ElementsAre(Sym("str"))); - EXPECT_THAT(locateSymbolAt(AST, T.point("7")), ElementsAre(Sym("abc"))); - EXPECT_THAT(locateSymbolAt(AST, T.point("8")), - ElementsAre(Sym("Foo"), Sym("abcd"))); - EXPECT_THAT(locateSymbolAt(AST, T.point("9")), - // First one is class definition, second is the constructor. - ElementsAre(Sym("Foo"), Sym("Foo"))); -} - -TEST(LocateSymbol, RelPathsInCompileCommand) { - // The source is in "/clangd-test/src". - // We build in "/clangd-test/build". - - Annotations SourceAnnotations(R"cpp( -#include "header_in_preamble.h" -int [[foo]]; -#include "header_not_in_preamble.h" -int baz = f$p1^oo + bar_pre$p2^amble + bar_not_pre$p3^amble; -)cpp"); - - Annotations HeaderInPreambleAnnotations(R"cpp( -int [[bar_preamble]]; -)cpp"); - - Annotations HeaderNotInPreambleAnnotations(R"cpp( -int [[bar_not_preamble]]; -)cpp"); - - // Make the compilation paths appear as ../src/foo.cpp in the compile - // commands. - SmallString<32> RelPathPrefix(".."); - llvm::sys::path::append(RelPathPrefix, "src"); - std::string BuildDir = testPath("build"); - MockCompilationDatabase CDB(BuildDir, RelPathPrefix); - - IgnoreDiagnostics DiagConsumer; - MockFSProvider FS; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - // Fill the filesystem. - auto FooCpp = testPath("src/foo.cpp"); - FS.Files[FooCpp] = ""; - auto HeaderInPreambleH = testPath("src/header_in_preamble.h"); - FS.Files[HeaderInPreambleH] = HeaderInPreambleAnnotations.code(); - auto HeaderNotInPreambleH = testPath("src/header_not_in_preamble.h"); - FS.Files[HeaderNotInPreambleH] = HeaderNotInPreambleAnnotations.code(); - - runAddDocument(Server, FooCpp, SourceAnnotations.code()); - - // Go to a definition in main source file. - auto Locations = - runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("p1")); - EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo", SourceAnnotations.range()))); - - // Go to a definition in header_in_preamble.h. - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("p2")); - EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; - EXPECT_THAT( - *Locations, - ElementsAre(Sym("bar_preamble", HeaderInPreambleAnnotations.range()))); - - // Go to a definition in header_not_in_preamble.h. - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("p3")); - EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error"; - EXPECT_THAT(*Locations, - ElementsAre(Sym("bar_not_preamble", - HeaderNotInPreambleAnnotations.range()))); -} - -TEST(Hover, All) { - struct OneTest { - StringRef Input; - StringRef ExpectedHover; - }; - - OneTest Tests[] = { - { - R"cpp(// No hover - ^int main() { - } - )cpp", - "", - }, - { - R"cpp(// Local variable - int main() { - int bonjour; - ^bonjour = 2; - int test1 = bonjour; - } - )cpp", - "Declared in function main\n\nint bonjour", - }, - { - R"cpp(// Local variable in method - struct s { - void method() { - int bonjour; - ^bonjour = 2; - } - }; - )cpp", - "Declared in function s::method\n\nint bonjour", - }, - { - R"cpp(// Struct - namespace ns1 { - struct MyClass {}; - } // namespace ns1 - int main() { - ns1::My^Class* Params; - } - )cpp", - "Declared in namespace ns1\n\nstruct MyClass {}", - }, - { - R"cpp(// Class - namespace ns1 { - class MyClass {}; - } // namespace ns1 - int main() { - ns1::My^Class* Params; - } - )cpp", - "Declared in namespace ns1\n\nclass MyClass {}", - }, - { - R"cpp(// Union - namespace ns1 { - union MyUnion { int x; int y; }; - } // namespace ns1 - int main() { - ns1::My^Union Params; - } - )cpp", - "Declared in namespace ns1\n\nunion MyUnion {}", - }, - { - R"cpp(// Function definition via pointer - int foo(int) {} - int main() { - auto *X = &^foo; - } - )cpp", - "Declared in global namespace\n\nint foo(int)", - }, - { - R"cpp(// Function declaration via call - int foo(int); - int main() { - return ^foo(42); - } - )cpp", - "Declared in global namespace\n\nint foo(int)", - }, - { - R"cpp(// Field - struct Foo { int x; }; - int main() { - Foo bar; - bar.^x; - } - )cpp", - "Declared in struct Foo\n\nint x", - }, - { - R"cpp(// Field with initialization - struct Foo { int x = 5; }; - int main() { - Foo bar; - bar.^x; - } - )cpp", - "Declared in struct Foo\n\nint x = 5", - }, - { - R"cpp(// Static field - struct Foo { static int x; }; - int main() { - Foo::^x; - } - )cpp", - "Declared in struct Foo\n\nstatic int x", - }, - { - R"cpp(// Field, member initializer - struct Foo { - int x; - Foo() : ^x(0) {} - }; - )cpp", - "Declared in struct Foo\n\nint x", - }, - { - R"cpp(// Field, GNU old-style field designator - struct Foo { int x; }; - int main() { - Foo bar = { ^x : 1 }; - } - )cpp", - "Declared in struct Foo\n\nint x", - }, - { - R"cpp(// Field, field designator - struct Foo { int x; }; - int main() { - Foo bar = { .^x = 2 }; - } - )cpp", - "Declared in struct Foo\n\nint x", - }, - { - R"cpp(// Method call - struct Foo { int x(); }; - int main() { - Foo bar; - bar.^x(); - } - )cpp", - "Declared in struct Foo\n\nint x()", - }, - { - R"cpp(// Static method call - struct Foo { static int x(); }; - int main() { - Foo::^x(); - } - )cpp", - "Declared in struct Foo\n\nstatic int x()", - }, - { - R"cpp(// Typedef - typedef int Foo; - int main() { - ^Foo bar; - } - )cpp", - "Declared in global namespace\n\ntypedef int Foo", - }, - { - R"cpp(// Namespace - namespace ns { - struct Foo { static void bar(); } - } // namespace ns - int main() { ^ns::Foo::bar(); } - )cpp", - "Declared in global namespace\n\nnamespace ns {\n}", - }, - { - R"cpp(// Anonymous namespace - namespace ns { - namespace { - int foo; - } // anonymous namespace - } // namespace ns - int main() { ns::f^oo++; } - )cpp", - "Declared in namespace ns::(anonymous)\n\nint foo", - }, - { - R"cpp(// Macro - #define MACRO 0 - #define MACRO 1 - int main() { return ^MACRO; } - #define MACRO 2 - #undef macro - )cpp", - "#define MACRO 1", - }, - { - R"cpp(// Macro - #define MACRO 0 - #define MACRO2 ^MACRO - )cpp", - "#define MACRO 0", - }, - { - R"cpp(// Macro - #define MACRO {\ - return 0;\ - } - int main() ^MACRO - )cpp", - R"cpp(#define MACRO {\ - return 0;\ - })cpp", - }, - { - R"cpp(// Forward class declaration - class Foo; - class Foo {}; - F^oo* foo(); - )cpp", - "Declared in global namespace\n\nclass Foo {}", - }, - { - R"cpp(// Function declaration - void foo(); - void g() { f^oo(); } - void foo() {} - )cpp", - "Declared in global namespace\n\nvoid foo()", - }, - { - R"cpp(// Enum declaration - enum Hello { - ONE, TWO, THREE, - }; - void foo() { - Hel^lo hello = ONE; - } - )cpp", - "Declared in global namespace\n\nenum Hello {\n}", - }, - { - R"cpp(// Enumerator - enum Hello { - ONE, TWO, THREE, - }; - void foo() { - Hello hello = O^NE; - } - )cpp", - "Declared in enum Hello\n\nONE", - }, - { - R"cpp(// Enumerator in anonymous enum - enum { - ONE, TWO, THREE, - }; - void foo() { - int hello = O^NE; - } - )cpp", - "Declared in enum (anonymous)\n\nONE", - }, - { - R"cpp(// Global variable - static int hey = 10; - void foo() { - he^y++; - } - )cpp", - "Declared in global namespace\n\nstatic int hey = 10", - }, - { - R"cpp(// Global variable in namespace - namespace ns1 { - static int hey = 10; - } - void foo() { - ns1::he^y++; - } - )cpp", - "Declared in namespace ns1\n\nstatic int hey = 10", - }, - { - R"cpp(// Field in anonymous struct - static struct { - int hello; - } s; - void foo() { - s.he^llo++; - } - )cpp", - "Declared in struct (anonymous)\n\nint hello", - }, - { - R"cpp(// Templated function - template <typename T> - T foo() { - return 17; - } - void g() { auto x = f^oo<int>(); } - )cpp", - "Declared in global namespace\n\ntemplate <typename T> T foo()", - }, - { - R"cpp(// Anonymous union - struct outer { - union { - int abc, def; - } v; - }; - void g() { struct outer o; o.v.d^ef++; } - )cpp", - "Declared in union outer::(anonymous)\n\nint def", - }, - { - R"cpp(// Nothing - void foo() { - ^ - } - )cpp", - "", - }, - { - R"cpp(// Simple initialization with auto - void foo() { - ^auto i = 1; - } - )cpp", - "int", - }, - { - R"cpp(// Simple initialization with const auto - void foo() { - const ^auto i = 1; - } - )cpp", - "int", - }, - { - R"cpp(// Simple initialization with const auto& - void foo() { - const ^auto& i = 1; - } - )cpp", - "int", - }, - { - R"cpp(// Simple initialization with auto& - void foo() { - ^auto& i = 1; - } - )cpp", - "int", - }, - { - R"cpp(// Simple initialization with auto* - void foo() { - int a = 1; - ^auto* i = &a; - } - )cpp", - "int", - }, - { - R"cpp(// Auto with initializer list. - namespace std - { - template<class _E> - class initializer_list {}; - } - void foo() { - ^auto i = {1,2}; - } - )cpp", - "class std::initializer_list<int>", - }, - { - R"cpp(// User defined conversion to auto - struct Bar { - operator ^auto() const { return 10; } - }; - )cpp", - "int", - }, - { - R"cpp(// Simple initialization with decltype(auto) - void foo() { - ^decltype(auto) i = 1; - } - )cpp", - "int", - }, - { - R"cpp(// Simple initialization with const decltype(auto) - void foo() { - const int j = 0; - ^decltype(auto) i = j; - } - )cpp", - "const int", - }, - { - R"cpp(// Simple initialization with const& decltype(auto) - void foo() { - int k = 0; - const int& j = k; - ^decltype(auto) i = j; - } - )cpp", - "const int &", - }, - { - R"cpp(// Simple initialization with & decltype(auto) - void foo() { - int k = 0; - int& j = k; - ^decltype(auto) i = j; - } - )cpp", - "int &", - }, - { - R"cpp(// decltype with initializer list: nothing - namespace std - { - template<class _E> - class initializer_list {}; - } - void foo() { - ^decltype(auto) i = {1,2}; - } - )cpp", - "", - }, - { - R"cpp(// simple trailing return type - ^auto main() -> int { - return 0; - } - )cpp", - "int", - }, - { - R"cpp(// auto function return with trailing type - struct Bar {}; - ^auto test() -> decltype(Bar()) { - return Bar(); - } - )cpp", - "struct Bar", - }, - { - R"cpp(// trailing return type - struct Bar {}; - auto test() -> ^decltype(Bar()) { - return Bar(); - } - )cpp", - "struct Bar", - }, - { - R"cpp(// auto in function return - struct Bar {}; - ^auto test() { - return Bar(); - } - )cpp", - "struct Bar", - }, - { - R"cpp(// auto& in function return - struct Bar {}; - ^auto& test() { - return Bar(); - } - )cpp", - "struct Bar", - }, - { - R"cpp(// auto* in function return - struct Bar {}; - ^auto* test() { - Bar* bar; - return bar; - } - )cpp", - "struct Bar", - }, - { - R"cpp(// const auto& in function return - struct Bar {}; - const ^auto& test() { - return Bar(); - } - )cpp", - "struct Bar", - }, - { - R"cpp(// decltype(auto) in function return - struct Bar {}; - ^decltype(auto) test() { - return Bar(); - } - )cpp", - "struct Bar", - }, - { - R"cpp(// decltype(auto) reference in function return - struct Bar {}; - ^decltype(auto) test() { - int a; - return (a); - } - )cpp", - "int &", - }, - { - R"cpp(// decltype lvalue reference - void foo() { - int I = 0; - ^decltype(I) J = I; - } - )cpp", - "int", - }, - { - R"cpp(// decltype lvalue reference - void foo() { - int I= 0; - int &K = I; - ^decltype(K) J = I; - } - )cpp", - "int &", - }, - { - R"cpp(// decltype lvalue reference parenthesis - void foo() { - int I = 0; - ^decltype((I)) J = I; - } - )cpp", - "int &", - }, - { - R"cpp(// decltype rvalue reference - void foo() { - int I = 0; - ^decltype(static_cast<int&&>(I)) J = static_cast<int&&>(I); - } - )cpp", - "int &&", - }, - { - R"cpp(// decltype rvalue reference function call - int && bar(); - void foo() { - int I = 0; - ^decltype(bar()) J = bar(); - } - )cpp", - "int &&", - }, - { - R"cpp(// decltype of function with trailing return type. - struct Bar {}; - auto test() -> decltype(Bar()) { - return Bar(); - } - void foo() { - ^decltype(test()) i = test(); - } - )cpp", - "struct Bar", - }, - { - R"cpp(// decltype of var with decltype. - void foo() { - int I = 0; - decltype(I) J = I; - ^decltype(J) K = J; - } - )cpp", - "int", - }, - { - R"cpp(// structured binding. Not supported yet - struct Bar {}; - void foo() { - Bar a[2]; - ^auto [x,y] = a; - } - )cpp", - "", - }, - { - R"cpp(// Template auto parameter. Nothing (Not useful). - template<^auto T> - void func() { - } - void foo() { - func<1>(); - } - )cpp", - "", - }, - { - R"cpp(// More compilcated structured types. - int bar(); - ^auto (*foo)() = bar; - )cpp", - "int", - }, - }; - - for (const OneTest &Test : Tests) { - Annotations T(Test.Input); - TestTU TU = TestTU::withCode(T.code()); - TU.ExtraArgs.push_back("-std=c++17"); - auto AST = TU.build(); - if (auto H = getHover(AST, T.point())) { - EXPECT_NE("", Test.ExpectedHover) << Test.Input; - EXPECT_EQ(H->contents.value, Test.ExpectedHover.str()) << Test.Input; - } else - EXPECT_EQ("", Test.ExpectedHover.str()) << Test.Input; - } -} - -TEST(GoToInclude, All) { - MockFSProvider FS; - IgnoreDiagnostics DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - const char *SourceContents = R"cpp( - #include ^"$2^foo.h$3^" - #include "$4^invalid.h" - int b = a; - // test - int foo; - #in$5^clude "$6^foo.h"$7^ - )cpp"; - Annotations SourceAnnotations(SourceContents); - FS.Files[FooCpp] = SourceAnnotations.code(); - auto FooH = testPath("foo.h"); - - const char *HeaderContents = R"cpp([[]]#pragma once - int a; - )cpp"; - Annotations HeaderAnnotations(HeaderContents); - FS.Files[FooH] = HeaderAnnotations.code(); - - Server.addDocument(FooH, HeaderAnnotations.code()); - Server.addDocument(FooCpp, SourceAnnotations.code()); - - // Test include in preamble. - auto Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point()); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); - - // Test include in preamble, last char. - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("2")); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); - - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("3")); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); - - // Test include outside of preamble. - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("6")); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); - - // Test a few positions that do not result in Locations. - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("4")); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, IsEmpty()); - - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("5")); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); - - Locations = runLocateSymbolAt(Server, FooCpp, SourceAnnotations.point("7")); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); - - // Objective C #import directive. - Annotations ObjC(R"objc( - #import "^foo.h" - )objc"); - auto FooM = testPath("foo.m"); - FS.Files[FooM] = ObjC.code(); - - Server.addDocument(FooM, ObjC.code()); - Locations = runLocateSymbolAt(Server, FooM, ObjC.point()); - ASSERT_TRUE(bool(Locations)) << "locateSymbolAt returned an error"; - EXPECT_THAT(*Locations, ElementsAre(Sym("foo.h", HeaderAnnotations.range()))); -} - -TEST(LocateSymbol, WithPreamble) { - // Test stragety: AST should always use the latest preamble instead of last - // good preamble. - MockFSProvider FS; - IgnoreDiagnostics DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - // The trigger locations must be the same. - Annotations FooWithHeader(R"cpp(#include "fo^o.h")cpp"); - Annotations FooWithoutHeader(R"cpp(double [[fo^o]]();)cpp"); - - FS.Files[FooCpp] = FooWithHeader.code(); - - auto FooH = testPath("foo.h"); - Annotations FooHeader(R"cpp([[]])cpp"); - FS.Files[FooH] = FooHeader.code(); - - runAddDocument(Server, FooCpp, FooWithHeader.code()); - // LocateSymbol goes to a #include file: the result comes from the preamble. - EXPECT_THAT( - cantFail(runLocateSymbolAt(Server, FooCpp, FooWithHeader.point())), - ElementsAre(Sym("foo.h", FooHeader.range()))); - - // Only preamble is built, and no AST is built in this request. - Server.addDocument(FooCpp, FooWithoutHeader.code(), WantDiagnostics::No); - // We build AST here, and it should use the latest preamble rather than the - // stale one. - EXPECT_THAT( - cantFail(runLocateSymbolAt(Server, FooCpp, FooWithoutHeader.point())), - ElementsAre(Sym("foo", FooWithoutHeader.range()))); - - // Reset test environment. - runAddDocument(Server, FooCpp, FooWithHeader.code()); - // Both preamble and AST are built in this request. - Server.addDocument(FooCpp, FooWithoutHeader.code(), WantDiagnostics::Yes); - // Use the AST being built in above request. - EXPECT_THAT( - cantFail(runLocateSymbolAt(Server, FooCpp, FooWithoutHeader.point())), - ElementsAre(Sym("foo", FooWithoutHeader.range()))); -} - -TEST(FindReferences, WithinAST) { - const char *Tests[] = { - R"cpp(// Local variable - int main() { - int [[foo]]; - [[^foo]] = 2; - int test1 = [[foo]]; - } - )cpp", - - R"cpp(// Struct - namespace ns1 { - struct [[Foo]] {}; - } // namespace ns1 - int main() { - ns1::[[Fo^o]]* Params; - } - )cpp", - - R"cpp(// Forward declaration - class [[Foo]]; - class [[Foo]] {} - int main() { - [[Fo^o]] foo; - } - )cpp", - - R"cpp(// Function - int [[foo]](int) {} - int main() { - auto *X = &[[^foo]]; - [[foo]](42) - } - )cpp", - - R"cpp(// Field - struct Foo { - int [[foo]]; - Foo() : [[foo]](0) {} - }; - int main() { - Foo f; - f.[[f^oo]] = 1; - } - )cpp", - - R"cpp(// Method call - struct Foo { int [[foo]](); }; - int Foo::[[foo]]() {} - int main() { - Foo f; - f.[[^foo]](); - } - )cpp", - - R"cpp(// Constructor - struct Foo { - [[F^oo]](int); - }; - void foo() { - Foo f = [[Foo]](42); - } - )cpp", - - R"cpp(// Typedef - typedef int [[Foo]]; - int main() { - [[^Foo]] bar; - } - )cpp", - - R"cpp(// Namespace - namespace [[ns]] { - struct Foo {}; - } // namespace ns - int main() { [[^ns]]::Foo foo; } - )cpp", - }; - for (const char *Test : Tests) { - Annotations T(Test); - auto AST = TestTU::withCode(T.code()).build(); - std::vector<Matcher<Location>> ExpectedLocations; - for (const auto &R : T.ranges()) - ExpectedLocations.push_back(RangeIs(R)); - EXPECT_THAT(findReferences(AST, T.point(), 0), - ElementsAreArray(ExpectedLocations)) - << Test; - } -} - -TEST(FindReferences, ExplicitSymbols) { - const char *Tests[] = { - R"cpp( - struct Foo { Foo* [self]() const; }; - void f() { - if (Foo* T = foo.[^self]()) {} // Foo member call expr. - } - )cpp", - - R"cpp( - struct Foo { Foo(int); }; - Foo f() { - int [b]; - return [^b]; // Foo constructor expr. - } - )cpp", - - R"cpp( - struct Foo {}; - void g(Foo); - Foo [f](); - void call() { - g([^f]()); // Foo constructor expr. - } - )cpp", - - R"cpp( - void [foo](int); - void [foo](double); - - namespace ns { - using ::[fo^o]; - } - )cpp", - }; - for (const char *Test : Tests) { - Annotations T(Test); - auto AST = TestTU::withCode(T.code()).build(); - std::vector<Matcher<Location>> ExpectedLocations; - for (const auto &R : T.ranges()) - ExpectedLocations.push_back(RangeIs(R)); - EXPECT_THAT(findReferences(AST, T.point(), 0), - ElementsAreArray(ExpectedLocations)) - << Test; - } -} - -TEST(FindReferences, NeedsIndex) { - const char *Header = "int foo();"; - Annotations Main("int main() { [[f^oo]](); }"); - TestTU TU; - TU.Code = Main.code(); - TU.HeaderCode = Header; - auto AST = TU.build(); - - // References in main file are returned without index. - EXPECT_THAT(findReferences(AST, Main.point(), 0, /*Index=*/nullptr), - ElementsAre(RangeIs(Main.range()))); - Annotations IndexedMain(R"cpp( - int main() { [[f^oo]](); } - )cpp"); - - // References from indexed files are included. - TestTU IndexedTU; - IndexedTU.Code = IndexedMain.code(); - IndexedTU.Filename = "Indexed.cpp"; - IndexedTU.HeaderCode = Header; - EXPECT_THAT(findReferences(AST, Main.point(), 0, IndexedTU.index().get()), - ElementsAre(RangeIs(Main.range()), RangeIs(IndexedMain.range()))); - - EXPECT_EQ(1u, findReferences(AST, Main.point(), /*Limit*/ 1, - IndexedTU.index().get()) - .size()); - - // If the main file is in the index, we don't return duplicates. - // (even if the references are in a different location) - TU.Code = ("\n\n" + Main.code()).str(); - EXPECT_THAT(findReferences(AST, Main.point(), 0, TU.index().get()), - ElementsAre(RangeIs(Main.range()))); -} - -TEST(FindReferences, NoQueryForLocalSymbols) { - struct RecordingIndex : public MemIndex { - mutable Optional<llvm::DenseSet<SymbolID>> RefIDs; - void refs(const RefsRequest &Req, - llvm::function_ref<void(const Ref &)>) const override { - RefIDs = Req.IDs; - } - }; - - struct Test { - StringRef AnnotatedCode; - bool WantQuery; - } Tests[] = { - {"int ^x;", true}, - // For now we don't assume header structure which would allow skipping. - {"namespace { int ^x; }", true}, - {"static int ^x;", true}, - // Anything in a function certainly can't be referenced though. - {"void foo() { int ^x; }", false}, - {"void foo() { struct ^x{}; }", false}, - {"auto lambda = []{ int ^x; };", false}, - }; - for (Test T : Tests) { - Annotations File(T.AnnotatedCode); - RecordingIndex Rec; - auto AST = TestTU::withCode(File.code()).build(); - findReferences(AST, File.point(), 0, &Rec); - if (T.WantQuery) - EXPECT_NE(Rec.RefIDs, None) << T.AnnotatedCode; - else - EXPECT_EQ(Rec.RefIDs, None) << T.AnnotatedCode; - } -} - -} // namespace -} // namespace clangd -} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/xpc/CMakeLists.txt b/clang-tools-extra/unittests/clangd/xpc/CMakeLists.txt deleted file mode 100644 index 229ad5af47e..00000000000 --- a/clang-tools-extra/unittests/clangd/xpc/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -set(LLVM_LINK_COMPONENTS - support - ) - -get_filename_component(CLANGD_SOURCE_DIR - ${CMAKE_CURRENT_SOURCE_DIR}/../../clangd REALPATH) -include_directories( - ${CLANGD_SOURCE_DIR} - ) - -add_extra_unittest(ClangdXpcTests - ConversionTests.cpp - ) - -target_link_libraries(ClangdXpcTests - PRIVATE - clangdXpcJsonConversions - clangDaemon - LLVMSupport - LLVMTestingSupport - ) diff --git a/clang-tools-extra/unittests/clangd/xpc/ConversionTests.cpp b/clang-tools-extra/unittests/clangd/xpc/ConversionTests.cpp deleted file mode 100644 index 5d0efd83509..00000000000 --- a/clang-tools-extra/unittests/clangd/xpc/ConversionTests.cpp +++ /dev/null @@ -1,35 +0,0 @@ -//===-- ConversionTests.cpp --------------------------*- C++ -*-----------===// -// -// 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 "xpc/Conversion.h" -#include "gtest/gtest.h" - -#include <limits> - -namespace clang { -namespace clangd { -namespace { - -using namespace llvm; - -TEST(JsonXpcConversionTest, JsonToXpcToJson) { - - for (auto &testcase : - {json::Value(false), json::Value(3.14), json::Value(42), - json::Value(-100), json::Value("foo"), json::Value(""), - json::Value("123"), json::Value(" "), - json::Value{true, "foo", nullptr, 42}, - json::Value(json::Object{ - {"a", true}, {"b", "foo"}, {"c", nullptr}, {"d", 42}})}) { - EXPECT_TRUE(testcase == xpcToJson(jsonToXpc(testcase))) << testcase; - } -} - -} // namespace -} // namespace clangd -} // namespace clang |

