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> |
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 | |
46 | public: |
47 | FakeProvider(llvm::StringRef Prefix) : Prefix(Prefix) {} |
48 | }; |
49 | |
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. |
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 | |
96 | TEST(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 | |
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(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 | |
181 | TEST(ProviderTest, SourceInfo) { |
182 | MockFS FS; |
183 | |
184 | FS.Files["baz/foo.yaml" ] = R"yaml( |
185 | If: |
186 | PathMatch: .* |
187 | PathExclude: bar.h |
188 | CompileFlags: |
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 | |