• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "Features.inc"
13 #include "TestFS.h"
14 #include "llvm/ADT/None.h"
15 #include "llvm/ADT/Optional.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 <string>
22 
23 namespace clang {
24 namespace clangd {
25 namespace config {
26 namespace {
27 using ::testing::AllOf;
28 using ::testing::Contains;
29 using ::testing::ElementsAre;
30 using ::testing::IsEmpty;
31 using ::testing::SizeIs;
32 using ::testing::StartsWith;
33 
34 class ConfigCompileTests : public ::testing::Test {
35 protected:
36   CapturedDiags Diags;
37   Config Conf;
38   Fragment Frag;
39   Params Parm;
40 
compileAndApply()41   bool compileAndApply() {
42     Conf = Config();
43     Diags.Diagnostics.clear();
44     auto Compiled = std::move(Frag).compile(Diags.callback());
45     return Compiled(Parm, Conf);
46   }
47 };
48 
TEST_F(ConfigCompileTests,Condition)49 TEST_F(ConfigCompileTests, Condition) {
50   // No condition.
51   Frag = {};
52   Frag.CompileFlags.Add.emplace_back("X");
53   EXPECT_TRUE(compileAndApply()) << "Empty config";
54   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
55   EXPECT_THAT(Conf.CompileFlags.Edits, SizeIs(1));
56 
57   // Regex with no file.
58   Frag = {};
59   Frag.If.PathMatch.emplace_back("fo*");
60   EXPECT_FALSE(compileAndApply());
61   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
62   EXPECT_THAT(Conf.CompileFlags.Edits, SizeIs(0));
63 
64   // Following tests have a file path set.
65   Parm.Path = "bar";
66 
67   // Non-matching regex.
68   Frag = {};
69   Frag.If.PathMatch.emplace_back("fo*");
70   EXPECT_FALSE(compileAndApply());
71   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
72 
73   // Matching regex.
74   Frag = {};
75   Frag.If.PathMatch.emplace_back("fo*");
76   Frag.If.PathMatch.emplace_back("ba*r");
77   EXPECT_TRUE(compileAndApply());
78   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
79 
80   // Excluded regex.
81   Frag = {};
82   Frag.If.PathMatch.emplace_back("b.*");
83   Frag.If.PathExclude.emplace_back(".*r");
84   EXPECT_FALSE(compileAndApply()) << "Included but also excluded";
85   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
86 
87   // Invalid regex.
88   Frag = {};
89   Frag.If.PathMatch.emplace_back("**]@theu");
90   EXPECT_TRUE(compileAndApply());
91   EXPECT_THAT(Diags.Diagnostics, SizeIs(1));
92   EXPECT_THAT(Diags.Diagnostics.front().Message, StartsWith("Invalid regex"));
93 
94   // Valid regex and unknown key.
95   Frag = {};
96   Frag.If.HasUnrecognizedCondition = true;
97   Frag.If.PathMatch.emplace_back("ba*r");
98   EXPECT_FALSE(compileAndApply());
99   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
100 }
101 
TEST_F(ConfigCompileTests,CompileCommands)102 TEST_F(ConfigCompileTests, CompileCommands) {
103   Frag.CompileFlags.Add.emplace_back("-foo");
104   Frag.CompileFlags.Remove.emplace_back("--include-directory=");
105   std::vector<std::string> Argv = {"clang", "-I", "bar/", "a.cc"};
106   EXPECT_TRUE(compileAndApply());
107   EXPECT_THAT(Conf.CompileFlags.Edits, SizeIs(2));
108   for (auto &Edit : Conf.CompileFlags.Edits)
109     Edit(Argv);
110   EXPECT_THAT(Argv, ElementsAre("clang", "a.cc", "-foo"));
111 }
112 
TEST_F(ConfigCompileTests,Index)113 TEST_F(ConfigCompileTests, Index) {
114   Frag.Index.Background.emplace("Skip");
115   EXPECT_TRUE(compileAndApply());
116   EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Skip);
117 
118   Frag = {};
119   Frag.Index.Background.emplace("Foo");
120   EXPECT_TRUE(compileAndApply());
121   EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Build)
122       << "by default";
123   EXPECT_THAT(
124       Diags.Diagnostics,
125       ElementsAre(DiagMessage(
126           "Invalid Background value 'Foo'. Valid values are Build, Skip.")));
127 }
128 
TEST_F(ConfigCompileTests,PathSpecMatch)129 TEST_F(ConfigCompileTests, PathSpecMatch) {
130   auto BarPath = llvm::sys::path::convert_to_slash(testPath("foo/bar.h"));
131   Parm.Path = BarPath;
132 
133   struct {
134     std::string Directory;
135     std::string PathSpec;
136     bool ShouldMatch;
137   } Cases[] = {
138       {
139           // Absolute path matches.
140           "",
141           llvm::sys::path::convert_to_slash(testPath("foo/bar.h")),
142           true,
143       },
144       {
145           // Absolute path fails.
146           "",
147           llvm::sys::path::convert_to_slash(testPath("bar/bar.h")),
148           false,
149       },
150       {
151           // Relative should fail to match as /foo/bar.h doesn't reside under
152           // /baz/.
153           testPath("baz"),
154           "bar\\.h",
155           false,
156       },
157       {
158           // Relative should pass with /foo as directory.
159           testPath("foo"),
160           "bar\\.h",
161           true,
162       },
163   };
164 
165   // PathMatch
166   for (const auto &Case : Cases) {
167     Frag = {};
168     Frag.If.PathMatch.emplace_back(Case.PathSpec);
169     Frag.Source.Directory = Case.Directory;
170     EXPECT_EQ(compileAndApply(), Case.ShouldMatch);
171     ASSERT_THAT(Diags.Diagnostics, IsEmpty());
172   }
173 
174   // PathEclude
175   for (const auto &Case : Cases) {
176     SCOPED_TRACE(Case.Directory);
177     SCOPED_TRACE(Case.PathSpec);
178     Frag = {};
179     Frag.If.PathExclude.emplace_back(Case.PathSpec);
180     Frag.Source.Directory = Case.Directory;
181     EXPECT_NE(compileAndApply(), Case.ShouldMatch);
182     ASSERT_THAT(Diags.Diagnostics, IsEmpty());
183   }
184 }
185 
TEST_F(ConfigCompileTests,Tidy)186 TEST_F(ConfigCompileTests, Tidy) {
187   Frag.ClangTidy.Add.emplace_back("bugprone-use-after-move");
188   Frag.ClangTidy.Add.emplace_back("llvm-*");
189   Frag.ClangTidy.Remove.emplace_back("llvm-include-order");
190   Frag.ClangTidy.Remove.emplace_back("readability-*");
191   Frag.ClangTidy.CheckOptions.emplace_back(
192       std::make_pair(std::string("StrictMode"), std::string("true")));
193   Frag.ClangTidy.CheckOptions.emplace_back(std::make_pair(
194       std::string("example-check.ExampleOption"), std::string("0")));
195   EXPECT_TRUE(compileAndApply());
196   EXPECT_EQ(
197       Conf.ClangTidy.Checks,
198       "bugprone-use-after-move,llvm-*,-llvm-include-order,-readability-*");
199   EXPECT_EQ(Conf.ClangTidy.CheckOptions.size(), 2U);
200   EXPECT_EQ(Conf.ClangTidy.CheckOptions.lookup("StrictMode"), "true");
201   EXPECT_EQ(Conf.ClangTidy.CheckOptions.lookup("example-check.ExampleOption"),
202             "0");
203 }
204 
TEST_F(ConfigCompileTests,ExternalBlockWarnOnMultipleSource)205 TEST_F(ConfigCompileTests, ExternalBlockWarnOnMultipleSource) {
206   Fragment::IndexBlock::ExternalBlock External;
207   External.File.emplace("");
208   External.Server.emplace("");
209   Frag.Index.External = std::move(External);
210   compileAndApply();
211   llvm::StringLiteral ExpectedDiag =
212 #ifdef CLANGD_ENABLE_REMOTE
213       "Exactly one of File or Server must be set.";
214 #else
215       "Clangd isn't compiled with remote index support, ignoring Server.";
216 #endif
217   EXPECT_THAT(Diags.Diagnostics,
218               Contains(AllOf(DiagMessage(ExpectedDiag),
219                              DiagKind(llvm::SourceMgr::DK_Error))));
220 }
221 
TEST_F(ConfigCompileTests,ExternalBlockErrOnNoSource)222 TEST_F(ConfigCompileTests, ExternalBlockErrOnNoSource) {
223   Frag.Index.External.emplace(Fragment::IndexBlock::ExternalBlock{});
224   compileAndApply();
225   EXPECT_THAT(
226       Diags.Diagnostics,
227       Contains(AllOf(DiagMessage("Exactly one of File or Server must be set."),
228                      DiagKind(llvm::SourceMgr::DK_Error))));
229 }
230 
TEST_F(ConfigCompileTests,ExternalBlockDisablesBackgroundIndex)231 TEST_F(ConfigCompileTests, ExternalBlockDisablesBackgroundIndex) {
232   auto BazPath = testPath("foo/bar/baz.h", llvm::sys::path::Style::posix);
233   Parm.Path = BazPath;
234   Frag.Index.Background.emplace("Build");
235   Fragment::IndexBlock::ExternalBlock External;
236   External.File.emplace(testPath("foo"));
237   External.MountPoint.emplace(
238       testPath("foo/bar", llvm::sys::path::Style::posix));
239   Frag.Index.External = std::move(External);
240   compileAndApply();
241   EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Skip);
242 }
243 
TEST_F(ConfigCompileTests,ExternalBlockMountPoint)244 TEST_F(ConfigCompileTests, ExternalBlockMountPoint) {
245   auto GetFrag = [](llvm::StringRef Directory,
246                     llvm::Optional<const char *> MountPoint) {
247     Fragment Frag;
248     Frag.Source.Directory = Directory.str();
249     Fragment::IndexBlock::ExternalBlock External;
250     External.File.emplace(testPath("foo"));
251     if (MountPoint)
252       External.MountPoint.emplace(*MountPoint);
253     Frag.Index.External = std::move(External);
254     return Frag;
255   };
256 
257   auto BarPath = testPath("foo/bar.h", llvm::sys::path::Style::posix);
258   BarPath = llvm::sys::path::convert_to_slash(BarPath);
259   Parm.Path = BarPath;
260   // Non-absolute MountPoint without a directory raises an error.
261   Frag = GetFrag("", "foo");
262   compileAndApply();
263   ASSERT_THAT(
264       Diags.Diagnostics,
265       ElementsAre(
266           AllOf(DiagMessage("MountPoint must be an absolute path, because this "
267                             "fragment is not associated with any directory."),
268                 DiagKind(llvm::SourceMgr::DK_Error))));
269   ASSERT_FALSE(Conf.Index.External);
270 
271   auto FooPath = testPath("foo/", llvm::sys::path::Style::posix);
272   FooPath = llvm::sys::path::convert_to_slash(FooPath);
273   // Ok when relative.
274   Frag = GetFrag(testRoot(), "foo/");
275   compileAndApply();
276   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
277   ASSERT_TRUE(Conf.Index.External);
278   EXPECT_THAT(Conf.Index.External->MountPoint, FooPath);
279 
280   // None defaults to ".".
281   Frag = GetFrag(FooPath, llvm::None);
282   compileAndApply();
283   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
284   ASSERT_TRUE(Conf.Index.External);
285   EXPECT_THAT(Conf.Index.External->MountPoint, FooPath);
286 
287   // Without a file, external index is empty.
288   Parm.Path = "";
289   Frag = GetFrag("", FooPath.c_str());
290   compileAndApply();
291   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
292   ASSERT_FALSE(Conf.Index.External);
293 
294   // File outside MountPoint, no index.
295   auto BazPath = testPath("bar/baz.h", llvm::sys::path::Style::posix);
296   BazPath = llvm::sys::path::convert_to_slash(BazPath);
297   Parm.Path = BazPath;
298   Frag = GetFrag("", FooPath.c_str());
299   compileAndApply();
300   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
301   ASSERT_FALSE(Conf.Index.External);
302 
303   // File under MountPoint, index should be set.
304   BazPath = testPath("foo/baz.h", llvm::sys::path::Style::posix);
305   BazPath = llvm::sys::path::convert_to_slash(BazPath);
306   Parm.Path = BazPath;
307   Frag = GetFrag("", FooPath.c_str());
308   compileAndApply();
309   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
310   ASSERT_TRUE(Conf.Index.External);
311   EXPECT_THAT(Conf.Index.External->MountPoint, FooPath);
312 }
313 } // namespace
314 } // namespace config
315 } // namespace clangd
316 } // namespace clang
317