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 "Feature.h"
13 #include "TestFS.h"
14 #include "clang/Basic/DiagnosticSema.h"
15 #include "llvm/ADT/None.h"
16 #include "llvm/ADT/Optional.h"
17 #include "llvm/ADT/StringRef.h"
18 #include "llvm/Support/Path.h"
19 #include "llvm/Support/SourceMgr.h"
20 #include "gmock/gmock.h"
21 #include "gtest/gtest.h"
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("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("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("fo*");
72   EXPECT_FALSE(compileAndApply());
73   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
74 
75   // Matching regex.
76   Frag = {};
77   Frag.If.PathMatch.emplace_back("fo*");
78   Frag.If.PathMatch.emplace_back("ba*r");
79   EXPECT_TRUE(compileAndApply());
80   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
81 
82   // Excluded regex.
83   Frag = {};
84   Frag.If.PathMatch.emplace_back("b.*");
85   Frag.If.PathExclude.emplace_back(".*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("**]@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("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("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("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.Add.emplace_back("-foo");
125   Frag.CompileFlags.Remove.emplace_back("--include-directory=");
126   std::vector<std::string> Argv = {"clang", "-I", "bar/", "--", "a.cc"};
127   EXPECT_TRUE(compileAndApply());
128   EXPECT_THAT(Conf.CompileFlags.Edits, SizeIs(2));
129   for (auto &Edit : Conf.CompileFlags.Edits)
130     Edit(Argv);
131   EXPECT_THAT(Argv, ElementsAre("clang", "-foo", "--", "a.cc"));
132 }
133 
134 TEST_F(ConfigCompileTests, CompilationDatabase) {
135   Frag.CompileFlags.CompilationDatabase.emplace("None");
136   EXPECT_TRUE(compileAndApply());
137   EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy,
138             Config::CDBSearchSpec::NoCDBSearch);
139 
140   Frag.CompileFlags.CompilationDatabase.emplace("Ancestors");
141   EXPECT_TRUE(compileAndApply());
142   EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy,
143             Config::CDBSearchSpec::Ancestors);
144 
145   // Relative path not allowed without directory set.
146   Frag.CompileFlags.CompilationDatabase.emplace("Something");
147   EXPECT_TRUE(compileAndApply());
148   EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy,
149             Config::CDBSearchSpec::Ancestors)
150       << "default value";
151   EXPECT_THAT(Diags.Diagnostics,
152               ElementsAre(DiagMessage(
153                   "CompilationDatabase must be an absolute path, because this "
154                   "fragment is not associated with any directory.")));
155 
156   // Relative path allowed if directory is set.
157   Frag.Source.Directory = testRoot();
158   EXPECT_TRUE(compileAndApply());
159   EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy,
160             Config::CDBSearchSpec::FixedDir);
161   EXPECT_EQ(Conf.CompileFlags.CDBSearch.FixedCDBPath, testPath("Something"));
162   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
163 
164   // Absolute path allowed.
165   Frag.Source.Directory.clear();
166   Frag.CompileFlags.CompilationDatabase.emplace(testPath("Something2"));
167   EXPECT_TRUE(compileAndApply());
168   EXPECT_EQ(Conf.CompileFlags.CDBSearch.Policy,
169             Config::CDBSearchSpec::FixedDir);
170   EXPECT_EQ(Conf.CompileFlags.CDBSearch.FixedCDBPath, testPath("Something2"));
171   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
172 }
173 
174 TEST_F(ConfigCompileTests, Index) {
175   Frag.Index.Background.emplace("Skip");
176   EXPECT_TRUE(compileAndApply());
177   EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Skip);
178 
179   Frag = {};
180   Frag.Index.Background.emplace("Foo");
181   EXPECT_TRUE(compileAndApply());
182   EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Build)
183       << "by default";
184   EXPECT_THAT(
185       Diags.Diagnostics,
186       ElementsAre(DiagMessage(
187           "Invalid Background value 'Foo'. Valid values are Build, Skip.")));
188 }
189 
190 TEST_F(ConfigCompileTests, PathSpecMatch) {
191   auto BarPath = llvm::sys::path::convert_to_slash(testPath("foo/bar.h"));
192   Parm.Path = BarPath;
193 
194   struct {
195     std::string Directory;
196     std::string PathSpec;
197     bool ShouldMatch;
198   } Cases[] = {
199       {
200           // Absolute path matches.
201           "",
202           llvm::sys::path::convert_to_slash(testPath("foo/bar.h")),
203           true,
204       },
205       {
206           // Absolute path fails.
207           "",
208           llvm::sys::path::convert_to_slash(testPath("bar/bar.h")),
209           false,
210       },
211       {
212           // Relative should fail to match as /foo/bar.h doesn't reside under
213           // /baz/.
214           testPath("baz"),
215           "bar\\.h",
216           false,
217       },
218       {
219           // Relative should pass with /foo as directory.
220           testPath("foo"),
221           "bar\\.h",
222           true,
223       },
224   };
225 
226   // PathMatch
227   for (const auto &Case : Cases) {
228     Frag = {};
229     Frag.If.PathMatch.emplace_back(Case.PathSpec);
230     Frag.Source.Directory = Case.Directory;
231     EXPECT_EQ(compileAndApply(), Case.ShouldMatch);
232     ASSERT_THAT(Diags.Diagnostics, IsEmpty());
233   }
234 
235   // PathEclude
236   for (const auto &Case : Cases) {
237     SCOPED_TRACE(Case.Directory);
238     SCOPED_TRACE(Case.PathSpec);
239     Frag = {};
240     Frag.If.PathExclude.emplace_back(Case.PathSpec);
241     Frag.Source.Directory = Case.Directory;
242     EXPECT_NE(compileAndApply(), Case.ShouldMatch);
243     ASSERT_THAT(Diags.Diagnostics, IsEmpty());
244   }
245 }
246 
247 TEST_F(ConfigCompileTests, DiagnosticsIncludeCleaner) {
248   // Defaults to None.
249   EXPECT_TRUE(compileAndApply());
250   EXPECT_EQ(Conf.Diagnostics.UnusedIncludes,
251             Config::UnusedIncludesPolicy::None);
252 
253   Frag = {};
254   Frag.Diagnostics.UnusedIncludes.emplace("None");
255   EXPECT_TRUE(compileAndApply());
256   EXPECT_EQ(Conf.Diagnostics.UnusedIncludes,
257             Config::UnusedIncludesPolicy::None);
258 
259   Frag = {};
260   Frag.Diagnostics.UnusedIncludes.emplace("Strict");
261   EXPECT_TRUE(compileAndApply());
262   EXPECT_EQ(Conf.Diagnostics.UnusedIncludes,
263             Config::UnusedIncludesPolicy::Strict);
264 }
265 
266 TEST_F(ConfigCompileTests, DiagnosticSuppression) {
267   Frag.Diagnostics.Suppress.emplace_back("bugprone-use-after-move");
268   Frag.Diagnostics.Suppress.emplace_back("unreachable-code");
269   Frag.Diagnostics.Suppress.emplace_back("-Wunused-variable");
270   Frag.Diagnostics.Suppress.emplace_back("typecheck_bool_condition");
271   Frag.Diagnostics.Suppress.emplace_back("err_unexpected_friend");
272   Frag.Diagnostics.Suppress.emplace_back("warn_alloca");
273   EXPECT_TRUE(compileAndApply());
274   EXPECT_THAT(Conf.Diagnostics.Suppress.keys(),
275               UnorderedElementsAre("bugprone-use-after-move",
276                                    "unreachable-code", "unused-variable",
277                                    "typecheck_bool_condition",
278                                    "unexpected_friend", "warn_alloca"));
279   EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::warn_unreachable,
280                                             Conf.Diagnostics.Suppress));
281   // Subcategory not respected/suppressed.
282   EXPECT_FALSE(isBuiltinDiagnosticSuppressed(diag::warn_unreachable_break,
283                                              Conf.Diagnostics.Suppress));
284   EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::warn_unused_variable,
285                                             Conf.Diagnostics.Suppress));
286   EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::err_typecheck_bool_condition,
287                                             Conf.Diagnostics.Suppress));
288   EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::err_unexpected_friend,
289                                             Conf.Diagnostics.Suppress));
290   EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::warn_alloca,
291                                             Conf.Diagnostics.Suppress));
292 
293   Frag.Diagnostics.Suppress.emplace_back("*");
294   EXPECT_TRUE(compileAndApply());
295   EXPECT_TRUE(Conf.Diagnostics.SuppressAll);
296   EXPECT_THAT(Conf.Diagnostics.Suppress, IsEmpty());
297 }
298 
299 TEST_F(ConfigCompileTests, Tidy) {
300   auto &Tidy = Frag.Diagnostics.ClangTidy;
301   Tidy.Add.emplace_back("bugprone-use-after-move");
302   Tidy.Add.emplace_back("llvm-*");
303   Tidy.Remove.emplace_back("llvm-include-order");
304   Tidy.Remove.emplace_back("readability-*");
305   Tidy.CheckOptions.emplace_back(
306       std::make_pair(std::string("StrictMode"), std::string("true")));
307   Tidy.CheckOptions.emplace_back(std::make_pair(
308       std::string("example-check.ExampleOption"), std::string("0")));
309   EXPECT_TRUE(compileAndApply());
310   EXPECT_EQ(Conf.Diagnostics.ClangTidy.CheckOptions.size(), 2U);
311   EXPECT_EQ(Conf.Diagnostics.ClangTidy.CheckOptions.lookup("StrictMode"),
312             "true");
313   EXPECT_EQ(Conf.Diagnostics.ClangTidy.CheckOptions.lookup(
314                 "example-check.ExampleOption"),
315             "0");
316 #if CLANGD_TIDY_CHECKS
317   EXPECT_EQ(
318       Conf.Diagnostics.ClangTidy.Checks,
319       "bugprone-use-after-move,llvm-*,-llvm-include-order,-readability-*");
320   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
321 #else // !CLANGD_TIDY_CHECKS
322   EXPECT_EQ(Conf.Diagnostics.ClangTidy.Checks, "llvm-*,-readability-*");
323   EXPECT_THAT(
324       Diags.Diagnostics,
325       ElementsAre(
326           DiagMessage(
327               "clang-tidy check 'bugprone-use-after-move' was not found"),
328           DiagMessage("clang-tidy check 'llvm-include-order' was not found")));
329 #endif
330 }
331 
332 TEST_F(ConfigCompileTests, TidyBadChecks) {
333   auto &Tidy = Frag.Diagnostics.ClangTidy;
334   Tidy.Add.emplace_back("unknown-check");
335   Tidy.Remove.emplace_back("*");
336   Tidy.Remove.emplace_back("llvm-includeorder");
337   EXPECT_TRUE(compileAndApply());
338   // Ensure bad checks are stripped from the glob.
339   EXPECT_EQ(Conf.Diagnostics.ClangTidy.Checks, "-*");
340   EXPECT_THAT(
341       Diags.Diagnostics,
342       ElementsAre(
343           AllOf(DiagMessage("clang-tidy check 'unknown-check' was not found"),
344                 DiagKind(llvm::SourceMgr::DK_Warning)),
345           AllOf(
346               DiagMessage("clang-tidy check 'llvm-includeorder' was not found"),
347               DiagKind(llvm::SourceMgr::DK_Warning))));
348 }
349 
350 TEST_F(ConfigCompileTests, ExternalServerNeedsTrusted) {
351   Fragment::IndexBlock::ExternalBlock External;
352   External.Server.emplace("xxx");
353   Frag.Index.External = std::move(External);
354   compileAndApply();
355   EXPECT_THAT(
356       Diags.Diagnostics,
357       ElementsAre(DiagMessage(
358           "Remote index may not be specified by untrusted configuration. "
359           "Copy this into user config to use it.")));
360   EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None);
361 }
362 
363 TEST_F(ConfigCompileTests, ExternalBlockWarnOnMultipleSource) {
364   Frag.Source.Trusted = true;
365   Fragment::IndexBlock::ExternalBlock External;
366   External.File.emplace("");
367   External.Server.emplace("");
368   Frag.Index.External = std::move(External);
369   compileAndApply();
370 #ifdef CLANGD_ENABLE_REMOTE
371   EXPECT_THAT(
372       Diags.Diagnostics,
373       Contains(
374           AllOf(DiagMessage("Exactly one of File, Server or None must be set."),
375                 DiagKind(llvm::SourceMgr::DK_Error))));
376 #else
377   ASSERT_TRUE(Conf.Index.External.hasValue());
378   EXPECT_EQ(Conf.Index.External->Kind, Config::ExternalIndexSpec::File);
379 #endif
380 }
381 
382 TEST_F(ConfigCompileTests, ExternalBlockDisableWithNone) {
383   compileAndApply();
384   EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None);
385 
386   Fragment::IndexBlock::ExternalBlock External;
387   External.IsNone = true;
388   Frag.Index.External = std::move(External);
389   compileAndApply();
390   EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None);
391 }
392 
393 TEST_F(ConfigCompileTests, ExternalBlockErrOnNoSource) {
394   Frag.Index.External.emplace(Fragment::IndexBlock::ExternalBlock{});
395   compileAndApply();
396   EXPECT_THAT(
397       Diags.Diagnostics,
398       Contains(
399           AllOf(DiagMessage("Exactly one of File, Server or None must be set."),
400                 DiagKind(llvm::SourceMgr::DK_Error))));
401 }
402 
403 TEST_F(ConfigCompileTests, ExternalBlockDisablesBackgroundIndex) {
404   auto BazPath = testPath("foo/bar/baz.h", llvm::sys::path::Style::posix);
405   Parm.Path = BazPath;
406   Frag.Index.Background.emplace("Build");
407   Fragment::IndexBlock::ExternalBlock External;
408   External.File.emplace(testPath("foo"));
409   External.MountPoint.emplace(
410       testPath("foo/bar", llvm::sys::path::Style::posix));
411   Frag.Index.External = std::move(External);
412   compileAndApply();
413   EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Skip);
414 }
415 
416 TEST_F(ConfigCompileTests, ExternalBlockMountPoint) {
417   auto GetFrag = [](llvm::StringRef Directory,
418                     llvm::Optional<const char *> MountPoint) {
419     Fragment Frag;
420     Frag.Source.Directory = Directory.str();
421     Fragment::IndexBlock::ExternalBlock External;
422     External.File.emplace(testPath("foo"));
423     if (MountPoint)
424       External.MountPoint.emplace(*MountPoint);
425     Frag.Index.External = std::move(External);
426     return Frag;
427   };
428 
429   auto BarPath = testPath("foo/bar.h", llvm::sys::path::Style::posix);
430   BarPath = llvm::sys::path::convert_to_slash(BarPath);
431   Parm.Path = BarPath;
432   // Non-absolute MountPoint without a directory raises an error.
433   Frag = GetFrag("", "foo");
434   compileAndApply();
435   ASSERT_THAT(
436       Diags.Diagnostics,
437       ElementsAre(
438           AllOf(DiagMessage("MountPoint must be an absolute path, because this "
439                             "fragment is not associated with any directory."),
440                 DiagKind(llvm::SourceMgr::DK_Error))));
441   EXPECT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None);
442 
443   auto FooPath = testPath("foo/", llvm::sys::path::Style::posix);
444   FooPath = llvm::sys::path::convert_to_slash(FooPath);
445   // Ok when relative.
446   Frag = GetFrag(testRoot(), "foo/");
447   compileAndApply();
448   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
449   ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File);
450   EXPECT_THAT(Conf.Index.External.MountPoint, FooPath);
451 
452   // None defaults to ".".
453   Frag = GetFrag(FooPath, llvm::None);
454   compileAndApply();
455   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
456   ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File);
457   EXPECT_THAT(Conf.Index.External.MountPoint, FooPath);
458 
459   // Without a file, external index is empty.
460   Parm.Path = "";
461   Frag = GetFrag("", FooPath.c_str());
462   compileAndApply();
463   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
464   ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None);
465 
466   // File outside MountPoint, no index.
467   auto BazPath = testPath("bar/baz.h", llvm::sys::path::Style::posix);
468   BazPath = llvm::sys::path::convert_to_slash(BazPath);
469   Parm.Path = BazPath;
470   Frag = GetFrag("", FooPath.c_str());
471   compileAndApply();
472   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
473   ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None);
474 
475   // File under MountPoint, index should be set.
476   BazPath = testPath("foo/baz.h", llvm::sys::path::Style::posix);
477   BazPath = llvm::sys::path::convert_to_slash(BazPath);
478   Parm.Path = BazPath;
479   Frag = GetFrag("", FooPath.c_str());
480   compileAndApply();
481   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
482   ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File);
483   EXPECT_THAT(Conf.Index.External.MountPoint, FooPath);
484 
485   // Only matches case-insensitively.
486   BazPath = testPath("fOo/baz.h", llvm::sys::path::Style::posix);
487   BazPath = llvm::sys::path::convert_to_slash(BazPath);
488   Parm.Path = BazPath;
489 
490   FooPath = testPath("FOO/", llvm::sys::path::Style::posix);
491   FooPath = llvm::sys::path::convert_to_slash(FooPath);
492   Frag = GetFrag("", FooPath.c_str());
493   compileAndApply();
494   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
495 #ifdef CLANGD_PATH_CASE_INSENSITIVE
496   ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::File);
497   EXPECT_THAT(Conf.Index.External.MountPoint, FooPath);
498 #else
499   ASSERT_EQ(Conf.Index.External.Kind, Config::ExternalIndexSpec::None);
500 #endif
501 }
502 
503 TEST_F(ConfigCompileTests, AllScopes) {
504   // Defaults to true.
505   EXPECT_TRUE(compileAndApply());
506   EXPECT_TRUE(Conf.Completion.AllScopes);
507 
508   Frag = {};
509   Frag.Completion.AllScopes = false;
510   EXPECT_TRUE(compileAndApply());
511   EXPECT_FALSE(Conf.Completion.AllScopes);
512 
513   Frag = {};
514   Frag.Completion.AllScopes = true;
515   EXPECT_TRUE(compileAndApply());
516   EXPECT_TRUE(Conf.Completion.AllScopes);
517 }
518 } // namespace
519 } // namespace config
520 } // namespace clangd
521 } // namespace clang
522