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 "CompileCommands.h" 12 #include "Config.h" 13 #include "TestFS.h" 14 #include "support/Path.h" 15 #include "support/ThreadsafeFS.h" 16 #include "clang/Tooling/CompilationDatabase.h" 17 #include "llvm/ADT/STLExtras.h" 18 #include "llvm/ADT/SmallString.h" 19 #include "llvm/ADT/StringRef.h" 20 #include "llvm/Support/FormatVariadic.h" 21 #include "llvm/Support/Path.h" 22 #include "gmock/gmock.h" 23 #include "gtest/gtest.h" 24 #include <chrono> 25 #include <fstream> 26 #include <optional> 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::UnorderedElementsAre; 40 41 TEST(GlobalCompilationDatabaseTest, FallbackCommand) { 42 MockFS TFS; 43 DirectoryBasedGlobalCompilationDatabase DB(TFS); 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 std::optional<tooling::CompileCommand> 67 getCompileCommand(llvm::StringRef File) const override { 68 if (File == testPath("foo.cc")) 69 return cmd(File, "-DA=1"); 70 return std::nullopt; 71 } 72 73 tooling::CompileCommand 74 getFallbackCommand(llvm::StringRef File) const override { 75 return cmd(File, "-DA=2"); 76 } 77 78 std::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")), std::nullopt); 93 94 auto Override = cmd(testPath("foo.cc"), "-DA=3"); 95 EXPECT_TRUE(CDB.setCompileCommand(testPath("foo.cc"), Override)); 96 EXPECT_FALSE(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")), std::nullopt); 100 EXPECT_TRUE(CDB.setCompileCommand(testPath("missing.cc"), Override)); 101 EXPECT_FALSE(CDB.setCompileCommand(testPath("missing.cc"), Override)); 102 EXPECT_THAT(CDB.getCompileCommand(testPath("missing.cc"))->CommandLine, 103 Contains("-DA=3")); 104 } 105 106 TEST_F(OverlayCDBTest, GetFallbackCommand) { 107 OverlayCDB CDB(Base.get(), {"-DA=4"}); 108 EXPECT_THAT(CDB.getFallbackCommand(testPath("bar.cc")).CommandLine, 109 ElementsAre("clang", "-DA=2", testPath("bar.cc"), "-DA=4")); 110 } 111 112 TEST_F(OverlayCDBTest, NoBase) { 113 OverlayCDB CDB(nullptr, {"-DA=6"}); 114 EXPECT_EQ(CDB.getCompileCommand(testPath("bar.cc")), std::nullopt); 115 auto Override = cmd(testPath("bar.cc"), "-DA=5"); 116 EXPECT_TRUE(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("clang", testPath("foo.cc"), "-DA=6")); 122 } 123 124 TEST_F(OverlayCDBTest, Watch) { 125 OverlayCDB Inner(nullptr); 126 OverlayCDB Outer(&Inner); 127 128 std::vector<std::vector<std::string>> Changes; 129 auto Sub = Outer.watch([&](const std::vector<std::string> &ChangedFiles) { 130 Changes.push_back(ChangedFiles); 131 }); 132 133 EXPECT_TRUE(Inner.setCompileCommand("A.cpp", tooling::CompileCommand())); 134 EXPECT_TRUE(Outer.setCompileCommand("B.cpp", tooling::CompileCommand())); 135 EXPECT_TRUE(Inner.setCompileCommand("A.cpp", std::nullopt)); 136 EXPECT_TRUE(Outer.setCompileCommand("C.cpp", std::nullopt)); 137 EXPECT_THAT(Changes, ElementsAre(ElementsAre("A.cpp"), ElementsAre("B.cpp"), 138 ElementsAre("A.cpp"), ElementsAre("C.cpp"))); 139 } 140 141 TEST_F(OverlayCDBTest, Adjustments) { 142 OverlayCDB CDB(Base.get(), {"-DFallback"}, 143 [](tooling::CompileCommand &Cmd, llvm::StringRef File) { 144 Cmd.CommandLine.push_back( 145 ("-DAdjust_" + llvm::sys::path::filename(File)).str()); 146 }); 147 // Command from underlying gets adjusted. 148 auto Cmd = *CDB.getCompileCommand(testPath("foo.cc")); 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 EXPECT_TRUE(CDB.setCompileCommand(testPath("bar.cc"), BarCommand)); 157 Cmd = *CDB.getCompileCommand(testPath("bar.cc")); 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 TEST_F(OverlayCDBTest, ExpandedResponseFiles) { 169 SmallString<1024> Path; 170 int FD; 171 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("args", "", FD, Path)); 172 llvm::raw_fd_ostream OutStream(FD, true); 173 OutStream << "-Wall"; 174 OutStream.close(); 175 176 OverlayCDB CDB(Base.get(), {"-DFallback"}); 177 auto Override = cmd(testPath("foo.cc"), ("@" + Path).str()); 178 CDB.setCompileCommand(testPath("foo.cc"), Override); 179 EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine, 180 Contains("-Wall")); 181 } 182 183 TEST(GlobalCompilationDatabaseTest, DiscoveryWithNestedCDBs) { 184 const char *const CDBOuter = 185 R"cdb( 186 [ 187 { 188 "file": "a.cc", 189 "command": "", 190 "directory": "{0}", 191 }, 192 { 193 "file": "build/gen.cc", 194 "command": "", 195 "directory": "{0}", 196 }, 197 { 198 "file": "build/gen2.cc", 199 "command": "", 200 "directory": "{0}", 201 } 202 ] 203 )cdb"; 204 const char *const CDBInner = 205 R"cdb( 206 [ 207 { 208 "file": "gen.cc", 209 "command": "", 210 "directory": "{0}/build", 211 } 212 ] 213 )cdb"; 214 MockFS FS; 215 FS.Files[testPath("compile_commands.json")] = 216 llvm::formatv(CDBOuter, llvm::sys::path::convert_to_slash(testRoot())); 217 FS.Files[testPath("build/compile_commands.json")] = 218 llvm::formatv(CDBInner, llvm::sys::path::convert_to_slash(testRoot())); 219 FS.Files[testPath("foo/compile_flags.txt")] = "-DFOO"; 220 221 // Note that gen2.cc goes missing with our following model, not sure this 222 // happens in practice though. 223 { 224 SCOPED_TRACE("Default ancestor scanning"); 225 DirectoryBasedGlobalCompilationDatabase DB(FS); 226 std::vector<std::string> DiscoveredFiles; 227 auto Sub = 228 DB.watch([&DiscoveredFiles](const std::vector<std::string> Changes) { 229 DiscoveredFiles = Changes; 230 }); 231 232 DB.getCompileCommand(testPath("build/../a.cc")); 233 ASSERT_TRUE(DB.blockUntilIdle(timeoutSeconds(10))); 234 EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(AllOf( 235 EndsWith("a.cc"), Not(HasSubstr(".."))))); 236 DiscoveredFiles.clear(); 237 238 DB.getCompileCommand(testPath("build/gen.cc")); 239 ASSERT_TRUE(DB.blockUntilIdle(timeoutSeconds(10))); 240 EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(EndsWith("gen.cc"))); 241 } 242 243 { 244 SCOPED_TRACE("With config"); 245 DirectoryBasedGlobalCompilationDatabase::Options Opts(FS); 246 Opts.ContextProvider = [&](llvm::StringRef Path) { 247 Config Cfg; 248 if (Path.ends_with("a.cc")) { 249 // a.cc uses another directory's CDB, so it won't be discovered. 250 Cfg.CompileFlags.CDBSearch.Policy = Config::CDBSearchSpec::FixedDir; 251 Cfg.CompileFlags.CDBSearch.FixedCDBPath = testPath("foo"); 252 } else if (Path.ends_with("gen.cc")) { 253 // gen.cc has CDB search disabled, so it won't be discovered. 254 Cfg.CompileFlags.CDBSearch.Policy = Config::CDBSearchSpec::NoCDBSearch; 255 } else if (Path.ends_with("gen2.cc")) { 256 // gen2.cc explicitly lists this directory, so it will be discovered. 257 Cfg.CompileFlags.CDBSearch.Policy = Config::CDBSearchSpec::FixedDir; 258 Cfg.CompileFlags.CDBSearch.FixedCDBPath = testRoot(); 259 } 260 return Context::current().derive(Config::Key, std::move(Cfg)); 261 }; 262 DirectoryBasedGlobalCompilationDatabase DB(Opts); 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 // Does not use the root CDB, so no broadcast. 270 auto Cmd = DB.getCompileCommand(testPath("build/../a.cc")); 271 ASSERT_TRUE(Cmd); 272 EXPECT_THAT(Cmd->CommandLine, Contains("-DFOO")) << "a.cc uses foo/ CDB"; 273 ASSERT_TRUE(DB.blockUntilIdle(timeoutSeconds(10))); 274 EXPECT_THAT(DiscoveredFiles, IsEmpty()) << "Root CDB not discovered yet"; 275 276 // No special config for b.cc, so we trigger broadcast of the root CDB. 277 DB.getCompileCommand(testPath("b.cc")); 278 ASSERT_TRUE(DB.blockUntilIdle(timeoutSeconds(10))); 279 EXPECT_THAT(DiscoveredFiles, ElementsAre(testPath("build/gen2.cc"))); 280 DiscoveredFiles.clear(); 281 282 // No CDB search so no discovery/broadcast triggered for build/ CDB. 283 DB.getCompileCommand(testPath("build/gen.cc")); 284 ASSERT_TRUE(DB.blockUntilIdle(timeoutSeconds(10))); 285 EXPECT_THAT(DiscoveredFiles, IsEmpty()); 286 } 287 288 { 289 SCOPED_TRACE("With custom compile commands dir"); 290 DirectoryBasedGlobalCompilationDatabase::Options Opts(FS); 291 Opts.CompileCommandsDir = testRoot(); 292 DirectoryBasedGlobalCompilationDatabase DB(Opts); 293 std::vector<std::string> DiscoveredFiles; 294 auto Sub = 295 DB.watch([&DiscoveredFiles](const std::vector<std::string> Changes) { 296 DiscoveredFiles = Changes; 297 }); 298 299 DB.getCompileCommand(testPath("a.cc")); 300 ASSERT_TRUE(DB.blockUntilIdle(timeoutSeconds(10))); 301 EXPECT_THAT(DiscoveredFiles, 302 UnorderedElementsAre(EndsWith("a.cc"), EndsWith("gen.cc"), 303 EndsWith("gen2.cc"))); 304 DiscoveredFiles.clear(); 305 306 DB.getCompileCommand(testPath("build/gen.cc")); 307 ASSERT_TRUE(DB.blockUntilIdle(timeoutSeconds(10))); 308 EXPECT_THAT(DiscoveredFiles, IsEmpty()); 309 } 310 } 311 312 TEST(GlobalCompilationDatabaseTest, BuildDir) { 313 MockFS FS; 314 auto Command = [&](llvm::StringRef Relative) { 315 DirectoryBasedGlobalCompilationDatabase::Options Opts(FS); 316 return DirectoryBasedGlobalCompilationDatabase(Opts) 317 .getCompileCommand(testPath(Relative)) 318 .value_or(tooling::CompileCommand()) 319 .CommandLine; 320 }; 321 EXPECT_THAT(Command("x/foo.cc"), IsEmpty()); 322 const char *const CDB = 323 R"cdb( 324 [ 325 { 326 "file": "{0}/x/foo.cc", 327 "command": "clang -DXYZZY {0}/x/foo.cc", 328 "directory": "{0}", 329 }, 330 { 331 "file": "{0}/bar.cc", 332 "command": "clang -DXYZZY {0}/bar.cc", 333 "directory": "{0}", 334 } 335 ] 336 )cdb"; 337 FS.Files[testPath("x/build/compile_commands.json")] = 338 llvm::formatv(CDB, llvm::sys::path::convert_to_slash(testRoot())); 339 EXPECT_THAT(Command("x/foo.cc"), Contains("-DXYZZY")); 340 EXPECT_THAT(Command("bar.cc"), IsEmpty()) 341 << "x/build/compile_flags.json only applicable to x/"; 342 } 343 344 TEST(GlobalCompilationDatabaseTest, CompileFlagsDirectory) { 345 MockFS FS; 346 FS.Files[testPath("x/compile_flags.txt")] = "-DFOO"; 347 DirectoryBasedGlobalCompilationDatabase CDB(FS); 348 auto Commands = CDB.getCompileCommand(testPath("x/y.cpp")); 349 ASSERT_TRUE(Commands.has_value()); 350 EXPECT_THAT(Commands->CommandLine, Contains("-DFOO")); 351 // Make sure we pick the right working directory. 352 EXPECT_EQ(testPath("x"), Commands->Directory); 353 } 354 355 MATCHER_P(hasArg, Flag, "") { 356 if (!arg) { 357 *result_listener << "command is null"; 358 return false; 359 } 360 if (!llvm::is_contained(arg->CommandLine, Flag)) { 361 *result_listener << "flags are " << printArgv(arg->CommandLine); 362 return false; 363 } 364 return true; 365 } 366 367 TEST(GlobalCompilationDatabaseTest, Config) { 368 MockFS FS; 369 FS.Files[testPath("x/compile_flags.txt")] = "-DX"; 370 FS.Files[testPath("x/y/z/compile_flags.txt")] = "-DZ"; 371 372 Config::CDBSearchSpec Spec; 373 DirectoryBasedGlobalCompilationDatabase::Options Opts(FS); 374 Opts.ContextProvider = [&](llvm::StringRef Path) { 375 Config C; 376 C.CompileFlags.CDBSearch = Spec; 377 return Context::current().derive(Config::Key, std::move(C)); 378 }; 379 DirectoryBasedGlobalCompilationDatabase CDB(Opts); 380 381 // Default ancestor behavior. 382 EXPECT_FALSE(CDB.getCompileCommand(testPath("foo.cc"))); 383 EXPECT_THAT(CDB.getCompileCommand(testPath("x/foo.cc")), hasArg("-DX")); 384 EXPECT_THAT(CDB.getCompileCommand(testPath("x/y/foo.cc")), hasArg("-DX")); 385 EXPECT_THAT(CDB.getCompileCommand(testPath("x/y/z/foo.cc")), hasArg("-DZ")); 386 387 Spec.Policy = Config::CDBSearchSpec::NoCDBSearch; 388 EXPECT_FALSE(CDB.getCompileCommand(testPath("foo.cc"))); 389 EXPECT_FALSE(CDB.getCompileCommand(testPath("x/foo.cc"))); 390 EXPECT_FALSE(CDB.getCompileCommand(testPath("x/y/foo.cc"))); 391 EXPECT_FALSE(CDB.getCompileCommand(testPath("x/y/z/foo.cc"))); 392 393 Spec.Policy = Config::CDBSearchSpec::FixedDir; 394 Spec.FixedCDBPath = testPath("w"); // doesn't exist 395 EXPECT_FALSE(CDB.getCompileCommand(testPath("foo.cc"))); 396 EXPECT_FALSE(CDB.getCompileCommand(testPath("x/foo.cc"))); 397 EXPECT_FALSE(CDB.getCompileCommand(testPath("x/y/foo.cc"))); 398 EXPECT_FALSE(CDB.getCompileCommand(testPath("x/y/z/foo.cc"))); 399 400 Spec.FixedCDBPath = testPath("x/y/z"); 401 EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc")), hasArg("-DZ")); 402 EXPECT_THAT(CDB.getCompileCommand(testPath("x/foo.cc")), hasArg("-DZ")); 403 EXPECT_THAT(CDB.getCompileCommand(testPath("x/y/foo.cc")), hasArg("-DZ")); 404 EXPECT_THAT(CDB.getCompileCommand(testPath("x/y/z/foo.cc")), hasArg("-DZ")); 405 } 406 407 TEST(GlobalCompilationDatabaseTest, NonCanonicalFilenames) { 408 OverlayCDB DB(nullptr); 409 std::vector<std::string> DiscoveredFiles; 410 auto Sub = 411 DB.watch([&DiscoveredFiles](const std::vector<std::string> Changes) { 412 DiscoveredFiles = Changes; 413 }); 414 415 llvm::SmallString<128> Root(testRoot()); 416 llvm::sys::path::append(Root, "build", "..", "a.cc"); 417 EXPECT_TRUE(DB.setCompileCommand(Root.str(), tooling::CompileCommand())); 418 EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(testPath("a.cc"))); 419 DiscoveredFiles.clear(); 420 421 llvm::SmallString<128> File(testRoot()); 422 llvm::sys::path::append(File, "blabla", "..", "a.cc"); 423 424 EXPECT_TRUE(DB.getCompileCommand(File)); 425 EXPECT_FALSE(DB.getProjectInfo(File)); 426 } 427 428 TEST_F(OverlayCDBTest, GetProjectInfo) { 429 OverlayCDB DB(Base.get()); 430 Path File = testPath("foo.cc"); 431 Path Header = testPath("foo.h"); 432 433 EXPECT_EQ(DB.getProjectInfo(File)->SourceRoot, testRoot()); 434 EXPECT_EQ(DB.getProjectInfo(Header)->SourceRoot, testRoot()); 435 436 // Shouldn't change after an override. 437 EXPECT_TRUE(DB.setCompileCommand(File, tooling::CompileCommand())); 438 EXPECT_EQ(DB.getProjectInfo(File)->SourceRoot, testRoot()); 439 EXPECT_EQ(DB.getProjectInfo(Header)->SourceRoot, testRoot()); 440 } 441 442 TEST(GlobalCompilationDatabaseTest, InferenceWithResponseFile) { 443 MockFS FS; 444 auto Command = [&](llvm::StringRef Relative) { 445 DirectoryBasedGlobalCompilationDatabase::Options Opts(FS); 446 return DirectoryBasedGlobalCompilationDatabase(Opts) 447 .getCompileCommand(testPath(Relative)) 448 .value_or(tooling::CompileCommand()) 449 .CommandLine; 450 }; 451 EXPECT_THAT(Command("foo.cc"), IsEmpty()); 452 453 // Have to use real FS for response file. 454 SmallString<1024> Path; 455 int FD; 456 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("args", "", FD, Path)); 457 llvm::raw_fd_ostream OutStream(FD, true); 458 OutStream << "-DXYZZY"; 459 OutStream.close(); 460 461 const char *const CDB = 462 R"cdb( 463 [ 464 { 465 "file": "{0}/foo.cc", 466 "command": "clang @{1} {0}/foo.cc", 467 "directory": "{0}", 468 } 469 ] 470 )cdb"; 471 FS.Files[testPath("compile_commands.json")] = 472 llvm::formatv(CDB, llvm::sys::path::convert_to_slash(testRoot()), 473 llvm::sys::path::convert_to_slash(Path)); 474 475 // File from CDB. 476 EXPECT_THAT(Command("foo.cc"), Contains("-DXYZZY")); 477 // File not in CDB, use inference. 478 EXPECT_THAT(Command("foo.h"), Contains("-DXYZZY")); 479 } 480 } // namespace 481 482 // Friend test has access to internals. 483 class DirectoryBasedGlobalCompilationDatabaseCacheTest 484 : public ::testing::Test { 485 protected: 486 std::shared_ptr<const tooling::CompilationDatabase> 487 lookupCDB(const DirectoryBasedGlobalCompilationDatabase &GDB, 488 llvm::StringRef Path, 489 std::chrono::steady_clock::time_point FreshTime) { 490 DirectoryBasedGlobalCompilationDatabase::CDBLookupRequest Req; 491 Req.FileName = Path; 492 Req.FreshTime = Req.FreshTimeMissing = FreshTime; 493 if (auto Result = GDB.lookupCDB(Req)) 494 return std::move(Result->CDB); 495 return nullptr; 496 } 497 }; 498 499 // Matches non-null CDBs which include the specified flag. 500 MATCHER_P2(hasFlag, Flag, Path, "") { 501 if (arg == nullptr) 502 return false; 503 auto Cmds = arg->getCompileCommands(Path); 504 if (Cmds.empty()) { 505 *result_listener << "yields no commands"; 506 return false; 507 } 508 if (!llvm::is_contained(Cmds.front().CommandLine, Flag)) { 509 *result_listener << "flags are: " << printArgv(Cmds.front().CommandLine); 510 return false; 511 } 512 return true; 513 } 514 515 auto hasFlag(llvm::StringRef Flag) { 516 return hasFlag(Flag, "mock_file_name.cc"); 517 } 518 519 TEST_F(DirectoryBasedGlobalCompilationDatabaseCacheTest, Cacheable) { 520 MockFS FS; 521 auto Stale = std::chrono::steady_clock::now() - std::chrono::minutes(1); 522 auto Fresh = std::chrono::steady_clock::now() + std::chrono::hours(24); 523 524 DirectoryBasedGlobalCompilationDatabase GDB(FS); 525 FS.Files["compile_flags.txt"] = "-DROOT"; 526 auto Root = lookupCDB(GDB, testPath("foo/test.cc"), Stale); 527 EXPECT_THAT(Root, hasFlag("-DROOT")); 528 529 // Add a compilation database to a subdirectory - CDB loaded. 530 FS.Files["foo/compile_flags.txt"] = "-DFOO"; 531 EXPECT_EQ(Root, lookupCDB(GDB, testPath("foo/test.cc"), Stale)) 532 << "cache still valid"; 533 auto Foo = lookupCDB(GDB, testPath("foo/test.cc"), Fresh); 534 EXPECT_THAT(Foo, hasFlag("-DFOO")) << "new cdb loaded"; 535 EXPECT_EQ(Foo, lookupCDB(GDB, testPath("foo/test.cc"), Stale)) 536 << "new cdb in cache"; 537 538 // Mtime changed, but no content change - CDB not reloaded. 539 ++FS.Timestamps["foo/compile_flags.txt"]; 540 auto FooAgain = lookupCDB(GDB, testPath("foo/test.cc"), Fresh); 541 EXPECT_EQ(Foo, FooAgain) << "Same content, read but not reloaded"; 542 // Content changed, but not size or mtime - CDB not reloaded. 543 FS.Files["foo/compile_flags.txt"] = "-DBAR"; 544 auto FooAgain2 = lookupCDB(GDB, testPath("foo/test.cc"), Fresh); 545 EXPECT_EQ(Foo, FooAgain2) << "Same filesize, change not detected"; 546 // Mtime change forces a re-read, and we notice the different content. 547 ++FS.Timestamps["foo/compile_flags.txt"]; 548 auto Bar = lookupCDB(GDB, testPath("foo/test.cc"), Fresh); 549 EXPECT_THAT(Bar, hasFlag("-DBAR")) << "refreshed with mtime change"; 550 551 // Size and content both change - CDB reloaded. 552 FS.Files["foo/compile_flags.txt"] = "-DFOOBAR"; 553 EXPECT_EQ(Bar, lookupCDB(GDB, testPath("foo/test.cc"), Stale)) 554 << "cache still valid"; 555 auto FooBar = lookupCDB(GDB, testPath("foo/test.cc"), Fresh); 556 EXPECT_THAT(FooBar, hasFlag("-DFOOBAR")) << "cdb reloaded"; 557 558 // compile_commands.json takes precedence over compile_flags.txt. 559 FS.Files["foo/compile_commands.json"] = 560 llvm::formatv(R"json([{ 561 "file": "{0}/foo/mock_file.cc", 562 "command": "clang -DBAZ mock_file.cc", 563 "directory": "{0}/foo", 564 }])json", 565 llvm::sys::path::convert_to_slash(testRoot())); 566 EXPECT_EQ(FooBar, lookupCDB(GDB, testPath("foo/test.cc"), Stale)) 567 << "cache still valid"; 568 auto Baz = lookupCDB(GDB, testPath("foo/test.cc"), Fresh); 569 EXPECT_THAT(Baz, hasFlag("-DBAZ", testPath("foo/mock_file.cc"))) 570 << "compile_commands overrides compile_flags"; 571 572 // Removing compile_commands.json reveals compile_flags.txt again. 573 // However this *does* cause a CDB reload (we cache only one CDB per dir). 574 FS.Files.erase("foo/compile_commands.json"); 575 auto FoobarAgain = lookupCDB(GDB, testPath("foo/test.cc"), Fresh); 576 EXPECT_THAT(FoobarAgain, hasFlag("-DFOOBAR")) << "reloaded compile_flags"; 577 EXPECT_NE(FoobarAgain, FooBar) << "CDB discarded (shadowed within directory)"; 578 579 // Removing the directory's CDB leaves the parent CDB active. 580 // The parent CDB is *not* reloaded (we cache the CDB per-directory). 581 FS.Files.erase("foo/compile_flags.txt"); 582 EXPECT_EQ(Root, lookupCDB(GDB, testPath("foo/test.cc"), Fresh)) 583 << "CDB retained (shadowed by another directory)"; 584 } 585 586 } // namespace clangd 587 } // namespace clang 588