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 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 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> 67 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 74 getFallbackCommand(llvm::StringRef File) const override { 75 return cmd(File, "-DA=2"); 76 } 77 78 llvm::Optional<ProjectInfo> getProjectInfo(PathRef File) const override { 79 return ProjectInfo{testRoot()}; 80 } 81 }; 82 83 protected: 84 OverlayCDBTest() : Base(std::make_unique<BaseCDB>()) {} 85 std::unique_ptr<GlobalCompilationDatabase> Base; 86 }; 87 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 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 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 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 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: 173 ScratchFS() { 174 EXPECT_FALSE(llvm::sys::fs::createUniqueDirectory("clangd-cdb-test", Root)) 175 << "Failed to create unique directory"; 176 } 177 178 ~ScratchFS() { 179 EXPECT_FALSE(llvm::sys::fs::remove_directories(Root)) 180 << "Failed to cleanup " << Root; 181 } 182 183 llvm::StringRef root() const { return Root; } 184 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 201 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 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 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 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_TRUE(DB.getProjectInfo(File)); 317 } 318 319 } // namespace 320 } // namespace clangd 321 } // namespace clang 322