brintos

brintos / llvm-project-archived public Read only

0
0
Text · 21.6 KiB · c9e01e5 Raw
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