216 lines · cpp
1//===-- ConfigProviderTests.cpp -------------------------------------------===//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 "Config.h"10#include "ConfigProvider.h"11#include "ConfigTesting.h"12#include "TestFS.h"13#include "llvm/Support/Path.h"14#include "llvm/Support/SourceMgr.h"15#include "gmock/gmock.h"16#include "gtest/gtest.h"17#include <atomic>18#include <chrono>19 20namespace clang {21namespace clangd {22namespace config {23namespace {24using ::testing::ElementsAre;25using ::testing::IsEmpty;26 27// Provider that appends an arg to compile flags.28// The arg is prefix<N>, where N is the times getFragments() was called.29// It also yields a diagnostic each time it's called.30class FakeProvider : public Provider {31 std::string Prefix;32 mutable std::atomic<unsigned> Index = {0};33 34 std::vector<CompiledFragment>35 getFragments(const Params &, DiagnosticCallback DC) const override {36 DC(llvm::SMDiagnostic("", llvm::SourceMgr::DK_Error, Prefix));37 CompiledFragment F =38 [Arg(Prefix + std::to_string(++Index))](const Params &P, Config &C) {39 C.CompileFlags.Edits.push_back(40 [Arg](std::vector<std::string> &Argv) { Argv.push_back(Arg); });41 return true;42 };43 return {F};44 }45 46public:47 FakeProvider(llvm::StringRef Prefix) : Prefix(Prefix) {}48};49 50std::vector<std::string> getAddedArgs(Config &C) {51 std::vector<std::string> Argv;52 for (auto &Edit : C.CompileFlags.Edits)53 Edit(Argv);54 return Argv;55}56 57// The provider from combine() should invoke its providers in order, and not58// cache their results.59TEST(ProviderTest, Combine) {60 CapturedDiags Diags;61 FakeProvider Foo("foo");62 FakeProvider Bar("bar");63 auto Combined = Provider::combine({&Foo, &Bar});64 Config Cfg = Combined->getConfig(Params(), Diags.callback());65 EXPECT_THAT(Diags.Diagnostics,66 ElementsAre(diagMessage("foo"), diagMessage("bar")));67 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo1", "bar1"));68 Diags.Diagnostics.clear();69 70 Cfg = Combined->getConfig(Params(), Diags.callback());71 EXPECT_THAT(Diags.Diagnostics,72 ElementsAre(diagMessage("foo"), diagMessage("bar")));73 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo2", "bar2"));74}75 76const char *AddFooWithErr = R"yaml(77CompileFlags:78 Add: foo79 Unknown: 4280)yaml";81 82const char *AddFooWithTypoErr = R"yaml(83CompileFlags:84 Add: foo85 Removr: 4286)yaml";87 88const char *AddBarBaz = R"yaml(89CompileFlags:90 Add: bar91---92CompileFlags:93 Add: baz94)yaml";95 96TEST(ProviderTest, FromYAMLFile) {97 MockFS FS;98 FS.Files["foo.yaml"] = AddFooWithErr;99 100 CapturedDiags Diags;101 auto P = Provider::fromYAMLFile(testPath("foo.yaml"), /*Directory=*/"", FS);102 auto Cfg = P->getConfig(Params(), Diags.callback());103 EXPECT_THAT(Diags.Diagnostics,104 ElementsAre(diagMessage("Unknown CompileFlags key 'Unknown'")));105 EXPECT_THAT(Diags.Files, ElementsAre(testPath("foo.yaml")));106 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));107 Diags.clear();108 109 Cfg = P->getConfig(Params(), Diags.callback());110 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Cached, not re-parsed";111 EXPECT_THAT(Diags.Files, IsEmpty());112 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));113 114 FS.Files["foo.yaml"] = AddFooWithTypoErr;115 Cfg = P->getConfig(Params(), Diags.callback());116 EXPECT_THAT(117 Diags.Diagnostics,118 ElementsAre(diagMessage(119 "Unknown CompileFlags key 'Removr'; did you mean 'Remove'?")));120 EXPECT_THAT(Diags.Files, ElementsAre(testPath("foo.yaml")));121 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));122 Diags.clear();123 124 FS.Files["foo.yaml"] = AddBarBaz;125 Cfg = P->getConfig(Params(), Diags.callback());126 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "New config, no errors";127 EXPECT_THAT(Diags.Files, ElementsAre(testPath("foo.yaml")));128 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar", "baz"));129 Diags.clear();130 131 FS.Files.erase("foo.yaml");132 Cfg = P->getConfig(Params(), Diags.callback());133 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Missing file is not an error";134 EXPECT_THAT(Diags.Files, IsEmpty());135 EXPECT_THAT(getAddedArgs(Cfg), IsEmpty());136}137 138TEST(ProviderTest, FromAncestorRelativeYAMLFiles) {139 MockFS FS;140 FS.Files["a/b/c/foo.yaml"] = AddBarBaz;141 FS.Files["a/foo.yaml"] = AddFooWithErr;142 143 std::string ABCPath =144 testPath("a/b/c/d/test.cc", llvm::sys::path::Style::posix);145 Params ABCParams;146 ABCParams.Path = ABCPath;147 std::string APath =148 testPath("a/b/e/f/test.cc", llvm::sys::path::Style::posix);149 Params AParams;150 AParams.Path = APath;151 152 CapturedDiags Diags;153 auto P = Provider::fromAncestorRelativeYAMLFiles("foo.yaml", FS);154 155 auto Cfg = P->getConfig(Params(), Diags.callback());156 EXPECT_THAT(Diags.Diagnostics, IsEmpty());157 EXPECT_THAT(Diags.Files, IsEmpty());158 EXPECT_THAT(getAddedArgs(Cfg), IsEmpty());159 160 Cfg = P->getConfig(ABCParams, Diags.callback());161 EXPECT_THAT(Diags.Diagnostics,162 ElementsAre(diagMessage("Unknown CompileFlags key 'Unknown'")));163 // FIXME: fails on windows: paths have mixed slashes like C:\a/b\c.yaml164 EXPECT_THAT(Diags.Files,165 ElementsAre(testPath("a/foo.yaml"), testPath("a/b/c/foo.yaml")));166 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo", "bar", "baz"));167 Diags.clear();168 169 Cfg = P->getConfig(AParams, Diags.callback());170 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Cached config";171 EXPECT_THAT(Diags.Files, IsEmpty());172 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));173 174 FS.Files.erase("a/foo.yaml");175 Cfg = P->getConfig(ABCParams, Diags.callback());176 EXPECT_THAT(Diags.Diagnostics, IsEmpty());177 EXPECT_THAT(Diags.Files, IsEmpty());178 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar", "baz"));179}180 181TEST(ProviderTest, SourceInfo) {182 MockFS FS;183 184 FS.Files["baz/foo.yaml"] = R"yaml(185If:186 PathMatch: .*187 PathExclude: bar.h188CompileFlags:189 Add: bar190)yaml";191 const auto BarPath = testPath("baz/bar.h", llvm::sys::path::Style::posix);192 CapturedDiags Diags;193 Params Bar;194 Bar.Path = BarPath;195 196 // This should be an absolute match/exclude hence baz/bar.h should not be197 // excluded.198 auto P =199 Provider::fromYAMLFile(testPath("baz/foo.yaml"), /*Directory=*/"", FS);200 auto Cfg = P->getConfig(Bar, Diags.callback());201 ASSERT_THAT(Diags.Diagnostics, IsEmpty());202 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar"));203 Diags.clear();204 205 // This should be a relative match/exclude hence baz/bar.h should be excluded.206 P = Provider::fromAncestorRelativeYAMLFiles("foo.yaml", FS);207 Cfg = P->getConfig(Bar, Diags.callback());208 ASSERT_THAT(Diags.Diagnostics, IsEmpty());209 EXPECT_THAT(getAddedArgs(Cfg), IsEmpty());210 Diags.clear();211}212} // namespace213} // namespace config214} // namespace clangd215} // namespace clang216