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
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(val: ++Index))](const Params &P, Config &C) {
39 C.CompileFlags.Edits.push_back(
40 x: [Arg](std::vector<std::string> &Argv) { Argv.push_back(x: 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 not
58// 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: foo
79 Unknown: 42
80)yaml";
81
82const char *AddFooWithTypoErr = R"yaml(
83CompileFlags:
84 Add: foo
85 Removr: 42
86)yaml";
87
88const char *AddBarBaz = R"yaml(
89CompileFlags:
90 Add: bar
91---
92CompileFlags:
93 Add: baz
94)yaml";
95
96TEST(ProviderTest, FromYAMLFile) {
97 MockFS FS;
98 FS.Files["foo.yaml"] = AddFooWithErr;
99
100 CapturedDiags Diags;
101 auto P = Provider::fromYAMLFile(AbsPath: testPath(File: "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(Key: "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(File: "a/b/c/d/test.cc", llvm::sys::path::Style::posix);
145 Params ABCParams;
146 ABCParams.Path = ABCPath;
147 std::string APath =
148 testPath(File: "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(RelPath: "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(Key: "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.h
188CompileFlags:
189 Add: bar
190)yaml";
191 const auto BarPath = testPath(File: "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 be
197 // excluded.
198 auto P =
199 Provider::fromYAMLFile(AbsPath: testPath(File: "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(RelPath: "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} // namespace
213} // namespace config
214} // namespace clangd
215} // namespace clang
216

source code of clang-tools-extra/clangd/unittests/ConfigProviderTests.cpp