588 lines · cpp
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-exception6//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 29namespace clang {30namespace clangd {31namespace {32using ::testing::AllOf;33using ::testing::Contains;34using ::testing::ElementsAre;35using ::testing::EndsWith;36using ::testing::HasSubstr;37using ::testing::IsEmpty;38using ::testing::Not;39using ::testing::UnorderedElementsAre;40 41TEST(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 58static 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 63class 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::CompileCommand74 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 83protected:84 OverlayCDBTest() : Base(std::make_unique<BaseCDB>()) {}85 std::unique_ptr<GlobalCompilationDatabase> Base;86};87 88TEST_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 106TEST_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 112TEST_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 124TEST_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 141TEST_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 168TEST_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 183TEST(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 this222 // 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 312TEST(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 344TEST(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 355MATCHER_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 367TEST(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 exist395 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 407TEST(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 428TEST_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 442TEST(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} // namespace481 482// Friend test has access to internals.483class DirectoryBasedGlobalCompilationDatabaseCacheTest484 : public ::testing::Test {485protected: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.500MATCHER_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 515auto hasFlag(llvm::StringRef Flag) {516 return hasFlag(Flag, "mock_file_name.cc");517}518 519TEST_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 clangd587} // namespace clang588