287 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			287 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===------ 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 "Headers.h"
 | |
| #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::EndsWith;
 | |
| 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.Flags & IncludeGraphNode::SourceFlag::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(std::string(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(
 | |
|         Opts, [&](SymbolSlab S) { IndexFile.Symbols = std::move(S); },
 | |
|         [&](RefSlab R) { IndexFile.Refs = std::move(R); },
 | |
|         [&](RelationSlab R) { IndexFile.Relations = 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(std::string(MainFilePath));
 | |
| 
 | |
|     tooling::ToolInvocation Invocation(
 | |
|         Args, std::move(Action), 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::getMemBufferCopy(Content));
 | |
|     FilePaths.push_back(std::string(Path));
 | |
|   }
 | |
| 
 | |
| protected:
 | |
|   SymbolCollector::Options Opts;
 | |
|   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")));
 | |
| }
 | |
| 
 | |
| TEST_F(IndexActionTest, SkipFiles) {
 | |
|   std::string MainFilePath = testPath("main.cpp");
 | |
|   addFile(MainFilePath, R"cpp(
 | |
|     // clang-format off
 | |
|     #include "good.h"
 | |
|     #include "bad.h"
 | |
|     // clang-format on
 | |
|   )cpp");
 | |
|   addFile(testPath("good.h"), R"cpp(
 | |
|     struct S { int s; };
 | |
|     void f1() { S f; }
 | |
|     auto unskippable1() { return S(); }
 | |
|   )cpp");
 | |
|   addFile(testPath("bad.h"), R"cpp(
 | |
|     struct T { S t; };
 | |
|     void f2() { S f; }
 | |
|     auto unskippable2() { return S(); }
 | |
|   )cpp");
 | |
|   Opts.FileFilter = [](const SourceManager &SM, FileID F) {
 | |
|     return !SM.getFileEntryForID(F)->getName().endswith("bad.h");
 | |
|   };
 | |
|   IndexFileIn IndexFile = runIndexingAction(MainFilePath, {"-std=c++14"});
 | |
|   EXPECT_THAT(*IndexFile.Symbols,
 | |
|               UnorderedElementsAre(HasName("S"), HasName("s"), HasName("f1"),
 | |
|                                    HasName("unskippable1")));
 | |
|   for (const auto &Pair : *IndexFile.Refs)
 | |
|     for (const auto &Ref : Pair.second)
 | |
|       EXPECT_THAT(Ref.Location.FileURI, EndsWith("good.h"));
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| } // namespace clangd
 | |
| } // namespace clang
 |