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 | |
282 | TEST_F(ConfigCompileTests, DiagnosticSuppression) { |
283 | Frag.Diagnostics.Suppress.emplace_back(args: "bugprone-use-after-move" ); |
284 | Frag.Diagnostics.Suppress.emplace_back(args: "unreachable-code" ); |
285 | Frag.Diagnostics.Suppress.emplace_back(args: "-Wunused-variable" ); |
286 | Frag.Diagnostics.Suppress.emplace_back(args: "typecheck_bool_condition" ); |
287 | Frag.Diagnostics.Suppress.emplace_back(args: "err_unexpected_friend" ); |
288 | Frag.Diagnostics.Suppress.emplace_back(args: "warn_alloca" ); |
289 | EXPECT_TRUE(compileAndApply()); |
290 | EXPECT_THAT(Conf.Diagnostics.Suppress.keys(), |
291 | UnorderedElementsAre("bugprone-use-after-move" , |
292 | "unreachable-code" , "unused-variable" , |
293 | "typecheck_bool_condition" , |
294 | "unexpected_friend" , "warn_alloca" )); |
295 | EXPECT_TRUE(isBuiltinDiagnosticSuppressed( |
296 | diag::warn_unreachable, Conf.Diagnostics.Suppress, LangOptions())); |
297 | // Subcategory not respected/suppressed. |
298 | EXPECT_FALSE(isBuiltinDiagnosticSuppressed( |
299 | diag::warn_unreachable_break, Conf.Diagnostics.Suppress, LangOptions())); |
300 | EXPECT_TRUE(isBuiltinDiagnosticSuppressed( |
301 | diag::warn_unused_variable, Conf.Diagnostics.Suppress, LangOptions())); |
302 | EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::err_typecheck_bool_condition, |
303 | Conf.Diagnostics.Suppress, |
304 | LangOptions())); |
305 | EXPECT_TRUE(isBuiltinDiagnosticSuppressed( |
306 | diag::err_unexpected_friend, Conf.Diagnostics.Suppress, LangOptions())); |
307 | EXPECT_TRUE(isBuiltinDiagnosticSuppressed( |
308 | diag::warn_alloca, Conf.Diagnostics.Suppress, LangOptions())); |
309 | |
310 | Frag.Diagnostics.Suppress.emplace_back(args: "*" ); |
311 | EXPECT_TRUE(compileAndApply()); |
312 | EXPECT_TRUE(Conf.Diagnostics.SuppressAll); |
313 | EXPECT_THAT(Conf.Diagnostics.Suppress, IsEmpty()); |
314 | } |
315 | |
316 | TEST_F(ConfigCompileTests, Tidy) { |
317 | auto &Tidy = Frag.Diagnostics.ClangTidy; |
318 | Tidy.Add.emplace_back(args: "bugprone-use-after-move" ); |
319 | Tidy.Add.emplace_back(args: "llvm-*" ); |
320 | Tidy.Remove.emplace_back(args: "llvm-include-order" ); |
321 | Tidy.Remove.emplace_back(args: "readability-*" ); |
322 | Tidy.CheckOptions.emplace_back( |
323 | args: std::make_pair(x: std::string("StrictMode" ), y: std::string("true" ))); |
324 | Tidy.CheckOptions.emplace_back(args: std::make_pair( |
325 | x: std::string("example-check.ExampleOption" ), y: std::string("0" ))); |
326 | EXPECT_TRUE(compileAndApply()); |
327 | EXPECT_EQ(Conf.Diagnostics.ClangTidy.CheckOptions.size(), 2U); |
328 | EXPECT_EQ(Conf.Diagnostics.ClangTidy.CheckOptions.lookup("StrictMode" ), |
329 | "true" ); |
330 | EXPECT_EQ(Conf.Diagnostics.ClangTidy.CheckOptions.lookup( |
331 | "example-check.ExampleOption" ), |
332 | "0" ); |
333 | #if CLANGD_TIDY_CHECKS |
334 | EXPECT_EQ( |
335 | Conf.Diagnostics.ClangTidy.Checks, |
336 | "bugprone-use-after-move,llvm-*,-llvm-include-order,-readability-*" ); |
337 | EXPECT_THAT(Diags.Diagnostics, IsEmpty()); |
338 | #else // !CLANGD_TIDY_CHECKS |
339 | EXPECT_EQ(Conf.Diagnostics.ClangTidy.Checks, "llvm-*,-readability-*" ); |
340 | EXPECT_THAT( |
341 | Diags.Diagnostics, |
342 | ElementsAre( |
343 | diagMessage( |
344 | "clang-tidy check 'bugprone-use-after-move' was not found" ), |
345 | diagMessage("clang-tidy check 'llvm-include-order' was not found" ))); |
346 | #endif |
347 | } |
348 | |
349 | TEST_F(ConfigCompileTests, TidyBadChecks) { |
350 | auto &Tidy = Frag.Diagnostics.ClangTidy; |
351 | Tidy.Add.emplace_back(args: "unknown-check" ); |
352 | Tidy.Remove.emplace_back(args: "*" ); |
353 | Tidy.Remove.emplace_back(args: "llvm-includeorder" ); |
354 | EXPECT_TRUE(compileAndApply()); |
355 | // Ensure bad checks are stripped from the glob. |
356 | EXPECT_EQ(Conf.Diagnostics.ClangTidy.Checks, "-*" ); |
357 | EXPECT_THAT( |
358 | Diags.Diagnostics, |
359 | ElementsAre( |
360 | AllOf(diagMessage("clang-tidy check 'unknown-check' was not found" ), |
361 | diagKind(llvm::SourceMgr::DK_Warning)), |
362 | AllOf( |
363 | diagMessage("clang-tidy check 'llvm-includeorder' was not found" ), |
364 | diagKind(llvm::SourceMgr::DK_Warning)))); |
365 | } |
366 | |
367 | TEST_F(ConfigCompileTests, ExternalServerNeedsTrusted) { |
368 | Fragment::IndexBlock::ExternalBlock External; |
369 | External.Server.emplace(args: "xxx" ); |
370 | Frag.Index.External = std::move(External); |
371 | compileAndApply(); |
372 | EXPECT_THAT( |
373 | Diags.Diagnostics, |
374 | ElementsAre(diagMessage( |
375 | "Remote index may not be specified by untrusted configuration. " |
376 | "Copy this into user config to use it." ))); |
377 | EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); |
378 | } |
379 | |
380 | TEST_F(ConfigCompileTests, ExternalBlockWarnOnMultipleSource) { |
381 | Frag.Source.Trusted = true; |
382 | Fragment::IndexBlock::ExternalBlock External; |
383 | External.File.emplace(args: "" ); |
384 | External.Server.emplace(args: "" ); |
385 | Frag.Index.External = std::move(External); |
386 | compileAndApply(); |
387 | #ifdef CLANGD_ENABLE_REMOTE |
388 | EXPECT_THAT( |
389 | Diags.Diagnostics, |
390 | Contains( |
391 | AllOf(diagMessage("Exactly one of File, Server or None must be set." ), |
392 | diagKind(llvm::SourceMgr::DK_Error)))); |
393 | #else |
394 | ASSERT_TRUE(Conf.Index.External.hasValue()); |
395 | EXPECT_EQ(Conf.Index.External->Kind, Config::ExternalIndexSpec::File); |
396 | #endif |
397 | } |
398 | |
399 | TEST_F(ConfigCompileTests, ExternalBlockDisableWithNone) { |
400 | compileAndApply(); |
401 | EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); |
402 | |
403 | Fragment::IndexBlock::ExternalBlock External; |
404 | External.IsNone = true; |
405 | Frag.Index.External = std::move(External); |
406 | compileAndApply(); |
407 | EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); |
408 | } |
409 | |
410 | TEST_F(ConfigCompileTests, ExternalBlockErrOnNoSource) { |
411 | Frag.Index.External.emplace(args: Fragment::IndexBlock::ExternalBlock{}); |
412 | compileAndApply(); |
413 | EXPECT_THAT( |
414 | Diags.Diagnostics, |
415 | Contains( |
416 | AllOf(diagMessage("Exactly one of File, Server or None must be set." ), |
417 | diagKind(llvm::SourceMgr::DK_Error)))); |
418 | } |
419 | |
420 | TEST_F(ConfigCompileTests, ExternalBlockDisablesBackgroundIndex) { |
421 | auto BazPath = testPath(File: "foo/bar/baz.h" , llvm::sys::path::Style::posix); |
422 | Parm.Path = BazPath; |
423 | Frag.Index.Background.emplace(args: "Build" ); |
424 | Fragment::IndexBlock::ExternalBlock External; |
425 | External.File.emplace(args: testPath(File: "foo" )); |
426 | External.MountPoint.emplace( |
427 | args: testPath(File: "foo/bar" , llvm::sys::path::Style::posix)); |
428 | Frag.Index.External = std::move(External); |
429 | compileAndApply(); |
430 | EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Skip); |
431 | } |
432 | |
433 | TEST_F(ConfigCompileTests, ExternalBlockMountPoint) { |
434 | auto GetFrag = [](llvm::StringRef Directory, |
435 | std::optional<const char *> MountPoint) { |
436 | Fragment Frag; |
437 | Frag.Source.Directory = Directory.str(); |
438 | Fragment::IndexBlock::ExternalBlock External; |
439 | External.File.emplace(args: testPath(File: "foo" )); |
440 | if (MountPoint) |
441 | External.MountPoint.emplace(args&: *MountPoint); |
442 | Frag.Index.External = std::move(External); |
443 | return Frag; |
444 | }; |
445 | |
446 | auto BarPath = testPath(File: "foo/bar.h" , llvm::sys::path::Style::posix); |
447 | BarPath = llvm::sys::path::convert_to_slash(path: BarPath); |
448 | Parm.Path = BarPath; |
449 | // Non-absolute MountPoint without a directory raises an error. |
450 | Frag = GetFrag("" , "foo" ); |
451 | compileAndApply(); |
452 | ASSERT_THAT( |
453 | Diags.Diagnostics, |
454 | ElementsAre( |
455 | AllOf(diagMessage("MountPoint must be an absolute path, because this " |
456 | "fragment is not associated with any directory." ), |
457 | diagKind(llvm::SourceMgr::DK_Error)))); |
458 | EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); |
459 | |
460 | auto FooPath = testPath(File: "foo/" , llvm::sys::path::Style::posix); |
461 | FooPath = llvm::sys::path::convert_to_slash(path: FooPath); |
462 | // Ok when relative. |
463 | Frag = GetFrag(testRoot(), "foo/" ); |
464 | compileAndApply(); |
465 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
466 | ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File); |
467 | EXPECT_THAT(Conf.Index.External.MountPoint, FooPath); |
468 | |
469 | // None defaults to ".". |
470 | Frag = GetFrag(FooPath, std::nullopt); |
471 | compileAndApply(); |
472 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
473 | ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File); |
474 | EXPECT_THAT(Conf.Index.External.MountPoint, FooPath); |
475 | |
476 | // Without a file, external index is empty. |
477 | Parm.Path = "" ; |
478 | Frag = GetFrag("" , FooPath.c_str()); |
479 | compileAndApply(); |
480 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
481 | ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); |
482 | |
483 | // File outside MountPoint, no index. |
484 | auto BazPath = testPath(File: "bar/baz.h" , llvm::sys::path::Style::posix); |
485 | BazPath = llvm::sys::path::convert_to_slash(path: BazPath); |
486 | Parm.Path = BazPath; |
487 | Frag = GetFrag("" , FooPath.c_str()); |
488 | compileAndApply(); |
489 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
490 | ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); |
491 | |
492 | // File under MountPoint, index should be set. |
493 | BazPath = testPath(File: "foo/baz.h" , llvm::sys::path::Style::posix); |
494 | BazPath = llvm::sys::path::convert_to_slash(path: BazPath); |
495 | Parm.Path = BazPath; |
496 | Frag = GetFrag("" , FooPath.c_str()); |
497 | compileAndApply(); |
498 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
499 | ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File); |
500 | EXPECT_THAT(Conf.Index.External.MountPoint, FooPath); |
501 | |
502 | // Only matches case-insensitively. |
503 | BazPath = testPath(File: "fOo/baz.h" , llvm::sys::path::Style::posix); |
504 | BazPath = llvm::sys::path::convert_to_slash(path: BazPath); |
505 | Parm.Path = BazPath; |
506 | |
507 | FooPath = testPath(File: "FOO/" , llvm::sys::path::Style::posix); |
508 | FooPath = llvm::sys::path::convert_to_slash(path: FooPath); |
509 | Frag = GetFrag("" , FooPath.c_str()); |
510 | compileAndApply(); |
511 | ASSERT_THAT(Diags.Diagnostics, IsEmpty()); |
512 | #ifdef CLANGD_PATH_CASE_INSENSITIVE |
513 | ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File); |
514 | EXPECT_THAT(Conf.Index.External.MountPoint, FooPath); |
515 | #else |
516 | ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None); |
517 | #endif |
518 | } |
519 | |
520 | TEST_F(ConfigCompileTests, AllScopes) { |
521 | // Defaults to true. |
522 | EXPECT_TRUE(compileAndApply()); |
523 | EXPECT_TRUE(Conf.Completion.AllScopes); |
524 | |
525 | Frag = {}; |
526 | Frag.Completion.AllScopes = false; |
527 | EXPECT_TRUE(compileAndApply()); |
528 | EXPECT_FALSE(Conf.Completion.AllScopes); |
529 | |
530 | Frag = {}; |
531 | Frag.Completion.AllScopes = true; |
532 | EXPECT_TRUE(compileAndApply()); |
533 | EXPECT_TRUE(Conf.Completion.AllScopes); |
534 | } |
535 | |
536 | TEST_F(ConfigCompileTests, Style) { |
537 | Frag = {}; |
538 | Frag.Style.FullyQualifiedNamespaces.push_back(x: std::string("foo" )); |
539 | Frag.Style.FullyQualifiedNamespaces.push_back(x: std::string("bar" )); |
540 | EXPECT_TRUE(compileAndApply()); |
541 | EXPECT_THAT(Conf.Style.FullyQualifiedNamespaces, ElementsAre("foo" , "bar" )); |
542 | } |
543 | } // namespace |
544 | } // namespace config |
545 | } // namespace clangd |
546 | } // namespace clang |
547 | |