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 
9 #include "GlobalCompilationDatabase.h"
10 
11 #include "Matchers.h"
12 #include "TestFS.h"
13 #include "support/Path.h"
14 #include "clang/Tooling/CompilationDatabase.h"
15 #include "llvm/ADT/Optional.h"
16 #include "llvm/ADT/SmallString.h"
17 #include "llvm/ADT/StringExtras.h"
18 #include "llvm/ADT/StringRef.h"
19 #include "llvm/Support/FileSystem.h"
20 #include "llvm/Support/FormatVariadic.h"
21 #include "llvm/Support/MemoryBuffer.h"
22 #include "llvm/Support/Path.h"
23 #include "llvm/Support/raw_ostream.h"
24 #include "gmock/gmock.h"
25 #include "gtest/gtest.h"
26 #include <fstream>
27 #include <string>
28 
29 namespace clang {
30 namespace clangd {
31 namespace {
32 using ::testing::AllOf;
33 using ::testing::Contains;
34 using ::testing::ElementsAre;
35 using ::testing::EndsWith;
36 using ::testing::HasSubstr;
37 using ::testing::IsEmpty;
38 using ::testing::Not;
39 using ::testing::StartsWith;
40 using ::testing::UnorderedElementsAre;
41 
TEST(GlobalCompilationDatabaseTest,FallbackCommand)42 TEST(GlobalCompilationDatabaseTest, FallbackCommand) {
43   DirectoryBasedGlobalCompilationDatabase DB(None);
44   auto Cmd = DB.getFallbackCommand(testPath("foo/bar.cc"));
45   EXPECT_EQ(Cmd.Directory, testPath("foo"));
46   EXPECT_THAT(Cmd.CommandLine, ElementsAre("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, ElementsAre("clang", "-xobjective-c++-header",
52                                            testPath("foo/bar.h")));
53   Cmd = DB.getFallbackCommand(testPath("foo/bar"));
54   EXPECT_THAT(Cmd.CommandLine, ElementsAre("clang", "-xobjective-c++-header",
55                                            testPath("foo/bar")));
56 }
57 
cmd(llvm::StringRef File,llvm::StringRef Arg)58 static tooling::CompileCommand cmd(llvm::StringRef File, llvm::StringRef Arg) {
59   return tooling::CompileCommand(
60       testRoot(), File, {"clang", std::string(Arg), std::string(File)}, "");
61 }
62 
63 class OverlayCDBTest : public ::testing::Test {
64   class BaseCDB : public GlobalCompilationDatabase {
65   public:
66     llvm::Optional<tooling::CompileCommand>
getCompileCommand(llvm::StringRef File) const67     getCompileCommand(llvm::StringRef File) const override {
68       if (File == testPath("foo.cc"))
69         return cmd(File, "-DA=1");
70       return None;
71     }
72 
73     tooling::CompileCommand
getFallbackCommand(llvm::StringRef File) const74     getFallbackCommand(llvm::StringRef File) const override {
75       return cmd(File, "-DA=2");
76     }
77 
getProjectInfo(PathRef File) const78     llvm::Optional<ProjectInfo> getProjectInfo(PathRef File) const override {
79       return ProjectInfo{testRoot()};
80     }
81   };
82 
83 protected:
OverlayCDBTest()84   OverlayCDBTest() : Base(std::make_unique<BaseCDB>()) {}
85   std::unique_ptr<GlobalCompilationDatabase> Base;
86 };
87 
TEST_F(OverlayCDBTest,GetCompileCommand)88 TEST_F(OverlayCDBTest, GetCompileCommand) {
89   OverlayCDB CDB(Base.get());
90   EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine,
91               AllOf(Contains(testPath("foo.cc")), Contains("-DA=1")));
92   EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), llvm::None);
93 
94   auto Override = cmd(testPath("foo.cc"), "-DA=3");
95   CDB.setCompileCommand(testPath("foo.cc"), Override);
96   EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine,
97               Contains("-DA=3"));
98   EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), llvm::None);
99   CDB.setCompileCommand(testPath("missing.cc"), Override);
100   EXPECT_THAT(CDB.getCompileCommand(testPath("missing.cc"))->CommandLine,
101               Contains("-DA=3"));
102 }
103 
TEST_F(OverlayCDBTest,GetFallbackCommand)104 TEST_F(OverlayCDBTest, GetFallbackCommand) {
105   OverlayCDB CDB(Base.get(), {"-DA=4"});
106   EXPECT_THAT(CDB.getFallbackCommand(testPath("bar.cc")).CommandLine,
107               ElementsAre("clang", "-DA=2", testPath("bar.cc"), "-DA=4"));
108 }
109 
TEST_F(OverlayCDBTest,NoBase)110 TEST_F(OverlayCDBTest, NoBase) {
111   OverlayCDB CDB(nullptr, {"-DA=6"});
112   EXPECT_EQ(CDB.getCompileCommand(testPath("bar.cc")), None);
113   auto Override = cmd(testPath("bar.cc"), "-DA=5");
114   CDB.setCompileCommand(testPath("bar.cc"), Override);
115   EXPECT_THAT(CDB.getCompileCommand(testPath("bar.cc"))->CommandLine,
116               Contains("-DA=5"));
117 
118   EXPECT_THAT(CDB.getFallbackCommand(testPath("foo.cc")).CommandLine,
119               ElementsAre("clang", testPath("foo.cc"), "-DA=6"));
120 }
121 
TEST_F(OverlayCDBTest,Watch)122 TEST_F(OverlayCDBTest, Watch) {
123   OverlayCDB Inner(nullptr);
124   OverlayCDB Outer(&Inner);
125 
126   std::vector<std::vector<std::string>> Changes;
127   auto Sub = Outer.watch([&](const std::vector<std::string> &ChangedFiles) {
128     Changes.push_back(ChangedFiles);
129   });
130 
131   Inner.setCompileCommand("A.cpp", tooling::CompileCommand());
132   Outer.setCompileCommand("B.cpp", tooling::CompileCommand());
133   Inner.setCompileCommand("A.cpp", llvm::None);
134   Outer.setCompileCommand("C.cpp", llvm::None);
135   EXPECT_THAT(Changes, ElementsAre(ElementsAre("A.cpp"), ElementsAre("B.cpp"),
136                                    ElementsAre("A.cpp"), ElementsAre("C.cpp")));
137 }
138 
TEST_F(OverlayCDBTest,Adjustments)139 TEST_F(OverlayCDBTest, Adjustments) {
140   OverlayCDB CDB(Base.get(), {"-DFallback"},
141                  [](const std::vector<std::string> &Cmd, llvm::StringRef File) {
142                    auto Ret = Cmd;
143                    Ret.push_back(
144                        ("-DAdjust_" + llvm::sys::path::filename(File)).str());
145                    return Ret;
146                  });
147   // Command from underlying gets adjusted.
148   auto Cmd = CDB.getCompileCommand(testPath("foo.cc")).getValue();
149   EXPECT_THAT(Cmd.CommandLine, ElementsAre("clang", "-DA=1", testPath("foo.cc"),
150                                            "-DAdjust_foo.cc"));
151 
152   // Command from overlay gets adjusted.
153   tooling::CompileCommand BarCommand;
154   BarCommand.Filename = testPath("bar.cc");
155   BarCommand.CommandLine = {"clang++", "-DB=1", testPath("bar.cc")};
156   CDB.setCompileCommand(testPath("bar.cc"), BarCommand);
157   Cmd = CDB.getCompileCommand(testPath("bar.cc")).getValue();
158   EXPECT_THAT(
159       Cmd.CommandLine,
160       ElementsAre("clang++", "-DB=1", testPath("bar.cc"), "-DAdjust_bar.cc"));
161 
162   // Fallback gets adjusted.
163   Cmd = CDB.getFallbackCommand("baz.cc");
164   EXPECT_THAT(Cmd.CommandLine, ElementsAre("clang", "-DA=2", "baz.cc",
165                                            "-DFallback", "-DAdjust_baz.cc"));
166 }
167 
168 // Allows placement of files for tests and cleans them up after.
169 class ScratchFS {
170   llvm::SmallString<128> Root;
171 
172 public:
ScratchFS()173   ScratchFS() {
174     EXPECT_FALSE(llvm::sys::fs::createUniqueDirectory("clangd-cdb-test", Root))
175         << "Failed to create unique directory";
176   }
177 
~ScratchFS()178   ~ScratchFS() {
179     EXPECT_FALSE(llvm::sys::fs::remove_directories(Root))
180         << "Failed to cleanup " << Root;
181   }
182 
root() const183   llvm::StringRef root() const { return Root; }
184 
write(PathRef RelativePath,llvm::StringRef Contents)185   void write(PathRef RelativePath, llvm::StringRef Contents) {
186     std::string AbsPath = path(RelativePath);
187     EXPECT_FALSE(llvm::sys::fs::create_directories(
188         llvm::sys::path::parent_path(AbsPath)))
189         << "Failed to create directories for: " << AbsPath;
190 
191     std::error_code EC;
192     llvm::raw_fd_ostream OS(AbsPath, EC);
193     EXPECT_FALSE(EC) << "Failed to open " << AbsPath << " for writing";
194     OS << llvm::formatv(Contents.data(),
195                         llvm::sys::path::convert_to_slash(Root));
196     OS.close();
197 
198     EXPECT_FALSE(OS.has_error());
199   }
200 
path(PathRef RelativePath) const201   std::string path(PathRef RelativePath) const {
202     llvm::SmallString<128> AbsPath(Root);
203     llvm::sys::path::append(AbsPath, RelativePath);
204     llvm::sys::path::native(AbsPath);
205     return AbsPath.str().str();
206   }
207 };
208 
TEST(GlobalCompilationDatabaseTest,DiscoveryWithNestedCDBs)209 TEST(GlobalCompilationDatabaseTest, DiscoveryWithNestedCDBs) {
210   const char *const CDBOuter =
211       R"cdb(
212       [
213         {
214           "file": "a.cc",
215           "command": "",
216           "directory": "{0}",
217         },
218         {
219           "file": "build/gen.cc",
220           "command": "",
221           "directory": "{0}",
222         },
223         {
224           "file": "build/gen2.cc",
225           "command": "",
226           "directory": "{0}",
227         }
228       ]
229       )cdb";
230   const char *const CDBInner =
231       R"cdb(
232       [
233         {
234           "file": "gen.cc",
235           "command": "",
236           "directory": "{0}/build",
237         }
238       ]
239       )cdb";
240   ScratchFS FS;
241   FS.write("compile_commands.json", CDBOuter);
242   FS.write("build/compile_commands.json", CDBInner);
243 
244   // Note that gen2.cc goes missing with our following model, not sure this
245   // happens in practice though.
246   {
247     DirectoryBasedGlobalCompilationDatabase DB(llvm::None);
248     std::vector<std::string> DiscoveredFiles;
249     auto Sub =
250         DB.watch([&DiscoveredFiles](const std::vector<std::string> Changes) {
251           DiscoveredFiles = Changes;
252         });
253 
254     DB.getCompileCommand(FS.path("build/../a.cc"));
255     EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(AllOf(
256                                      EndsWith("a.cc"), Not(HasSubstr("..")))));
257     DiscoveredFiles.clear();
258 
259     DB.getCompileCommand(FS.path("build/gen.cc"));
260     EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(EndsWith("gen.cc")));
261   }
262 
263   // With a custom compile commands dir.
264   {
265     DirectoryBasedGlobalCompilationDatabase DB(FS.root().str());
266     std::vector<std::string> DiscoveredFiles;
267     auto Sub =
268         DB.watch([&DiscoveredFiles](const std::vector<std::string> Changes) {
269           DiscoveredFiles = Changes;
270         });
271 
272     DB.getCompileCommand(FS.path("a.cc"));
273     EXPECT_THAT(DiscoveredFiles,
274                 UnorderedElementsAre(EndsWith("a.cc"), EndsWith("gen.cc"),
275                                      EndsWith("gen2.cc")));
276     DiscoveredFiles.clear();
277 
278     DB.getCompileCommand(FS.path("build/gen.cc"));
279     EXPECT_THAT(DiscoveredFiles, IsEmpty());
280   }
281 }
282 
TEST(GlobalCompilationDatabaseTest,BuildDir)283 TEST(GlobalCompilationDatabaseTest, BuildDir) {
284   ScratchFS FS;
285   auto Command = [&](llvm::StringRef Relative) {
286     return DirectoryBasedGlobalCompilationDatabase(llvm::None)
287         .getCompileCommand(FS.path(Relative))
288         .getValueOr(tooling::CompileCommand())
289         .CommandLine;
290   };
291   EXPECT_THAT(Command("x/foo.cc"), IsEmpty());
292   FS.write("x/build/compile_flags.txt", "-DXYZZY");
293   EXPECT_THAT(Command("x/foo.cc"), Contains("-DXYZZY"));
294   EXPECT_THAT(Command("bar.cc"), IsEmpty())
295       << "x/build/compile_flags.txt only applicable to x/";
296 }
297 
TEST(GlobalCompilationDatabaseTest,NonCanonicalFilenames)298 TEST(GlobalCompilationDatabaseTest, NonCanonicalFilenames) {
299   OverlayCDB DB(nullptr);
300   std::vector<std::string> DiscoveredFiles;
301   auto Sub =
302       DB.watch([&DiscoveredFiles](const std::vector<std::string> Changes) {
303         DiscoveredFiles = Changes;
304       });
305 
306   llvm::SmallString<128> Root(testRoot());
307   llvm::sys::path::append(Root, "build", "..", "a.cc");
308   DB.setCompileCommand(Root.str(), tooling::CompileCommand());
309   EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(testPath("a.cc")));
310   DiscoveredFiles.clear();
311 
312   llvm::SmallString<128> File(testRoot());
313   llvm::sys::path::append(File, "blabla", "..", "a.cc");
314 
315   EXPECT_TRUE(DB.getCompileCommand(File));
316   EXPECT_FALSE(DB.getProjectInfo(File));
317 }
318 
TEST_F(OverlayCDBTest,GetProjectInfo)319 TEST_F(OverlayCDBTest, GetProjectInfo) {
320   OverlayCDB DB(Base.get());
321   Path File = testPath("foo.cc");
322   Path Header = testPath("foo.h");
323 
324   EXPECT_EQ(DB.getProjectInfo(File)->SourceRoot, testRoot());
325   EXPECT_EQ(DB.getProjectInfo(Header)->SourceRoot, testRoot());
326 
327   // Shouldn't change after an override.
328   DB.setCompileCommand(File, tooling::CompileCommand());
329   EXPECT_EQ(DB.getProjectInfo(File)->SourceRoot, testRoot());
330   EXPECT_EQ(DB.getProjectInfo(Header)->SourceRoot, testRoot());
331 }
332 } // namespace
333 } // namespace clangd
334 } // namespace clang
335