1 | //===-- ConfigCompileTests.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 "ConfigFragment.h" |
11 | #include "ConfigTesting.h" |
12 | #include "Diagnostics.h" |
13 | #include "Feature.h" |
14 | #include "TestFS.h" |
15 | #include "clang/Basic/DiagnosticSema.h" |
16 | #include "llvm/ADT/StringRef.h" |
17 | #include "llvm/Support/Path.h" |
18 | #include "llvm/Support/SourceMgr.h" |
19 | #include "gmock/gmock.h" |
20 | #include "gtest/gtest.h" |
21 | #include <optional> |
22 | #include <string> |
23 | |
24 | namespace clang { |
25 | namespace clangd { |
26 | namespace config { |
27 | namespace { |
28 | using ::testing::AllOf; |
29 | using ::testing::Contains; |
30 | using ::testing::ElementsAre; |
31 | using ::testing::IsEmpty; |
32 | using ::testing::SizeIs; |
33 | using ::testing::StartsWith; |
34 | using ::testing::UnorderedElementsAre; |
35 | |
36 | class ConfigCompileTests : public ::testing::Test { |
37 | protected: |
38 | CapturedDiags Diags; |
39 | Config Conf; |
40 | Fragment Frag; |
41 | Params Parm; |
42 | |
43 | bool compileAndApply() { |
44 | Conf = Config(); |
45 | Diags.Diagnostics.clear(); |
46 | auto Compiled = std::move(Frag).compile(Diags.callback()); |
47 | return Compiled(Parm, Conf); |
48 | } |
49 | }; |
50 | |
51 | TEST_F(ConfigCompileTests, Condition) { |
52 | // No condition. |
53 | Frag = {}; |
54 | Frag.CompileFlags.Add.emplace_back(args: "X" ); |
55 | EXPECT_TRUE(compileAndApply()) << "Empty config" ; |
56 | EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
57 | EXPECT_THAT(Conf.CompileFlags.Edits, SizeIs(1)); |
58 | |
59 | // Regex with no file. |
60 | Frag = {}; |
61 | Frag.If.PathMatch.emplace_back(args: "fo*" ); |
62 | EXPECT_FALSE(compileAndApply()); |
63 | EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
64 | EXPECT_THAT(Conf.CompileFlags.Edits, SizeIs(0)); |
65 | |
66 | // Following tests have a file path set. |
67 | Parm.Path = "bar" ; |
68 | |
69 | // Non-matching regex. |
70 | Frag = {}; |
71 | Frag.If.PathMatch.emplace_back(args: "fo*" ); |
72 | EXPECT_FALSE(compileAndApply()); |
73 | EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
74 | |
75 | // Matching regex. |
76 | Frag = {}; |
77 | Frag.If.PathMatch.emplace_back(args: "fo*" ); |
78 | Frag.If.PathMatch.emplace_back(args: "ba*r" ); |
79 | EXPECT_TRUE(compileAndApply()); |
80 | EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
81 | |
82 | // Excluded regex. |
83 | Frag = {}; |
84 | Frag.If.PathMatch.emplace_back(args: "b.*" ); |
85 | Frag.If.PathExclude.emplace_back(args: ".*r" ); |
86 | EXPECT_FALSE(compileAndApply()) << "Included but also excluded" ; |
87 | EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
88 | |
89 | // Invalid regex. |
90 | Frag = {}; |
91 | Frag.If.PathMatch.emplace_back(args: "**]@theu" ); |
92 | EXPECT_TRUE(compileAndApply()); |
93 | EXPECT_THAT(Diags.Diagnostics, SizeIs(1)); |
94 | EXPECT_THAT(Diags.Diagnostics.front().Message, StartsWith("Invalid regex" )); |
95 | |
96 | // Valid regex and unknown key. |
97 | Frag = {}; |
98 | Frag.If.HasUnrecognizedCondition = true; |
99 | Frag.If.PathMatch.emplace_back(args: "ba*r" ); |
100 | EXPECT_FALSE(compileAndApply()); |
101 | EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
102 | |
103 | // Only matches case-insensitively. |
104 | Frag = {}; |
105 | Frag.If.PathMatch.emplace_back(args: "B.*R" ); |
106 | EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
107 | #ifdef CLANGD_PATH_CASE_INSENSITIVE |
108 | EXPECT_TRUE(compileAndApply()); |
109 | #else |
110 | EXPECT_FALSE(compileAndApply()); |
111 | #endif |
112 | |
113 | Frag = {}; |
114 | Frag.If.PathExclude.emplace_back(args: "B.*R" ); |
115 | EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
116 | #ifdef CLANGD_PATH_CASE_INSENSITIVE |
117 | EXPECT_FALSE(compileAndApply()); |
118 | #else |
119 | EXPECT_TRUE(compileAndApply()); |
120 | #endif |
121 | } |
122 | |
123 | TEST_F(ConfigCompileTests, CompileCommands) { |
124 | Frag.CompileFlags.Compiler.emplace(args: "tpc.exe" ); |
125 | Frag.CompileFlags.Add.emplace_back(args: "-foo" ); |
126 | Frag.CompileFlags.Remove.emplace_back(args: "--include-directory=" ); |
127 | std::vector<std::string> Argv = {"clang" , "-I" , "bar/" , "--" , "a.cc" }; |
128 | EXPECT_TRUE(compileAndApply()); |
129 | EXPECT_THAT(Conf.CompileFlags.Edits, SizeIs(3)); |
130 | for (auto &Edit : Conf.CompileFlags.Edits) |
131 | Edit(Argv); |
132 | EXPECT_THAT(Argv, ElementsAre("tpc.exe" , "-foo" , "--" , "a.cc" )); |
133 | } |
134 | |
135 | TEST_F(ConfigCompileTests, CompilationDatabase) { |
136 | Frag.CompileFlags.CompilationDatabase.emplace(args: "None" ); |
137 | EXPECT_TRUE(compileAndApply()); |
138 | EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy, |
139 | Config::CDBSearchSpec::NoCDBSearch); |
140 | |
141 | Frag.CompileFlags.CompilationDatabase.emplace(args: "Ancestors" ); |
142 | EXPECT_TRUE(compileAndApply()); |
143 | EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy, |
144 | Config::CDBSearchSpec::Ancestors); |
145 | |
146 | // Relative path not allowed without directory set. |
147 | Frag.CompileFlags.CompilationDatabase.emplace(args: "Something" ); |
148 | EXPECT_TRUE(compileAndApply()); |
149 | EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy, |
150 | Config::CDBSearchSpec::Ancestors) |
151 | << "default value" ; |
152 | EXPECT_THAT(Diags.Diagnostics, |
153 | ElementsAre(diagMessage( |
154 | "CompilationDatabase must be an absolute path, because this " |
155 | "fragment is not associated with any directory." ))); |
156 | |
157 | // Relative path allowed if directory is set. |
158 | Frag.Source.Directory = testRoot(); |
159 | EXPECT_TRUE(compileAndApply()); |
160 | EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy, |
161 | Config::CDBSearchSpec::FixedDir); |
162 | EXPECT_EQ(Conf.CompileFlags.CDBSearch.FixedCDBPath, testPath("Something" )); |
163 | EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
164 | |
165 | // Absolute path allowed. |
166 | Frag.Source.Directory.clear(); |
167 | Frag.CompileFlags.CompilationDatabase.emplace(args: testPath(File: "Something2" )); |
168 | EXPECT_TRUE(compileAndApply()); |
169 | EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy, |
170 | Config::CDBSearchSpec::FixedDir); |
171 | EXPECT_EQ(Conf.CompileFlags.CDBSearch.FixedCDBPath, testPath("Something2" )); |
172 | EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
173 | } |
174 | |
175 | TEST_F(ConfigCompileTests, Index) { |
176 | Frag.Index.Background.emplace(args: "Skip" ); |
177 | EXPECT_TRUE(compileAndApply()); |
178 | EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Skip); |
179 | |
180 | Frag = {}; |
181 | Frag.Index.Background.emplace(args: "Foo" ); |
182 | EXPECT_TRUE(compileAndApply()); |
183 | EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Build) |
184 | << "by default" ; |
185 | EXPECT_THAT( |
186 | Diags.Diagnostics, |
187 | ElementsAre(diagMessage( |
188 | "Invalid Background value 'Foo'. Valid values are Build, Skip." ))); |
189 | } |
190 | |
191 | TEST_F(ConfigCompileTests, PathSpecMatch) { |
192 | auto BarPath = llvm::sys::path::convert_to_slash(path: testPath(File: "foo/bar.h" )); |
193 | Parm.Path = BarPath; |
194 | |
195 | struct { |
196 | std::string Directory; |
197 | std::string PathSpec; |
198 | bool ShouldMatch; |
199 | } Cases[] = { |
200 | { |
201 | // Absolute path matches. |
202 | .Directory: "" , |
203 | .PathSpec: llvm::sys::path::convert_to_slash(path: testPath(File: "foo/bar.h" )), |
204 | .ShouldMatch: true, |
205 | }, |
206 | { |
207 | // Absolute path fails. |
208 | .Directory: "" , |
209 | .PathSpec: llvm::sys::path::convert_to_slash(path: testPath(File: "bar/bar.h" )), |
210 | .ShouldMatch: false, |
211 | }, |
212 | { |
213 | // Relative should fail to match as /foo/bar.h doesn't reside under |
214 | // /baz/. |
215 | .Directory: testPath(File: "baz" ), |
216 | .PathSpec: "bar\\.h" , |
217 | .ShouldMatch: false, |
218 | }, |
219 | { |
220 | // Relative should pass with /foo as directory. |
221 | .Directory: testPath(File: "foo" ), |
222 | .PathSpec: "bar\\.h" , |
223 | .ShouldMatch: true, |
224 | }, |
225 | }; |
226 | |
227 | // PathMatch |
228 | for (const auto &Case : Cases) { |
229 | Frag = {}; |
230 | Frag.If.PathMatch.emplace_back(args: Case.PathSpec); |
231 | Frag.Source.Directory = Case.Directory; |
232 | EXPECT_EQ(compileAndApply(), Case.ShouldMatch); |
233 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
234 | } |
235 | |
236 | // PathEclude |
237 | for (const auto &Case : Cases) { |
238 | SCOPED_TRACE(Case.Directory); |
239 | SCOPED_TRACE(Case.PathSpec); |
240 | Frag = {}; |
241 | Frag.If.PathExclude.emplace_back(args: Case.PathSpec); |
242 | Frag.Source.Directory = Case.Directory; |
243 | EXPECT_NE(compileAndApply(), Case.ShouldMatch); |
244 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
245 | } |
246 | } |
247 | |
248 | TEST_F(ConfigCompileTests, DiagnosticsIncludeCleaner) { |
249 | // Defaults to Strict. |
250 | EXPECT_TRUE(compileAndApply()); |
251 | EXPECT_EQ(Conf.Diagnostics.UnusedIncludes, Config::IncludesPolicy::Strict); |
252 | |
253 | Frag = {}; |
254 | Frag.Diagnostics.UnusedIncludes.emplace(args: "None" ); |
255 | EXPECT_TRUE(compileAndApply()); |
256 | EXPECT_EQ(Conf.Diagnostics.UnusedIncludes, Config::IncludesPolicy::None); |
257 | |
258 | Frag = {}; |
259 | Frag.Diagnostics.UnusedIncludes.emplace(args: "Strict" ); |
260 | EXPECT_TRUE(compileAndApply()); |
261 | EXPECT_EQ(Conf.Diagnostics.UnusedIncludes, Config::IncludesPolicy::Strict); |
262 | |
263 | Frag = {}; |
264 | EXPECT_TRUE(Conf.Diagnostics.Includes.IgnoreHeader.empty()) |
265 | << Conf.Diagnostics.Includes.IgnoreHeader.size(); |
266 | Frag.Diagnostics.Includes.IgnoreHeader.push_back( |
267 | x: Located<std::string>("foo.h" )); |
268 | Frag.Diagnostics.Includes.IgnoreHeader.push_back( |
269 | x: Located<std::string>(".*inc" )); |
270 | EXPECT_TRUE(compileAndApply()); |
271 | auto = [this](llvm::StringRef Path) { |
272 | for (auto &Filter : Conf.Diagnostics.Includes.IgnoreHeader) { |
273 | if (Filter(Path)) |
274 | return true; |
275 | } |
276 | return false; |
277 | }; |
278 | EXPECT_TRUE(HeaderFilter("foo.h" )); |
279 | EXPECT_FALSE(HeaderFilter("bar.h" )); |
280 | |
281 | Frag = {}; |
282 | EXPECT_FALSE(Conf.Diagnostics.Includes.AnalyzeAngledIncludes); |
283 | Frag.Diagnostics.Includes.AnalyzeAngledIncludes = true; |
284 | EXPECT_TRUE(compileAndApply()); |
285 | EXPECT_TRUE(Conf.Diagnostics.Includes.AnalyzeAngledIncludes); |
286 | } |
287 | |
288 | TEST_F(ConfigCompileTests, DiagnosticSuppression) { |
289 | Frag.Diagnostics.Suppress.emplace_back(args: "bugprone-use-after-move" ); |
290 | Frag.Diagnostics.Suppress.emplace_back(args: "unreachable-code" ); |
291 | Frag.Diagnostics.Suppress.emplace_back(args: "-Wunused-variable" ); |
292 | Frag.Diagnostics.Suppress.emplace_back(args: "typecheck_bool_condition" ); |
293 | Frag.Diagnostics.Suppress.emplace_back(args: "err_unexpected_friend" ); |
294 | Frag.Diagnostics.Suppress.emplace_back(args: "warn_alloca" ); |
295 | EXPECT_TRUE(compileAndApply()); |
296 | EXPECT_THAT(Conf.Diagnostics.Suppress.keys(), |
297 | UnorderedElementsAre("bugprone-use-after-move" , |
298 | "unreachable-code" , "unused-variable" , |
299 | "typecheck_bool_condition" , |
300 | "unexpected_friend" , "warn_alloca" )); |
301 | clang::DiagnosticOptions DiagOpts; |
302 | clang::DiagnosticsEngine DiagEngine(new DiagnosticIDs, DiagOpts, |
303 | new clang::IgnoringDiagConsumer); |
304 | |
305 | using Diag = clang::Diagnostic; |
306 | { |
307 | auto D = DiagEngine.Report(diag::warn_unreachable); |
308 | EXPECT_TRUE(isDiagnosticSuppressed( |
309 | Diag{&DiagEngine, D}, Conf.Diagnostics.Suppress, LangOptions())); |
310 | } |
311 | // Subcategory not respected/suppressed. |
312 | { |
313 | auto D = DiagEngine.Report(diag::warn_unreachable_break); |
314 | EXPECT_FALSE(isDiagnosticSuppressed( |
315 | Diag{&DiagEngine, D}, Conf.Diagnostics.Suppress, LangOptions())); |
316 | } |
317 | { |
318 | auto D = DiagEngine.Report(diag::warn_unused_variable); |
319 | EXPECT_TRUE(isDiagnosticSuppressed( |
320 | Diag{&DiagEngine, D}, Conf.Diagnostics.Suppress, LangOptions())); |
321 | } |
322 | { |
323 | auto D = DiagEngine.Report(diag::err_typecheck_bool_condition); |
324 | EXPECT_TRUE(isDiagnosticSuppressed( |
325 | Diag{&DiagEngine, D}, Conf.Diagnostics.Suppress, LangOptions())); |
326 | } |
327 | { |
328 | auto D = DiagEngine.Report(diag::err_unexpected_friend); |
329 | EXPECT_TRUE(isDiagnosticSuppressed( |
330 | Diag{&DiagEngine, D}, Conf.Diagnostics.Suppress, LangOptions())); |
331 | } |
332 | { |
333 | auto D = DiagEngine.Report(diag::warn_alloca); |
334 | EXPECT_TRUE(isDiagnosticSuppressed( |
335 | Diag{&DiagEngine, D}, Conf.Diagnostics.Suppress, LangOptions())); |
336 | } |
337 | |
338 | Frag.Diagnostics.Suppress.emplace_back(args: "*" ); |
339 | EXPECT_TRUE(compileAndApply()); |
340 | EXPECT_TRUE(Conf.Diagnostics.SuppressAll); |
341 | EXPECT_THAT(Conf.Diagnostics.Suppress, IsEmpty()); |
342 | } |
343 | |
344 | TEST_F(ConfigCompileTests, Tidy) { |
345 | auto &Tidy = Frag.Diagnostics.ClangTidy; |
346 | Tidy.Add.emplace_back(args: "bugprone-use-after-move" ); |
347 | Tidy.Add.emplace_back(args: "llvm-*" ); |
348 | Tidy.Remove.emplace_back(args: "llvm-include-order" ); |
349 | Tidy.Remove.emplace_back(args: "readability-*" ); |
350 | Tidy.CheckOptions.emplace_back( |
351 | args: std::make_pair(x: std::string("StrictMode" ), y: std::string("true" ))); |
352 | Tidy.CheckOptions.emplace_back(args: std::make_pair( |
353 | x: std::string("example-check.ExampleOption" ), y: std::string("0" ))); |
354 | EXPECT_TRUE(compileAndApply()); |
355 | EXPECT_EQ(Conf.Diagnostics.ClangTidy.CheckOptions.size(), 2U); |
356 | EXPECT_EQ(Conf.Diagnostics.ClangTidy.CheckOptions.lookup("StrictMode" ), |
357 | "true" ); |
358 | EXPECT_EQ(Conf.Diagnostics.ClangTidy.CheckOptions.lookup( |
359 | "example-check.ExampleOption" ), |
360 | "0" ); |
361 | #if CLANGD_TIDY_CHECKS |
362 | EXPECT_EQ( |
363 | Conf.Diagnostics.ClangTidy.Checks, |
364 | "bugprone-use-after-move,llvm-*,-llvm-include-order,-readability-*" ); |
365 | EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
366 | #else // !CLANGD_TIDY_CHECKS |
367 | EXPECT_EQ(Conf.Diagnostics.ClangTidy.Checks, "llvm-*,-readability-*" ); |
368 | EXPECT_THAT( |
369 | Diags.Diagnostics, |
370 | ElementsAre( |
371 | diagMessage( |
372 | "clang-tidy check 'bugprone-use-after-move' was not found" ), |
373 | diagMessage("clang-tidy check 'llvm-include-order' was not found" ))); |
374 | #endif |
375 | } |
376 | |
377 | TEST_F(ConfigCompileTests, TidyBadChecks) { |
378 | auto &Tidy = Frag.Diagnostics.ClangTidy; |
379 | Tidy.Add.emplace_back(args: "unknown-check" ); |
380 | Tidy.Remove.emplace_back(args: "*" ); |
381 | Tidy.Remove.emplace_back(args: "llvm-includeorder" ); |
382 | EXPECT_TRUE(compileAndApply()); |
383 | // Ensure bad checks are stripped from the glob. |
384 | EXPECT_EQ(Conf.Diagnostics.ClangTidy.Checks, "-*" ); |
385 | EXPECT_THAT( |
386 | Diags.Diagnostics, |
387 | ElementsAre( |
388 | AllOf(diagMessage("clang-tidy check 'unknown-check' was not found" ), |
389 | diagKind(llvm::SourceMgr::DK_Warning)), |
390 | AllOf( |
391 | diagMessage("clang-tidy check 'llvm-includeorder' was not found" ), |
392 | diagKind(llvm::SourceMgr::DK_Warning)))); |
393 | } |
394 | |
395 | TEST_F(ConfigCompileTests, ExternalServerNeedsTrusted) { |
396 | Fragment::IndexBlock::ExternalBlock External; |
397 | External.Server.emplace(args: "xxx" ); |
398 | Frag.Index.External = std::move(External); |
399 | compileAndApply(); |
400 | EXPECT_THAT( |
401 | Diags.Diagnostics, |
402 | ElementsAre(diagMessage( |
403 | "Remote index may not be specified by untrusted configuration. " |
404 | "Copy this into user config to use it." ))); |
405 | EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); |
406 | } |
407 | |
408 | TEST_F(ConfigCompileTests, ExternalBlockWarnOnMultipleSource) { |
409 | Frag.Source.Trusted = true; |
410 | Fragment::IndexBlock::ExternalBlock External; |
411 | External.File.emplace(args: "" ); |
412 | External.Server.emplace(args: "" ); |
413 | Frag.Index.External = std::move(External); |
414 | compileAndApply(); |
415 | #ifdef CLANGD_ENABLE_REMOTE |
416 | EXPECT_THAT( |
417 | Diags.Diagnostics, |
418 | Contains( |
419 | AllOf(diagMessage("Exactly one of File, Server or None must be set." ), |
420 | diagKind(llvm::SourceMgr::DK_Error)))); |
421 | #else |
422 | ASSERT_TRUE(Conf.Index.External.hasValue()); |
423 | EXPECT_EQ(Conf.Index.External->Kind, Config::ExternalIndexSpec::File); |
424 | #endif |
425 | } |
426 | |
427 | TEST_F(ConfigCompileTests, ExternalBlockDisableWithNone) { |
428 | compileAndApply(); |
429 | EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); |
430 | |
431 | Fragment::IndexBlock::ExternalBlock External; |
432 | External.IsNone = true; |
433 | Frag.Index.External = std::move(External); |
434 | compileAndApply(); |
435 | EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); |
436 | } |
437 | |
438 | TEST_F(ConfigCompileTests, ExternalBlockErrOnNoSource) { |
439 | Frag.Index.External.emplace(args: Fragment::IndexBlock::ExternalBlock{}); |
440 | compileAndApply(); |
441 | EXPECT_THAT( |
442 | Diags.Diagnostics, |
443 | Contains( |
444 | AllOf(diagMessage("Exactly one of File, Server or None must be set." ), |
445 | diagKind(llvm::SourceMgr::DK_Error)))); |
446 | } |
447 | |
448 | TEST_F(ConfigCompileTests, ExternalBlockDisablesBackgroundIndex) { |
449 | auto BazPath = testPath(File: "foo/bar/baz.h" , llvm::sys::path::Style::posix); |
450 | Parm.Path = BazPath; |
451 | Frag.Index.Background.emplace(args: "Build" ); |
452 | Fragment::IndexBlock::ExternalBlock External; |
453 | External.File.emplace(args: testPath(File: "foo" )); |
454 | External.MountPoint.emplace( |
455 | args: testPath(File: "foo/bar" , llvm::sys::path::Style::posix)); |
456 | Frag.Index.External = std::move(External); |
457 | compileAndApply(); |
458 | EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Skip); |
459 | } |
460 | |
461 | TEST_F(ConfigCompileTests, ExternalBlockMountPoint) { |
462 | auto GetFrag = [](llvm::StringRef Directory, |
463 | std::optional<const char *> MountPoint) { |
464 | Fragment Frag; |
465 | Frag.Source.Directory = Directory.str(); |
466 | Fragment::IndexBlock::ExternalBlock External; |
467 | External.File.emplace(args: testPath(File: "foo" )); |
468 | if (MountPoint) |
469 | External.MountPoint.emplace(args&: *MountPoint); |
470 | Frag.Index.External = std::move(External); |
471 | return Frag; |
472 | }; |
473 | |
474 | auto BarPath = testPath(File: "foo/bar.h" , llvm::sys::path::Style::posix); |
475 | BarPath = llvm::sys::path::convert_to_slash(path: BarPath); |
476 | Parm.Path = BarPath; |
477 | // Non-absolute MountPoint without a directory raises an error. |
478 | Frag = GetFrag("" , "foo" ); |
479 | compileAndApply(); |
480 | ASSERT_THAT( |
481 | Diags.Diagnostics, |
482 | ElementsAre( |
483 | AllOf(diagMessage("MountPoint must be an absolute path, because this " |
484 | "fragment is not associated with any directory." ), |
485 | diagKind(llvm::SourceMgr::DK_Error)))); |
486 | EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); |
487 | |
488 | auto FooPath = testPath(File: "foo/" , llvm::sys::path::Style::posix); |
489 | FooPath = llvm::sys::path::convert_to_slash(path: FooPath); |
490 | // Ok when relative. |
491 | Frag = GetFrag(testRoot(), "foo/" ); |
492 | compileAndApply(); |
493 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
494 | ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File); |
495 | EXPECT_THAT(Conf.Index.External.MountPoint, FooPath); |
496 | |
497 | // None defaults to ".". |
498 | Frag = GetFrag(FooPath, std::nullopt); |
499 | compileAndApply(); |
500 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
501 | ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File); |
502 | EXPECT_THAT(Conf.Index.External.MountPoint, FooPath); |
503 | |
504 | // Without a file, external index is empty. |
505 | Parm.Path = "" ; |
506 | Frag = GetFrag("" , FooPath.c_str()); |
507 | compileAndApply(); |
508 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
509 | ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); |
510 | |
511 | // File outside MountPoint, no index. |
512 | auto BazPath = testPath(File: "bar/baz.h" , llvm::sys::path::Style::posix); |
513 | BazPath = llvm::sys::path::convert_to_slash(path: BazPath); |
514 | Parm.Path = BazPath; |
515 | Frag = GetFrag("" , FooPath.c_str()); |
516 | compileAndApply(); |
517 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
518 | ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); |
519 | |
520 | // File under MountPoint, index should be set. |
521 | BazPath = testPath(File: "foo/baz.h" , llvm::sys::path::Style::posix); |
522 | BazPath = llvm::sys::path::convert_to_slash(path: BazPath); |
523 | Parm.Path = BazPath; |
524 | Frag = GetFrag("" , FooPath.c_str()); |
525 | compileAndApply(); |
526 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
527 | ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File); |
528 | EXPECT_THAT(Conf.Index.External.MountPoint, FooPath); |
529 | |
530 | // Only matches case-insensitively. |
531 | BazPath = testPath(File: "fOo/baz.h" , llvm::sys::path::Style::posix); |
532 | BazPath = llvm::sys::path::convert_to_slash(path: BazPath); |
533 | Parm.Path = BazPath; |
534 | |
535 | FooPath = testPath(File: "FOO/" , llvm::sys::path::Style::posix); |
536 | FooPath = llvm::sys::path::convert_to_slash(path: FooPath); |
537 | Frag = GetFrag("" , FooPath.c_str()); |
538 | compileAndApply(); |
539 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
540 | #ifdef CLANGD_PATH_CASE_INSENSITIVE |
541 | ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File); |
542 | EXPECT_THAT(Conf.Index.External.MountPoint, FooPath); |
543 | #else |
544 | ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); |
545 | #endif |
546 | } |
547 | |
548 | TEST_F(ConfigCompileTests, AllScopes) { |
549 | // Defaults to true. |
550 | EXPECT_TRUE(compileAndApply()); |
551 | EXPECT_TRUE(Conf.Completion.AllScopes); |
552 | |
553 | Frag = {}; |
554 | Frag.Completion.AllScopes = false; |
555 | EXPECT_TRUE(compileAndApply()); |
556 | EXPECT_FALSE(Conf.Completion.AllScopes); |
557 | |
558 | Frag = {}; |
559 | Frag.Completion.AllScopes = true; |
560 | EXPECT_TRUE(compileAndApply()); |
561 | EXPECT_TRUE(Conf.Completion.AllScopes); |
562 | } |
563 | |
564 | TEST_F(ConfigCompileTests, Style) { |
565 | Frag = {}; |
566 | Frag.Style.FullyQualifiedNamespaces.push_back(x: std::string("foo" )); |
567 | Frag.Style.FullyQualifiedNamespaces.push_back(x: std::string("bar" )); |
568 | EXPECT_TRUE(compileAndApply()); |
569 | EXPECT_THAT(Conf.Style.FullyQualifiedNamespaces, ElementsAre("foo" , "bar" )); |
570 | |
571 | { |
572 | Frag = {}; |
573 | EXPECT_TRUE(Conf.Style.QuotedHeaders.empty()) |
574 | << Conf.Style.QuotedHeaders.size(); |
575 | Frag.Style.QuotedHeaders.push_back(x: Located<std::string>("foo.h" )); |
576 | Frag.Style.QuotedHeaders.push_back(x: Located<std::string>(".*inc" )); |
577 | EXPECT_TRUE(compileAndApply()); |
578 | auto = [this](llvm::StringRef Path) { |
579 | for (auto &Filter : Conf.Style.QuotedHeaders) { |
580 | if (Filter(Path)) |
581 | return true; |
582 | } |
583 | return false; |
584 | }; |
585 | EXPECT_TRUE(HeaderFilter("foo.h" )); |
586 | EXPECT_TRUE(HeaderFilter("prefix/foo.h" )); |
587 | EXPECT_FALSE(HeaderFilter("bar.h" )); |
588 | EXPECT_FALSE(HeaderFilter("foo.h/bar.h" )); |
589 | } |
590 | |
591 | { |
592 | Frag = {}; |
593 | EXPECT_TRUE(Conf.Style.AngledHeaders.empty()) |
594 | << Conf.Style.AngledHeaders.size(); |
595 | Frag.Style.AngledHeaders.push_back(x: Located<std::string>("foo.h" )); |
596 | Frag.Style.AngledHeaders.push_back(x: Located<std::string>(".*inc" )); |
597 | EXPECT_TRUE(compileAndApply()); |
598 | auto = [this](llvm::StringRef Path) { |
599 | for (auto &Filter : Conf.Style.AngledHeaders) { |
600 | if (Filter(Path)) |
601 | return true; |
602 | } |
603 | return false; |
604 | }; |
605 | EXPECT_TRUE(HeaderFilter("foo.h" )); |
606 | EXPECT_FALSE(HeaderFilter("bar.h" )); |
607 | } |
608 | } |
609 | } // namespace |
610 | } // namespace config |
611 | } // namespace clangd |
612 | } // namespace clang |
613 | |