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 *AddBarBaz = R"yaml(
83 CompileFlags:
84 Add: bar
85 ---
86 CompileFlags:
87 Add: baz
88 )yaml";
89
TEST(ProviderTest,FromYAMLFile)90 TEST(ProviderTest, FromYAMLFile) {
91 MockFS FS;
92 FS.Files["foo.yaml"] = AddFooWithErr;
93
94 CapturedDiags Diags;
95 auto P = Provider::fromYAMLFile(testPath("foo.yaml"), /*Directory=*/"", FS);
96 auto Cfg = P->getConfig(Params(), Diags.callback());
97 EXPECT_THAT(Diags.Diagnostics,
98 ElementsAre(DiagMessage("Unknown CompileFlags key Unknown")));
99 EXPECT_THAT(Diags.Files, ElementsAre(testPath("foo.yaml")));
100 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));
101 Diags.clear();
102
103 Cfg = P->getConfig(Params(), Diags.callback());
104 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Cached, not re-parsed";
105 EXPECT_THAT(Diags.Files, IsEmpty());
106 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));
107
108 FS.Files["foo.yaml"] = AddBarBaz;
109 Cfg = P->getConfig(Params(), Diags.callback());
110 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "New config, no errors";
111 EXPECT_THAT(Diags.Files, ElementsAre(testPath("foo.yaml")));
112 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar", "baz"));
113 Diags.clear();
114
115 FS.Files.erase("foo.yaml");
116 Cfg = P->getConfig(Params(), Diags.callback());
117 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Missing file is not an error";
118 EXPECT_THAT(Diags.Files, IsEmpty());
119 EXPECT_THAT(getAddedArgs(Cfg), IsEmpty());
120 }
121
TEST(ProviderTest,FromAncestorRelativeYAMLFiles)122 TEST(ProviderTest, FromAncestorRelativeYAMLFiles) {
123 MockFS FS;
124 FS.Files["a/b/c/foo.yaml"] = AddBarBaz;
125 FS.Files["a/foo.yaml"] = AddFooWithErr;
126
127 std::string ABCPath =
128 testPath("a/b/c/d/test.cc", llvm::sys::path::Style::posix);
129 Params ABCParams;
130 ABCParams.Path = ABCPath;
131 std::string APath =
132 testPath("a/b/e/f/test.cc", llvm::sys::path::Style::posix);
133 Params AParams;
134 AParams.Path = APath;
135
136 CapturedDiags Diags;
137 auto P = Provider::fromAncestorRelativeYAMLFiles("foo.yaml", FS);
138
139 auto Cfg = P->getConfig(Params(), Diags.callback());
140 EXPECT_THAT(Diags.Diagnostics, IsEmpty());
141 EXPECT_THAT(Diags.Files, IsEmpty());
142 EXPECT_THAT(getAddedArgs(Cfg), IsEmpty());
143
144 Cfg = P->getConfig(ABCParams, Diags.callback());
145 EXPECT_THAT(Diags.Diagnostics,
146 ElementsAre(DiagMessage("Unknown CompileFlags key Unknown")));
147 // FIXME: fails on windows: paths have mixed slashes like C:\a/b\c.yaml
148 EXPECT_THAT(Diags.Files,
149 ElementsAre(testPath("a/foo.yaml"), testPath("a/b/c/foo.yaml")));
150 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo", "bar", "baz"));
151 Diags.clear();
152
153 Cfg = P->getConfig(AParams, Diags.callback());
154 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Cached config";
155 EXPECT_THAT(Diags.Files, IsEmpty());
156 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));
157
158 FS.Files.erase("a/foo.yaml");
159 Cfg = P->getConfig(ABCParams, Diags.callback());
160 EXPECT_THAT(Diags.Diagnostics, IsEmpty());
161 EXPECT_THAT(Diags.Files, IsEmpty());
162 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar", "baz"));
163 }
164
165 // FIXME: delete this test, it's covered by FileCacheTests.
TEST(ProviderTest,Staleness)166 TEST(ProviderTest, Staleness) {
167 MockFS FS;
168
169 auto StartTime = std::chrono::steady_clock::now();
170 Params StaleOK;
171 StaleOK.FreshTime = StartTime;
172 Params MustBeFresh;
173 MustBeFresh.FreshTime = StartTime + std::chrono::hours(1);
174 CapturedDiags Diags;
175 auto P = Provider::fromYAMLFile(testPath("foo.yaml"), /*Directory=*/"", FS);
176
177 // Initial query always reads, regardless of policy.
178 FS.Files["foo.yaml"] = AddFooWithErr;
179 auto Cfg = P->getConfig(StaleOK, Diags.callback());
180 EXPECT_THAT(Diags.Diagnostics,
181 ElementsAre(DiagMessage("Unknown CompileFlags key Unknown")));
182 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));
183 Diags.clear();
184
185 // Stale value reused by policy.
186 FS.Files["foo.yaml"] = AddBarBaz;
187 Cfg = P->getConfig(StaleOK, Diags.callback());
188 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Cached, not re-parsed";
189 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));
190
191 // Cache revalidated by policy.
192 Cfg = P->getConfig(MustBeFresh, Diags.callback());
193 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "New config, no errors";
194 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar", "baz"));
195
196 // Cache revalidated by (default) policy.
197 FS.Files.erase("foo.yaml");
198 Cfg = P->getConfig(Params(), Diags.callback());
199 EXPECT_THAT(Diags.Diagnostics, IsEmpty());
200 EXPECT_THAT(getAddedArgs(Cfg), IsEmpty());
201 }
202
TEST(ProviderTest,SourceInfo)203 TEST(ProviderTest, SourceInfo) {
204 MockFS FS;
205
206 FS.Files["baz/foo.yaml"] = R"yaml(
207 If:
208 PathMatch: .*
209 PathExclude: bar.h
210 CompileFlags:
211 Add: bar
212 )yaml";
213 const auto BarPath = testPath("baz/bar.h", llvm::sys::path::Style::posix);
214 CapturedDiags Diags;
215 Params Bar;
216 Bar.Path = BarPath;
217
218 // This should be an absolute match/exclude hence baz/bar.h should not be
219 // excluded.
220 auto P =
221 Provider::fromYAMLFile(testPath("baz/foo.yaml"), /*Directory=*/"", FS);
222 auto Cfg = P->getConfig(Bar, Diags.callback());
223 ASSERT_THAT(Diags.Diagnostics, IsEmpty());
224 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar"));
225 Diags.clear();
226
227 // This should be a relative match/exclude hence baz/bar.h should be excluded.
228 P = Provider::fromAncestorRelativeYAMLFiles("foo.yaml", FS);
229 Cfg = P->getConfig(Bar, Diags.callback());
230 ASSERT_THAT(Diags.Diagnostics, IsEmpty());
231 EXPECT_THAT(getAddedArgs(Cfg), IsEmpty());
232 Diags.clear();
233 }
234 } // namespace
235 } // namespace config
236 } // namespace clangd
237 } // namespace clang
238