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