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