clang-tools  10.0.0svn
GlobalCompilationDatabaseTests.cpp
Go to the documentation of this file.
1 //===-- GlobalCompilationDatabaseTests.cpp ----------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
10 
11 #include "Path.h"
12 #include "TestFS.h"
13 #include "clang/Tooling/CompilationDatabase.h"
14 #include "llvm/ADT/Optional.h"
15 #include "llvm/ADT/SmallString.h"
16 #include "llvm/ADT/StringExtras.h"
17 #include "llvm/ADT/StringRef.h"
18 #include "llvm/Support/FileSystem.h"
19 #include "llvm/Support/FormatVariadic.h"
20 #include "llvm/Support/MemoryBuffer.h"
21 #include "llvm/Support/Path.h"
22 #include "llvm/Support/raw_ostream.h"
23 #include "gmock/gmock.h"
24 #include "gtest/gtest.h"
25 #include <fstream>
26 #include <string>
27 
28 namespace clang {
29 namespace clangd {
30 namespace {
31 using ::testing::AllOf;
32 using ::testing::Contains;
33 using ::testing::ElementsAre;
34 using ::testing::EndsWith;
35 using ::testing::HasSubstr;
36 using ::testing::IsEmpty;
37 using ::testing::Not;
38 using ::testing::StartsWith;
39 using ::testing::UnorderedElementsAre;
40 
41 TEST(GlobalCompilationDatabaseTest, FallbackCommand) {
42  DirectoryBasedGlobalCompilationDatabase DB(None);
43  auto Cmd = DB.getFallbackCommand(testPath("foo/bar.cc"));
44  EXPECT_EQ(Cmd.Directory, testPath("foo"));
45  EXPECT_THAT(Cmd.CommandLine,
46  ElementsAre(EndsWith("clang"), testPath("foo/bar.cc")));
47  EXPECT_EQ(Cmd.Output, "");
48 
49  // .h files have unknown language, so they are parsed liberally as obj-c++.
50  Cmd = DB.getFallbackCommand(testPath("foo/bar.h"));
51  EXPECT_THAT(Cmd.CommandLine,
52  ElementsAre(EndsWith("clang"), "-xobjective-c++-header",
53  testPath("foo/bar.h")));
54  Cmd = DB.getFallbackCommand(testPath("foo/bar"));
55  EXPECT_THAT(Cmd.CommandLine,
56  ElementsAre(EndsWith("clang"), "-xobjective-c++-header",
57  testPath("foo/bar")));
58 }
59 
60 static tooling::CompileCommand cmd(llvm::StringRef File, llvm::StringRef Arg) {
61  return tooling::CompileCommand(testRoot(), File, {"clang", Arg, File}, "");
62 }
63 
64 class OverlayCDBTest : public ::testing::Test {
65  class BaseCDB : public GlobalCompilationDatabase {
66  public:
67  llvm::Optional<tooling::CompileCommand>
68  getCompileCommand(llvm::StringRef File) const override {
69  if (File == testPath("foo.cc"))
70  return cmd(File, "-DA=1");
71  return None;
72  }
73 
74  tooling::CompileCommand
75  getFallbackCommand(llvm::StringRef File) const override {
76  return cmd(File, "-DA=2");
77  }
78 
79  llvm::Optional<ProjectInfo> getProjectInfo(PathRef File) const override {
80  return ProjectInfo{testRoot()};
81  }
82  };
83 
84 protected:
85  OverlayCDBTest() : Base(std::make_unique<BaseCDB>()) {}
86  std::unique_ptr<GlobalCompilationDatabase> Base;
87 };
88 
89 TEST_F(OverlayCDBTest, GetCompileCommand) {
90  OverlayCDB CDB(Base.get(), {}, std::string(""));
91  EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine,
92  AllOf(Contains(testPath("foo.cc")), Contains("-DA=1")));
93  EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), llvm::None);
94 
95  auto Override = cmd(testPath("foo.cc"), "-DA=3");
96  CDB.setCompileCommand(testPath("foo.cc"), Override);
97  EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine,
98  Contains("-DA=3"));
99  EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), llvm::None);
100  CDB.setCompileCommand(testPath("missing.cc"), Override);
101  EXPECT_THAT(CDB.getCompileCommand(testPath("missing.cc"))->CommandLine,
102  Contains("-DA=3"));
103 }
104 
105 TEST_F(OverlayCDBTest, GetFallbackCommand) {
106  OverlayCDB CDB(Base.get(), {"-DA=4"});
107  EXPECT_THAT(CDB.getFallbackCommand(testPath("bar.cc")).CommandLine,
108  ElementsAre("clang", "-DA=2", testPath("bar.cc"), "-DA=4",
109  "-fsyntax-only", StartsWith("-resource-dir")));
110 }
111 
112 TEST_F(OverlayCDBTest, NoBase) {
113  OverlayCDB CDB(nullptr, {"-DA=6"}, std::string(""));
114  EXPECT_EQ(CDB.getCompileCommand(testPath("bar.cc")), None);
115  auto Override = cmd(testPath("bar.cc"), "-DA=5");
116  CDB.setCompileCommand(testPath("bar.cc"), Override);
117  EXPECT_THAT(CDB.getCompileCommand(testPath("bar.cc"))->CommandLine,
118  Contains("-DA=5"));
119 
120  EXPECT_THAT(CDB.getFallbackCommand(testPath("foo.cc")).CommandLine,
121  ElementsAre(EndsWith("clang"), testPath("foo.cc"), "-DA=6",
122  "-fsyntax-only"));
123 }
124 
125 TEST_F(OverlayCDBTest, Watch) {
126  OverlayCDB Inner(nullptr);
127  OverlayCDB Outer(&Inner);
128 
129  std::vector<std::vector<std::string>> Changes;
130  auto Sub = Outer.watch([&](const std::vector<std::string> &ChangedFiles) {
131  Changes.push_back(ChangedFiles);
132  });
133 
134  Inner.setCompileCommand("A.cpp", tooling::CompileCommand());
135  Outer.setCompileCommand("B.cpp", tooling::CompileCommand());
136  Inner.setCompileCommand("A.cpp", llvm::None);
137  Outer.setCompileCommand("C.cpp", llvm::None);
138  EXPECT_THAT(Changes, ElementsAre(ElementsAre("A.cpp"), ElementsAre("B.cpp"),
139  ElementsAre("A.cpp"), ElementsAre("C.cpp")));
140 }
141 
142 TEST_F(OverlayCDBTest, Adjustments) {
143  OverlayCDB CDB(Base.get(), {}, std::string(""));
144  auto Cmd = CDB.getCompileCommand(testPath("foo.cc")).getValue();
145  // Delete the file name.
146  Cmd.CommandLine.pop_back();
147 
148  // Check dependency file commands are dropped.
149  Cmd.CommandLine.push_back("-MF");
150  Cmd.CommandLine.push_back("random-dependency");
151 
152  // Check plugin-related commands are dropped.
153  Cmd.CommandLine.push_back("-Xclang");
154  Cmd.CommandLine.push_back("-load");
155  Cmd.CommandLine.push_back("-Xclang");
156  Cmd.CommandLine.push_back("random-plugin");
157 
158  Cmd.CommandLine.push_back("-DA=5");
159  Cmd.CommandLine.push_back(Cmd.Filename);
160 
161  CDB.setCompileCommand(testPath("foo.cc"), Cmd);
162 
163  EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine,
164  AllOf(Contains("-fsyntax-only"), Contains("-DA=5"),
165  Contains(testPath("foo.cc")), Not(Contains("-MF")),
166  Not(Contains("random-dependency")),
167  Not(Contains("-Xclang")), Not(Contains("-load")),
168  Not(Contains("random-plugin"))));
169 }
170 
171 TEST(GlobalCompilationDatabaseTest, DiscoveryWithNestedCDBs) {
172  const char *const CDBOuter =
173  R"cdb(
174  [
175  {
176  "file": "a.cc",
177  "command": "",
178  "directory": "{0}",
179  },
180  {
181  "file": "build/gen.cc",
182  "command": "",
183  "directory": "{0}",
184  },
185  {
186  "file": "build/gen2.cc",
187  "command": "",
188  "directory": "{0}",
189  }
190  ]
191  )cdb";
192  const char *const CDBInner =
193  R"cdb(
194  [
195  {
196  "file": "gen.cc",
197  "command": "",
198  "directory": "{0}/build",
199  }
200  ]
201  )cdb";
202  class CleaningFS {
203  public:
204  llvm::SmallString<128> Root;
205 
206  CleaningFS() {
207  EXPECT_FALSE(
208  llvm::sys::fs::createUniqueDirectory("clangd-cdb-test", Root))
209  << "Failed to create unique directory";
210  }
211 
212  ~CleaningFS() {
213  EXPECT_FALSE(llvm::sys::fs::remove_directories(Root))
214  << "Failed to cleanup " << Root;
215  }
216 
217  void registerFile(PathRef RelativePath, llvm::StringRef Contents) {
218  llvm::SmallString<128> AbsPath(Root);
219  llvm::sys::path::append(AbsPath, RelativePath);
220 
221  EXPECT_FALSE(llvm::sys::fs::create_directories(
222  llvm::sys::path::parent_path(AbsPath)))
223  << "Failed to create directories for: " << AbsPath;
224 
225  std::error_code EC;
226  llvm::raw_fd_ostream OS(AbsPath, EC);
227  EXPECT_FALSE(EC) << "Failed to open " << AbsPath << " for writing";
228  OS << llvm::formatv(Contents.data(),
229  llvm::sys::path::convert_to_slash(Root));
230  OS.close();
231 
232  EXPECT_FALSE(OS.has_error());
233  }
234  };
235 
236  CleaningFS FS;
237  FS.registerFile("compile_commands.json", CDBOuter);
238  FS.registerFile("build/compile_commands.json", CDBInner);
239  llvm::SmallString<128> File;
240 
241  // Note that gen2.cc goes missing with our following model, not sure this
242  // happens in practice though.
243  {
244  DirectoryBasedGlobalCompilationDatabase DB(llvm::None);
245  std::vector<std::string> DiscoveredFiles;
246  auto Sub =
247  DB.watch([&DiscoveredFiles](const std::vector<std::string> Changes) {
248  DiscoveredFiles = Changes;
249  });
250 
251  File = FS.Root;
252  llvm::sys::path::append(File, "build", "..", "a.cc");
253  DB.getCompileCommand(File.str());
254  EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(AllOf(
255  EndsWith("a.cc"), Not(HasSubstr("..")))));
256  DiscoveredFiles.clear();
257 
258  File = FS.Root;
259  llvm::sys::path::append(File, "build", "gen.cc");
260  DB.getCompileCommand(File.str());
261  EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(EndsWith("gen.cc")));
262  }
263 
264  // With a custom compile commands dir.
265  {
266  DirectoryBasedGlobalCompilationDatabase DB(FS.Root.str().str());
267  std::vector<std::string> DiscoveredFiles;
268  auto Sub =
269  DB.watch([&DiscoveredFiles](const std::vector<std::string> Changes) {
270  DiscoveredFiles = Changes;
271  });
272 
273  File = FS.Root;
274  llvm::sys::path::append(File, "a.cc");
275  DB.getCompileCommand(File.str());
276  EXPECT_THAT(DiscoveredFiles,
277  UnorderedElementsAre(EndsWith("a.cc"), EndsWith("gen.cc"),
278  EndsWith("gen2.cc")));
279  DiscoveredFiles.clear();
280 
281  File = FS.Root;
282  llvm::sys::path::append(File, "build", "gen.cc");
283  DB.getCompileCommand(File.str());
284  EXPECT_THAT(DiscoveredFiles, IsEmpty());
285  }
286 }
287 
288 TEST(GlobalCompilationDatabaseTest, NonCanonicalFilenames) {
289  OverlayCDB DB(nullptr);
290  std::vector<std::string> DiscoveredFiles;
291  auto Sub =
292  DB.watch([&DiscoveredFiles](const std::vector<std::string> Changes) {
293  DiscoveredFiles = Changes;
294  });
295 
296  llvm::SmallString<128> Root(testRoot());
297  llvm::sys::path::append(Root, "build", "..", "a.cc");
298  DB.setCompileCommand(Root.str(), tooling::CompileCommand());
299  EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(testPath("a.cc")));
300  DiscoveredFiles.clear();
301 
302  llvm::SmallString<128> File(testRoot());
303  llvm::sys::path::append(File, "blabla", "..", "a.cc");
304 
305  EXPECT_TRUE(DB.getCompileCommand(File));
306  EXPECT_TRUE(DB.getProjectInfo(File));
307 }
308 
309 } // namespace
310 } // namespace clangd
311 } // namespace clang
llvm::StringRef Contents
tooling::Replacements Changes
Definition: Format.cpp:108
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition: Path.h:23
MockFSProvider FS
Documents should not be synced at all.
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
TEST(BackgroundQueueTest, Priority)
std::string testPath(PathRef File)
Definition: TestFS.cpp:82
const char * testRoot()
Definition: TestFS.cpp:74
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
std::unique_ptr< GlobalCompilationDatabase > Base