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/SourceMgr.h"
14 #include "gmock/gmock.h"
15 #include "gtest/gtest.h"
16 #include <atomic>
17 #include <chrono>
18
19 namespace clang {
20 namespace clangd {
21 namespace config {
22 namespace {
23 using ::testing::ElementsAre;
24 using ::testing::IsEmpty;
25
26 // Provider that appends an arg to compile flags.
27 // The arg is prefix<N>, where N is the times getFragments() was called.
28 // It also yields a diagnostic each time it's called.
29 class FakeProvider : public Provider {
30 std::string Prefix;
31 mutable std::atomic<unsigned> Index = {0};
32
33 std::vector<CompiledFragment>
getFragments(const Params &,DiagnosticCallback DC) const34 getFragments(const Params &, DiagnosticCallback DC) const override {
35 DC(llvm::SMDiagnostic("", llvm::SourceMgr::DK_Error, Prefix));
36 CompiledFragment F =
37 [Arg(Prefix + std::to_string(++Index))](const Params &P, Config &C) {
38 C.CompileFlags.Edits.push_back(
39 [Arg](std::vector<std::string> &Argv) { Argv.push_back(Arg); });
40 return true;
41 };
42 return {F};
43 }
44
45 public:
FakeProvider(llvm::StringRef Prefix)46 FakeProvider(llvm::StringRef Prefix) : Prefix(Prefix) {}
47 };
48
getAddedArgs(Config & C)49 std::vector<std::string> getAddedArgs(Config &C) {
50 std::vector<std::string> Argv;
51 for (auto &Edit : C.CompileFlags.Edits)
52 Edit(Argv);
53 return Argv;
54 }
55
56 // The provider from combine() should invoke its providers in order, and not
57 // cache their results.
TEST(ProviderTest,Combine)58 TEST(ProviderTest, Combine) {
59 CapturedDiags Diags;
60 FakeProvider Foo("foo");
61 FakeProvider Bar("bar");
62 auto Combined = Provider::combine({&Foo, &Bar});
63 Config Cfg = Combined->getConfig(Params(), Diags.callback());
64 EXPECT_THAT(Diags.Diagnostics,
65 ElementsAre(DiagMessage("foo"), DiagMessage("bar")));
66 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo1", "bar1"));
67 Diags.Diagnostics.clear();
68
69 Cfg = Combined->getConfig(Params(), Diags.callback());
70 EXPECT_THAT(Diags.Diagnostics,
71 ElementsAre(DiagMessage("foo"), DiagMessage("bar")));
72 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo2", "bar2"));
73 }
74
75 const char *AddFooWithErr = R"yaml(
76 CompileFlags:
77 Add: foo
78 Unknown: 42
79 )yaml";
80
81 const char *AddBarBaz = R"yaml(
82 CompileFlags:
83 Add: bar
84 ---
85 CompileFlags:
86 Add: baz
87 )yaml";
88
TEST(ProviderTest,FromYAMLFile)89 TEST(ProviderTest, FromYAMLFile) {
90 MockFS FS;
91 FS.Files["foo.yaml"] = AddFooWithErr;
92
93 CapturedDiags Diags;
94 auto P = Provider::fromYAMLFile(testPath("foo.yaml"), FS);
95 auto Cfg = P->getConfig(Params(), Diags.callback());
96 EXPECT_THAT(Diags.Diagnostics,
97 ElementsAre(DiagMessage("Unknown CompileFlags key Unknown")));
98 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));
99 Diags.Diagnostics.clear();
100
101 Cfg = P->getConfig(Params(), Diags.callback());
102 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Cached, not re-parsed";
103 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));
104
105 FS.Files["foo.yaml"] = AddBarBaz;
106 Cfg = P->getConfig(Params(), Diags.callback());
107 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "New config, no errors";
108 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar", "baz"));
109
110 FS.Files.erase("foo.yaml");
111 Cfg = P->getConfig(Params(), Diags.callback());
112 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Missing file is not an error";
113 EXPECT_THAT(getAddedArgs(Cfg), IsEmpty());
114 }
115
TEST(ProviderTest,FromAncestorRelativeYAMLFiles)116 TEST(ProviderTest, FromAncestorRelativeYAMLFiles) {
117 MockFS FS;
118 FS.Files["a/b/c/foo.yaml"] = AddBarBaz;
119 FS.Files["a/foo.yaml"] = AddFooWithErr;
120
121 std::string ABCPath =
122 testPath("a/b/c/d/test.cc", llvm::sys::path::Style::posix);
123 Params ABCParams;
124 ABCParams.Path = ABCPath;
125 std::string APath =
126 testPath("a/b/e/f/test.cc", llvm::sys::path::Style::posix);
127 Params AParams;
128 AParams.Path = APath;
129
130 CapturedDiags Diags;
131 auto P = Provider::fromAncestorRelativeYAMLFiles("foo.yaml", FS);
132
133 auto Cfg = P->getConfig(Params(), Diags.callback());
134 EXPECT_THAT(Diags.Diagnostics, IsEmpty());
135 EXPECT_THAT(getAddedArgs(Cfg), IsEmpty());
136
137 Cfg = P->getConfig(ABCParams, Diags.callback());
138 EXPECT_THAT(Diags.Diagnostics,
139 ElementsAre(DiagMessage("Unknown CompileFlags key Unknown")));
140 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo", "bar", "baz"));
141 Diags.Diagnostics.clear();
142
143 Cfg = P->getConfig(AParams, Diags.callback());
144 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Cached config";
145 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));
146
147 FS.Files.erase("a/foo.yaml");
148 Cfg = P->getConfig(ABCParams, Diags.callback());
149 EXPECT_THAT(Diags.Diagnostics, IsEmpty());
150 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar", "baz"));
151 }
152
TEST(ProviderTest,Staleness)153 TEST(ProviderTest, Staleness) {
154 MockFS FS;
155
156 auto StartTime = std::chrono::steady_clock::now();
157 Params StaleOK;
158 StaleOK.FreshTime = StartTime;
159 Params MustBeFresh;
160 MustBeFresh.FreshTime = StartTime + std::chrono::hours(1);
161 CapturedDiags Diags;
162 auto P = Provider::fromYAMLFile(testPath("foo.yaml"), FS);
163
164 // Initial query always reads, regardless of policy.
165 FS.Files["foo.yaml"] = AddFooWithErr;
166 auto Cfg = P->getConfig(StaleOK, Diags.callback());
167 EXPECT_THAT(Diags.Diagnostics,
168 ElementsAre(DiagMessage("Unknown CompileFlags key Unknown")));
169 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));
170 Diags.Diagnostics.clear();
171
172 // Stale value reused by policy.
173 FS.Files["foo.yaml"] = AddBarBaz;
174 Cfg = P->getConfig(StaleOK, Diags.callback());
175 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "Cached, not re-parsed";
176 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("foo"));
177
178 // Cache revalidated by policy.
179 Cfg = P->getConfig(MustBeFresh, Diags.callback());
180 EXPECT_THAT(Diags.Diagnostics, IsEmpty()) << "New config, no errors";
181 EXPECT_THAT(getAddedArgs(Cfg), ElementsAre("bar", "baz"));
182
183 // Cache revalidated by (default) policy.
184 FS.Files.erase("foo.yaml");
185 Cfg = P->getConfig(Params(), Diags.callback());
186 EXPECT_THAT(Diags.Diagnostics, IsEmpty());
187 EXPECT_THAT(getAddedArgs(Cfg), IsEmpty());
188 }
189
190 } // namespace
191 } // namespace config
192 } // namespace clangd
193 } // namespace clang
194