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-exception
6 //
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 
20 namespace clang {
21 namespace clangd {
22 namespace config {
23 namespace {
24 using ::testing::ElementsAre;
25 using ::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.
30 class FakeProvider : public Provider {
31   std::string Prefix;
32   mutable std::atomic<unsigned> Index = {0};
33 
34   std::vector<CompiledFragment>
getFragments(const Params &,DiagnosticCallback DC) const35   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 
46 public:
FakeProvider(llvm::StringRef Prefix)47   FakeProvider(llvm::StringRef Prefix) : Prefix(Prefix) {}
48 };
49 
getAddedArgs(Config & C)50 std::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 not
58 // cache their results.
TEST(ProviderTest,Combine)59 TEST(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 
76 const char *AddFooWithErr = R"yaml(
77 CompileFlags:
78   Add: foo
79   Unknown: 42
80 )yaml";
81 
82 const char *AddFooWithTypoErr = R"yaml(
83 CompileFlags:
84   Add: foo
85   Removr: 42
86 )yaml";
87 
88 const char *AddBarBaz = R"yaml(
89 CompileFlags:
90   Add: bar
91 ---
92 CompileFlags:
93   Add: baz
94 )yaml";
95 
TEST(ProviderTest,FromYAMLFile)96 TEST(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 
TEST(ProviderTest,FromAncestorRelativeYAMLFiles)138 TEST(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.yaml
164   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 
181 // FIXME: delete this test, it's covered by FileCacheTests.
TEST(ProviderTest,Staleness)182 TEST(ProviderTest, Staleness) {
183   MockFS FS;
184 
185   auto StartTime = std::chrono::steady_clock::now();
186   Params StaleOK;
187   StaleOK.FreshTime = StartTime;
188   Params MustBeFresh;
189   MustBeFresh.FreshTime = StartTime + std::chrono::hours(1);
190   CapturedDiags Diags;
191   auto P = Provider::fromYAMLFile(testPath("foo.yaml"), /*Directory=*/"", FS);
192 
193   // Initial query always reads, regardless of policy.
194   FS.Files["foo.yaml"] = AddFooWithErr;
195   auto Cfg = P->getConfig(StaleOK, Diags.callback());
196   EXPECT_THAT(Diags.Diagnostics,
197               ElementsAre(DiagMessage("Unknown CompileFlags key 'Unknown'")));
198   EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));
199   Diags.clear();
200 
201   // Stale value reused by policy.
202   FS.Files["foo.yaml"] = AddBarBaz;
203   Cfg = P->getConfig(StaleOK, Diags.callback());
204   EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Cached, not re-parsed";
205   EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));
206 
207   // Cache revalidated by policy.
208   Cfg = P->getConfig(MustBeFresh, Diags.callback());
209   EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "New config, no errors";
210   EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar", "baz"));
211 
212   // Cache revalidated by (default) policy.
213   FS.Files.erase("foo.yaml");
214   Cfg = P->getConfig(Params(), Diags.callback());
215   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
216   EXPECT_THAT(getAddedArgs(Cfg), IsEmpty());
217 }
218 
TEST(ProviderTest,SourceInfo)219 TEST(ProviderTest, SourceInfo) {
220   MockFS FS;
221 
222   FS.Files["baz/foo.yaml"] = R"yaml(
223 If:
224   PathMatch: .*
225   PathExclude: bar.h
226 CompileFlags:
227   Add: bar
228 )yaml";
229   const auto BarPath = testPath("baz/bar.h", llvm::sys::path::Style::posix);
230   CapturedDiags Diags;
231   Params Bar;
232   Bar.Path = BarPath;
233 
234   // This should be an absolute match/exclude hence baz/bar.h should not be
235   // excluded.
236   auto P =
237       Provider::fromYAMLFile(testPath("baz/foo.yaml"), /*Directory=*/"", FS);
238   auto Cfg = P->getConfig(Bar, Diags.callback());
239   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
240   EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar"));
241   Diags.clear();
242 
243   // This should be a relative match/exclude hence baz/bar.h should be excluded.
244   P = Provider::fromAncestorRelativeYAMLFiles("foo.yaml", FS);
245   Cfg = P->getConfig(Bar, Diags.callback());
246   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
247   EXPECT_THAT(getAddedArgs(Cfg), IsEmpty());
248   Diags.clear();
249 }
250 } // namespace
251 } // namespace config
252 } // namespace clangd
253 } // namespace clang
254