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 "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, ElementsAre("clang", testPath("foo/bar.cc"))); 46 EXPECT_EQ(Cmd.Output, ""); 47 48 // .h files have unknown language, so they are parsed liberally as obj-c++. 49 Cmd = DB.getFallbackCommand(testPath("foo/bar.h")); 50 EXPECT_THAT(Cmd.CommandLine, ElementsAre("clang", "-xobjective-c++-header", 51 testPath("foo/bar.h"))); 52 Cmd = DB.getFallbackCommand(testPath("foo/bar")); 53 EXPECT_THAT(Cmd.CommandLine, ElementsAre("clang", "-xobjective-c++-header", 54 testPath("foo/bar"))); 55 } 56 57 static tooling::CompileCommand cmd(llvm::StringRef File, llvm::StringRef Arg) { 58 return tooling::CompileCommand( 59 testRoot(), File, {"clang", std::string(Arg), std::string(File)}, ""); 60 } 61 62 class OverlayCDBTest : public ::testing::Test { 63 class BaseCDB : public GlobalCompilationDatabase { 64 public: 65 llvm::Optional<tooling::CompileCommand> 66 getCompileCommand(llvm::StringRef File) const override { 67 if (File == testPath("foo.cc")) 68 return cmd(File, "-DA=1"); 69 return None; 70 } 71 72 tooling::CompileCommand 73 getFallbackCommand(llvm::StringRef File) const override { 74 return cmd(File, "-DA=2"); 75 } 76 77 llvm::Optional<ProjectInfo> getProjectInfo(PathRef File) const override { 78 return ProjectInfo{testRoot()}; 79 } 80 }; 81 82 protected: 83 OverlayCDBTest() : Base(std::make_unique<BaseCDB>()) {} 84 std::unique_ptr<GlobalCompilationDatabase> Base; 85 }; 86 87 TEST_F(OverlayCDBTest, GetCompileCommand) { 88 OverlayCDB CDB(Base.get()); 89 EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine, 90 AllOf(Contains(testPath("foo.cc")), Contains("-DA=1"))); 91 EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), llvm::None); 92 93 auto Override = cmd(testPath("foo.cc"), "-DA=3"); 94 CDB.setCompileCommand(testPath("foo.cc"), Override); 95 EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine, 96 Contains("-DA=3")); 97 EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), llvm::None); 98 CDB.setCompileCommand(testPath("missing.cc"), Override); 99 EXPECT_THAT(CDB.getCompileCommand(testPath("missing.cc"))->CommandLine, 100 Contains("-DA=3")); 101 } 102 103 TEST_F(OverlayCDBTest, GetFallbackCommand) { 104 OverlayCDB CDB(Base.get(), {"-DA=4"}); 105 EXPECT_THAT(CDB.getFallbackCommand(testPath("bar.cc")).CommandLine, 106 ElementsAre("clang", "-DA=2", testPath("bar.cc"), "-DA=4")); 107 } 108 109 TEST_F(OverlayCDBTest, NoBase) { 110 OverlayCDB CDB(nullptr, {"-DA=6"}); 111 EXPECT_EQ(CDB.getCompileCommand(testPath("bar.cc")), None); 112 auto Override = cmd(testPath("bar.cc"), "-DA=5"); 113 CDB.setCompileCommand(testPath("bar.cc"), Override); 114 EXPECT_THAT(CDB.getCompileCommand(testPath("bar.cc"))->CommandLine, 115 Contains("-DA=5")); 116 117 EXPECT_THAT(CDB.getFallbackCommand(testPath("foo.cc")).CommandLine, 118 ElementsAre("clang", testPath("foo.cc"), "-DA=6")); 119 } 120 121 TEST_F(OverlayCDBTest, Watch) { 122 OverlayCDB Inner(nullptr); 123 OverlayCDB Outer(&Inner); 124 125 std::vector<std::vector<std::string>> Changes; 126 auto Sub = Outer.watch([&](const std::vector<std::string> &ChangedFiles) { 127 Changes.push_back(ChangedFiles); 128 }); 129 130 Inner.setCompileCommand("A.cpp", tooling::CompileCommand()); 131 Outer.setCompileCommand("B.cpp", tooling::CompileCommand()); 132 Inner.setCompileCommand("A.cpp", llvm::None); 133 Outer.setCompileCommand("C.cpp", llvm::None); 134 EXPECT_THAT(Changes, ElementsAre(ElementsAre("A.cpp"), ElementsAre("B.cpp"), 135 ElementsAre("A.cpp"), ElementsAre("C.cpp"))); 136 } 137 138 TEST_F(OverlayCDBTest, Adjustments) { 139 OverlayCDB CDB(Base.get(), {"-DFallback"}, 140 [](const std::vector<std::string> &Cmd, llvm::StringRef File) { 141 auto Ret = Cmd; 142 Ret.push_back( 143 ("-DAdjust_" + llvm::sys::path::filename(File)).str()); 144 return Ret; 145 }); 146 // Command from underlying gets adjusted. 147 auto Cmd = CDB.getCompileCommand(testPath("foo.cc")).getValue(); 148 EXPECT_THAT(Cmd.CommandLine, ElementsAre("clang", "-DA=1", testPath("foo.cc"), 149 "-DAdjust_foo.cc")); 150 151 // Command from overlay gets adjusted. 152 tooling::CompileCommand BarCommand; 153 BarCommand.Filename = testPath("bar.cc"); 154 BarCommand.CommandLine = {"clang++", "-DB=1", testPath("bar.cc")}; 155 CDB.setCompileCommand(testPath("bar.cc"), BarCommand); 156 Cmd = CDB.getCompileCommand(testPath("bar.cc")).getValue(); 157 EXPECT_THAT( 158 Cmd.CommandLine, 159 ElementsAre("clang++", "-DB=1", testPath("bar.cc"), "-DAdjust_bar.cc")); 160 161 // Fallback gets adjusted. 162 Cmd = CDB.getFallbackCommand("baz.cc"); 163 EXPECT_THAT(Cmd.CommandLine, ElementsAre("clang", "-DA=2", "baz.cc", 164 "-DFallback", "-DAdjust_baz.cc")); 165 } 166 167 TEST(GlobalCompilationDatabaseTest, DiscoveryWithNestedCDBs) { 168 const char *const CDBOuter = 169 R"cdb( 170 [ 171 { 172 "file": "a.cc", 173 "command": "", 174 "directory": "{0}", 175 }, 176 { 177 "file": "build/gen.cc", 178 "command": "", 179 "directory": "{0}", 180 }, 181 { 182 "file": "build/gen2.cc", 183 "command": "", 184 "directory": "{0}", 185 } 186 ] 187 )cdb"; 188 const char *const CDBInner = 189 R"cdb( 190 [ 191 { 192 "file": "gen.cc", 193 "command": "", 194 "directory": "{0}/build", 195 } 196 ] 197 )cdb"; 198 class CleaningFS { 199 public: 200 llvm::SmallString<128> Root; 201 202 CleaningFS() { 203 EXPECT_FALSE( 204 llvm::sys::fs::createUniqueDirectory("clangd-cdb-test", Root)) 205 << "Failed to create unique directory"; 206 } 207 208 ~CleaningFS() { 209 EXPECT_FALSE(llvm::sys::fs::remove_directories(Root)) 210 << "Failed to cleanup " << Root; 211 } 212 213 void registerFile(PathRef RelativePath, llvm::StringRef Contents) { 214 llvm::SmallString<128> AbsPath(Root); 215 llvm::sys::path::append(AbsPath, RelativePath); 216 217 EXPECT_FALSE(llvm::sys::fs::create_directories( 218 llvm::sys::path::parent_path(AbsPath))) 219 << "Failed to create directories for: " << AbsPath; 220 221 std::error_code EC; 222 llvm::raw_fd_ostream OS(AbsPath, EC); 223 EXPECT_FALSE(EC) << "Failed to open " << AbsPath << " for writing"; 224 OS << llvm::formatv(Contents.data(), 225 llvm::sys::path::convert_to_slash(Root)); 226 OS.close(); 227 228 EXPECT_FALSE(OS.has_error()); 229 } 230 }; 231 232 CleaningFS FS; 233 FS.registerFile("compile_commands.json", CDBOuter); 234 FS.registerFile("build/compile_commands.json", CDBInner); 235 llvm::SmallString<128> File; 236 237 // Note that gen2.cc goes missing with our following model, not sure this 238 // happens in practice though. 239 { 240 DirectoryBasedGlobalCompilationDatabase DB(llvm::None); 241 std::vector<std::string> DiscoveredFiles; 242 auto Sub = 243 DB.watch([&DiscoveredFiles](const std::vector<std::string> Changes) { 244 DiscoveredFiles = Changes; 245 }); 246 247 File = FS.Root; 248 llvm::sys::path::append(File, "build", "..", "a.cc"); 249 DB.getCompileCommand(File.str()); 250 EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(AllOf( 251 EndsWith("a.cc"), Not(HasSubstr(".."))))); 252 DiscoveredFiles.clear(); 253 254 File = FS.Root; 255 llvm::sys::path::append(File, "build", "gen.cc"); 256 DB.getCompileCommand(File.str()); 257 EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(EndsWith("gen.cc"))); 258 } 259 260 // With a custom compile commands dir. 261 { 262 DirectoryBasedGlobalCompilationDatabase DB(FS.Root.str().str()); 263 std::vector<std::string> DiscoveredFiles; 264 auto Sub = 265 DB.watch([&DiscoveredFiles](const std::vector<std::string> Changes) { 266 DiscoveredFiles = Changes; 267 }); 268 269 File = FS.Root; 270 llvm::sys::path::append(File, "a.cc"); 271 DB.getCompileCommand(File.str()); 272 EXPECT_THAT(DiscoveredFiles, 273 UnorderedElementsAre(EndsWith("a.cc"), EndsWith("gen.cc"), 274 EndsWith("gen2.cc"))); 275 DiscoveredFiles.clear(); 276 277 File = FS.Root; 278 llvm::sys::path::append(File, "build", "gen.cc"); 279 DB.getCompileCommand(File.str()); 280 EXPECT_THAT(DiscoveredFiles, IsEmpty()); 281 } 282 } 283 284 TEST(GlobalCompilationDatabaseTest, NonCanonicalFilenames) { 285 OverlayCDB DB(nullptr); 286 std::vector<std::string> DiscoveredFiles; 287 auto Sub = 288 DB.watch([&DiscoveredFiles](const std::vector<std::string> Changes) { 289 DiscoveredFiles = Changes; 290 }); 291 292 llvm::SmallString<128> Root(testRoot()); 293 llvm::sys::path::append(Root, "build", "..", "a.cc"); 294 DB.setCompileCommand(Root.str(), tooling::CompileCommand()); 295 EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(testPath("a.cc"))); 296 DiscoveredFiles.clear(); 297 298 llvm::SmallString<128> File(testRoot()); 299 llvm::sys::path::append(File, "blabla", "..", "a.cc"); 300 301 EXPECT_TRUE(DB.getCompileCommand(File)); 302 EXPECT_TRUE(DB.getProjectInfo(File)); 303 } 304 305 } // namespace 306 } // namespace clangd 307 } // namespace clang 308