1#include "ClangTidyOptions.h"
2#include "ClangTidyCheck.h"
3#include "ClangTidyDiagnosticConsumer.h"
4#include "llvm/ADT/StringExtras.h"
5#include "llvm/Support/ScopedPrinter.h"
6#include "llvm/Testing/Annotations/Annotations.h"
7#include "gmock/gmock.h"
8#include "gtest/gtest.h"
9#include <optional>
10
11namespace clang {
12namespace tidy {
13
14enum class Colours { Red, Orange, Yellow, Green, Blue, Indigo, Violet };
15
16template <> struct OptionEnumMapping<Colours> {
17 static llvm::ArrayRef<std::pair<Colours, StringRef>> getEnumMapping() {
18 static constexpr std::pair<Colours, StringRef> Mapping[] = {
19 {Colours::Red, "Red"}, {Colours::Orange, "Orange"},
20 {Colours::Yellow, "Yellow"}, {Colours::Green, "Green"},
21 {Colours::Blue, "Blue"}, {Colours::Indigo, "Indigo"},
22 {Colours::Violet, "Violet"}};
23 return ArrayRef(Mapping);
24 }
25};
26
27namespace test {
28
29TEST(ParseLineFilter, EmptyFilter) {
30 ClangTidyGlobalOptions Options;
31 EXPECT_FALSE(parseLineFilter("", Options));
32 EXPECT_TRUE(Options.LineFilter.empty());
33 EXPECT_FALSE(parseLineFilter("[]", Options));
34 EXPECT_TRUE(Options.LineFilter.empty());
35}
36
37TEST(ParseLineFilter, InvalidFilter) {
38 ClangTidyGlobalOptions Options;
39 EXPECT_TRUE(!!parseLineFilter("asdf", Options));
40 EXPECT_TRUE(Options.LineFilter.empty());
41
42 EXPECT_TRUE(!!parseLineFilter("[{}]", Options));
43 EXPECT_TRUE(!!parseLineFilter("[{\"name\":\"\"}]", Options));
44 EXPECT_TRUE(
45 !!parseLineFilter("[{\"name\":\"test\",\"lines\":[[1]]}]", Options));
46 EXPECT_TRUE(
47 !!parseLineFilter("[{\"name\":\"test\",\"lines\":[[1,2,3]]}]", Options));
48 EXPECT_TRUE(
49 !!parseLineFilter("[{\"name\":\"test\",\"lines\":[[1,-1]]}]", Options));
50}
51
52TEST(ParseLineFilter, ValidFilter) {
53 ClangTidyGlobalOptions Options;
54 std::error_code Error = parseLineFilter(
55 LineFilter: "[{\"name\":\"file1.cpp\",\"lines\":[[3,15],[20,30],[42,42]]},"
56 "{\"name\":\"file2.h\"},"
57 "{\"name\":\"file3.cc\",\"lines\":[[100,1000]]}]",
58 Options);
59 EXPECT_FALSE(Error);
60 EXPECT_EQ(3u, Options.LineFilter.size());
61 EXPECT_EQ("file1.cpp", Options.LineFilter[0].Name);
62 EXPECT_EQ(3u, Options.LineFilter[0].LineRanges.size());
63 EXPECT_EQ(3u, Options.LineFilter[0].LineRanges[0].first);
64 EXPECT_EQ(15u, Options.LineFilter[0].LineRanges[0].second);
65 EXPECT_EQ(20u, Options.LineFilter[0].LineRanges[1].first);
66 EXPECT_EQ(30u, Options.LineFilter[0].LineRanges[1].second);
67 EXPECT_EQ(42u, Options.LineFilter[0].LineRanges[2].first);
68 EXPECT_EQ(42u, Options.LineFilter[0].LineRanges[2].second);
69 EXPECT_EQ("file2.h", Options.LineFilter[1].Name);
70 EXPECT_EQ(0u, Options.LineFilter[1].LineRanges.size());
71 EXPECT_EQ("file3.cc", Options.LineFilter[2].Name);
72 EXPECT_EQ(1u, Options.LineFilter[2].LineRanges.size());
73 EXPECT_EQ(100u, Options.LineFilter[2].LineRanges[0].first);
74 EXPECT_EQ(1000u, Options.LineFilter[2].LineRanges[0].second);
75}
76
77TEST(ParseConfiguration, ValidConfiguration) {
78 llvm::ErrorOr<ClangTidyOptions> Options =
79 parseConfiguration(Config: llvm::MemoryBufferRef(
80 "Checks: \"-*,misc-*\"\n"
81 "HeaderFileExtensions: [\"\",\"h\",\"hh\",\"hpp\",\"hxx\"]\n"
82 "ImplementationFileExtensions: [\"c\",\"cc\",\"cpp\",\"cxx\"]\n"
83 "HeaderFilterRegex: \".*\"\n"
84 "User: some.user",
85 "Options"));
86 EXPECT_TRUE(!!Options);
87 EXPECT_EQ("-*,misc-*", *Options->Checks);
88 EXPECT_EQ(std::vector<std::string>({"", "h", "hh", "hpp", "hxx"}),
89 *Options->HeaderFileExtensions);
90 EXPECT_EQ(std::vector<std::string>({"c", "cc", "cpp", "cxx"}),
91 *Options->ImplementationFileExtensions);
92 EXPECT_EQ(".*", *Options->HeaderFilterRegex);
93 EXPECT_EQ("some.user", *Options->User);
94}
95
96TEST(ParseConfiguration, ChecksSeparatedByNewlines) {
97 auto MemoryBuffer = llvm::MemoryBufferRef("Checks: |\n"
98 " -*,misc-*\n"
99 " llvm-*\n"
100 " -clang-*,\n"
101 " google-*",
102 "Options");
103
104 auto Options = parseConfiguration(Config: MemoryBuffer);
105
106 EXPECT_TRUE(!!Options);
107 EXPECT_EQ("-*,misc-*\nllvm-*\n-clang-*,\ngoogle-*\n", *Options->Checks);
108}
109
110TEST(ParseConfiguration, MergeConfigurations) {
111 llvm::ErrorOr<ClangTidyOptions> Options1 =
112 parseConfiguration(Config: llvm::MemoryBufferRef(R"(
113 Checks: "check1,check2"
114 HeaderFileExtensions: ["h","hh"]
115 ImplementationFileExtensions: ["c","cc"]
116 HeaderFilterRegex: "filter1"
117 User: user1
118 ExtraArgs: ['arg1', 'arg2']
119 ExtraArgsBefore: ['arg-before1', 'arg-before2']
120 UseColor: false
121 SystemHeaders: false
122 )",
123 "Options1"));
124 ASSERT_TRUE(!!Options1);
125 llvm::ErrorOr<ClangTidyOptions> Options2 =
126 parseConfiguration(Config: llvm::MemoryBufferRef(R"(
127 Checks: "check3,check4"
128 HeaderFileExtensions: ["hpp","hxx"]
129 ImplementationFileExtensions: ["cpp","cxx"]
130 HeaderFilterRegex: "filter2"
131 User: user2
132 ExtraArgs: ['arg3', 'arg4']
133 ExtraArgsBefore: ['arg-before3', 'arg-before4']
134 UseColor: true
135 SystemHeaders: true
136 )",
137 "Options2"));
138 ASSERT_TRUE(!!Options2);
139 ClangTidyOptions Options = Options1->merge(Other: *Options2, Order: 0);
140 EXPECT_EQ("check1,check2,check3,check4", *Options.Checks);
141 EXPECT_EQ(std::vector<std::string>({"hpp", "hxx"}),
142 *Options.HeaderFileExtensions);
143 EXPECT_EQ(std::vector<std::string>({"cpp", "cxx"}),
144 *Options.ImplementationFileExtensions);
145 EXPECT_EQ("filter2", *Options.HeaderFilterRegex);
146 EXPECT_EQ("user2", *Options.User);
147 ASSERT_TRUE(Options.ExtraArgs.has_value());
148 EXPECT_EQ("arg1,arg2,arg3,arg4", llvm::join(Options.ExtraArgs->begin(),
149 Options.ExtraArgs->end(), ","));
150 ASSERT_TRUE(Options.ExtraArgsBefore.has_value());
151 EXPECT_EQ("arg-before1,arg-before2,arg-before3,arg-before4",
152 llvm::join(Options.ExtraArgsBefore->begin(),
153 Options.ExtraArgsBefore->end(), ","));
154 ASSERT_TRUE(Options.UseColor.has_value());
155 EXPECT_TRUE(*Options.UseColor);
156
157 ASSERT_TRUE(Options.SystemHeaders.has_value());
158 EXPECT_TRUE(*Options.SystemHeaders);
159}
160
161namespace {
162class DiagCollecter {
163public:
164 struct Diag {
165 private:
166 static size_t posToOffset(const llvm::SMLoc Loc,
167 const llvm::SourceMgr *Src) {
168 return Loc.getPointer() -
169 Src->getMemoryBuffer(i: Src->FindBufferContainingLoc(Loc))
170 ->getBufferStart();
171 }
172
173 public:
174 Diag(const llvm::SMDiagnostic &D)
175 : Message(D.getMessage()), Kind(D.getKind()),
176 Pos(posToOffset(Loc: D.getLoc(), Src: D.getSourceMgr())) {
177 if (!D.getRanges().empty()) {
178 // Ranges are stored as column numbers on the line that has the error.
179 unsigned Offset = Pos - D.getColumnNo();
180 Range.emplace();
181 Range->Begin = Offset + D.getRanges().front().first,
182 Range->End = Offset + D.getRanges().front().second;
183 }
184 }
185 std::string Message;
186 llvm::SourceMgr::DiagKind Kind;
187 size_t Pos;
188 std::optional<llvm::Annotations::Range> Range;
189
190 friend void PrintTo(const Diag &D, std::ostream *OS) {
191 *OS << (D.Kind == llvm::SourceMgr::DK_Error ? "error: " : "warning: ")
192 << D.Message << "@" << llvm::to_string(Value: D.Pos);
193 if (D.Range)
194 *OS << ":[" << D.Range->Begin << ", " << D.Range->End << ")";
195 }
196 };
197
198 DiagCollecter() = default;
199 DiagCollecter(const DiagCollecter &) = delete;
200
201 std::function<void(const llvm::SMDiagnostic &)>
202 getCallback(bool Clear = true) & {
203 if (Clear)
204 Diags.clear();
205 return [&](const llvm::SMDiagnostic &Diag) { Diags.emplace_back(args: Diag); };
206 }
207
208 std::function<void(const llvm::SMDiagnostic &)>
209 getCallback(bool Clear = true) && = delete;
210
211 llvm::ArrayRef<Diag> getDiags() const { return Diags; }
212
213private:
214 std::vector<Diag> Diags;
215};
216
217MATCHER_P(DiagMessage, M, "") { return arg.Message == M; }
218MATCHER_P(DiagKind, K, "") { return arg.Kind == K; }
219MATCHER_P(DiagPos, P, "") { return arg.Pos == P; }
220MATCHER_P(DiagRange, P, "") { return arg.Range && *arg.Range == P; }
221} // namespace
222
223using ::testing::AllOf;
224using ::testing::ElementsAre;
225using ::testing::UnorderedElementsAre;
226
227TEST(ParseConfiguration, CollectDiags) {
228 DiagCollecter Collector;
229 auto ParseWithDiags = [&](llvm::StringRef Buffer) {
230 return parseConfigurationWithDiags(Config: llvm::MemoryBufferRef(Buffer, "Options"),
231 Handler: Collector.getCallback());
232 };
233 llvm::Annotations Options(R"(
234 [[Check]]: llvm-include-order
235 )");
236 llvm::ErrorOr<ClangTidyOptions> ParsedOpt = ParseWithDiags(Options.code());
237 EXPECT_TRUE(!ParsedOpt);
238 EXPECT_THAT(Collector.getDiags(),
239 testing::ElementsAre(AllOf(DiagMessage("unknown key 'Check'"),
240 DiagKind(llvm::SourceMgr::DK_Error),
241 DiagPos(Options.range().Begin),
242 DiagRange(Options.range()))));
243
244 Options = llvm::Annotations(R"(
245 UseColor: [[NotABool]]
246 )");
247 ParsedOpt = ParseWithDiags(Options.code());
248 EXPECT_TRUE(!ParsedOpt);
249 EXPECT_THAT(Collector.getDiags(),
250 testing::ElementsAre(AllOf(DiagMessage("invalid boolean"),
251 DiagKind(llvm::SourceMgr::DK_Error),
252 DiagPos(Options.range().Begin),
253 DiagRange(Options.range()))));
254
255 Options = llvm::Annotations(R"(
256 SystemHeaders: [[NotABool]]
257 )");
258 ParsedOpt = ParseWithDiags(Options.code());
259 EXPECT_TRUE(!ParsedOpt);
260 EXPECT_THAT(Collector.getDiags(),
261 testing::ElementsAre(AllOf(DiagMessage("invalid boolean"),
262 DiagKind(llvm::SourceMgr::DK_Error),
263 DiagPos(Options.range().Begin),
264 DiagRange(Options.range()))));
265}
266
267namespace {
268class TestCheck : public ClangTidyCheck {
269public:
270 TestCheck(ClangTidyContext *Context) : ClangTidyCheck("test", Context) {}
271
272 template <typename... Args> auto getLocal(Args &&... Arguments) {
273 return Options.get(std::forward<Args>(Arguments)...);
274 }
275
276 template <typename... Args> auto getGlobal(Args &&... Arguments) {
277 return Options.getLocalOrGlobal(std::forward<Args>(Arguments)...);
278 }
279
280 template <typename IntType = int, typename... Args>
281 auto getIntLocal(Args &&... Arguments) {
282 return Options.get<IntType>(std::forward<Args>(Arguments)...);
283 }
284
285 template <typename IntType = int, typename... Args>
286 auto getIntGlobal(Args &&... Arguments) {
287 return Options.getLocalOrGlobal<IntType>(std::forward<Args>(Arguments)...);
288 }
289};
290
291#define CHECK_VAL(Value, Expected) \
292 do { \
293 auto Item = Value; \
294 ASSERT_TRUE(!!Item); \
295 EXPECT_EQ(*Item, Expected); \
296 } while (false)
297
298MATCHER_P(ToolDiagMessage, M, "") { return arg.Message.Message == M; }
299MATCHER_P(ToolDiagLevel, L, "") { return arg.DiagLevel == L; }
300
301} // namespace
302
303} // namespace test
304
305static constexpr auto Warning = tooling::Diagnostic::Warning;
306static constexpr auto Error = tooling::Diagnostic::Error;
307
308static void PrintTo(const ClangTidyError &Err, ::std::ostream *OS) {
309 *OS << (Err.DiagLevel == Error ? "error: " : "warning: ")
310 << Err.Message.Message;
311}
312
313namespace test {
314
315TEST(CheckOptionsValidation, MissingOptions) {
316 ClangTidyOptions Options;
317 ClangTidyContext Context(std::make_unique<DefaultOptionsProvider>(
318 args: ClangTidyGlobalOptions(), args&: Options));
319 ClangTidyDiagnosticConsumer DiagConsumer(Context);
320 DiagnosticsEngine DE(new DiagnosticIDs(), new DiagnosticOptions,
321 &DiagConsumer, false);
322 Context.setDiagnosticsEngine(&DE);
323 TestCheck TestCheck(&Context);
324 EXPECT_FALSE(TestCheck.getLocal("Opt"));
325 EXPECT_EQ(TestCheck.getLocal("Opt", "Unknown"), "Unknown");
326 // Missing options aren't errors.
327 EXPECT_TRUE(DiagConsumer.take().empty());
328}
329
330TEST(CheckOptionsValidation, ValidIntOptions) {
331 ClangTidyOptions Options;
332 auto &CheckOptions = Options.CheckOptions;
333 CheckOptions["test.IntExpected"] = "1";
334 CheckOptions["test.IntInvalid1"] = "1WithMore";
335 CheckOptions["test.IntInvalid2"] = "NoInt";
336 CheckOptions["GlobalIntExpected"] = "1";
337 CheckOptions["GlobalIntInvalid"] = "NoInt";
338 CheckOptions["test.DefaultedIntInvalid"] = "NoInt";
339 CheckOptions["test.BoolITrueValue"] = "1";
340 CheckOptions["test.BoolIFalseValue"] = "0";
341 CheckOptions["test.BoolTrueValue"] = "true";
342 CheckOptions["test.BoolFalseValue"] = "false";
343 CheckOptions["test.BoolTrueShort"] = "Y";
344 CheckOptions["test.BoolFalseShort"] = "N";
345 CheckOptions["test.BoolUnparseable"] = "Nothing";
346
347 ClangTidyContext Context(std::make_unique<DefaultOptionsProvider>(
348 args: ClangTidyGlobalOptions(), args&: Options));
349 ClangTidyDiagnosticConsumer DiagConsumer(Context);
350 DiagnosticsEngine DE(new DiagnosticIDs(), new DiagnosticOptions,
351 &DiagConsumer, false);
352 Context.setDiagnosticsEngine(&DE);
353 TestCheck TestCheck(&Context);
354
355 CHECK_VAL(TestCheck.getIntLocal("IntExpected"), 1);
356 CHECK_VAL(TestCheck.getIntGlobal("GlobalIntExpected"), 1);
357 EXPECT_FALSE(TestCheck.getIntLocal("IntInvalid1").has_value());
358 EXPECT_FALSE(TestCheck.getIntLocal("IntInvalid2").has_value());
359 EXPECT_FALSE(TestCheck.getIntGlobal("GlobalIntInvalid").has_value());
360 ASSERT_EQ(TestCheck.getIntLocal("DefaultedIntInvalid", 1), 1);
361
362 CHECK_VAL(TestCheck.getIntLocal<bool>("BoolITrueValue"), true);
363 CHECK_VAL(TestCheck.getIntLocal<bool>("BoolIFalseValue"), false);
364 CHECK_VAL(TestCheck.getIntLocal<bool>("BoolTrueValue"), true);
365 CHECK_VAL(TestCheck.getIntLocal<bool>("BoolFalseValue"), false);
366 CHECK_VAL(TestCheck.getIntLocal<bool>("BoolTrueShort"), true);
367 CHECK_VAL(TestCheck.getIntLocal<bool>("BoolFalseShort"), false);
368 EXPECT_FALSE(TestCheck.getIntLocal<bool>("BoolUnparseable"));
369
370 EXPECT_THAT(
371 DiagConsumer.take(),
372 UnorderedElementsAre(
373 AllOf(ToolDiagMessage(
374 "invalid configuration value '1WithMore' for option "
375 "'test.IntInvalid1'; expected an integer"),
376 ToolDiagLevel(Warning)),
377 AllOf(
378 ToolDiagMessage("invalid configuration value 'NoInt' for option "
379 "'test.IntInvalid2'; expected an integer"),
380 ToolDiagLevel(Warning)),
381 AllOf(
382 ToolDiagMessage("invalid configuration value 'NoInt' for option "
383 "'GlobalIntInvalid'; expected an integer"),
384 ToolDiagLevel(Warning)),
385 AllOf(ToolDiagMessage(
386 "invalid configuration value 'NoInt' for option "
387 "'test.DefaultedIntInvalid'; expected an integer"),
388 ToolDiagLevel(Warning)),
389 AllOf(ToolDiagMessage(
390 "invalid configuration value 'Nothing' for option "
391 "'test.BoolUnparseable'; expected a bool"),
392 ToolDiagLevel(Warning))));
393}
394
395TEST(ValidConfiguration, ValidEnumOptions) {
396
397 ClangTidyOptions Options;
398 auto &CheckOptions = Options.CheckOptions;
399
400 CheckOptions["test.Valid"] = "Red";
401 CheckOptions["test.Invalid"] = "Scarlet";
402 CheckOptions["test.ValidWrongCase"] = "rED";
403 CheckOptions["test.NearMiss"] = "Oragne";
404 CheckOptions["GlobalValid"] = "Violet";
405 CheckOptions["GlobalInvalid"] = "Purple";
406 CheckOptions["GlobalValidWrongCase"] = "vIOLET";
407 CheckOptions["GlobalNearMiss"] = "Yelow";
408
409 ClangTidyContext Context(std::make_unique<DefaultOptionsProvider>(
410 args: ClangTidyGlobalOptions(), args&: Options));
411 ClangTidyDiagnosticConsumer DiagConsumer(Context);
412 DiagnosticsEngine DE(new DiagnosticIDs(), new DiagnosticOptions,
413 &DiagConsumer, false);
414 Context.setDiagnosticsEngine(&DE);
415 TestCheck TestCheck(&Context);
416
417 CHECK_VAL(TestCheck.getIntLocal<Colours>("Valid"), Colours::Red);
418 CHECK_VAL(TestCheck.getIntGlobal<Colours>("GlobalValid"), Colours::Violet);
419
420 CHECK_VAL(
421 TestCheck.getIntLocal<Colours>("ValidWrongCase", /*IgnoreCase*/ true),
422 Colours::Red);
423 CHECK_VAL(TestCheck.getIntGlobal<Colours>("GlobalValidWrongCase",
424 /*IgnoreCase*/ true),
425 Colours::Violet);
426
427 EXPECT_FALSE(TestCheck.getIntLocal<Colours>("ValidWrongCase").has_value());
428 EXPECT_FALSE(TestCheck.getIntLocal<Colours>("NearMiss").has_value());
429 EXPECT_FALSE(TestCheck.getIntGlobal<Colours>("GlobalInvalid").has_value());
430 EXPECT_FALSE(
431 TestCheck.getIntGlobal<Colours>("GlobalValidWrongCase").has_value());
432 EXPECT_FALSE(TestCheck.getIntGlobal<Colours>("GlobalNearMiss").has_value());
433
434 EXPECT_FALSE(TestCheck.getIntLocal<Colours>("Invalid").has_value());
435 EXPECT_THAT(
436 DiagConsumer.take(),
437 UnorderedElementsAre(
438 AllOf(ToolDiagMessage("invalid configuration value "
439 "'Scarlet' for option 'test.Invalid'"),
440 ToolDiagLevel(Warning)),
441 AllOf(ToolDiagMessage("invalid configuration value 'rED' for option "
442 "'test.ValidWrongCase'; did you mean 'Red'?"),
443 ToolDiagLevel(Warning)),
444 AllOf(
445 ToolDiagMessage("invalid configuration value 'Oragne' for option "
446 "'test.NearMiss'; did you mean 'Orange'?"),
447 ToolDiagLevel(Warning)),
448 AllOf(ToolDiagMessage("invalid configuration value "
449 "'Purple' for option 'GlobalInvalid'"),
450 ToolDiagLevel(Warning)),
451 AllOf(
452 ToolDiagMessage("invalid configuration value 'vIOLET' for option "
453 "'GlobalValidWrongCase'; did you mean 'Violet'?"),
454 ToolDiagLevel(Warning)),
455 AllOf(
456 ToolDiagMessage("invalid configuration value 'Yelow' for option "
457 "'GlobalNearMiss'; did you mean 'Yellow'?"),
458 ToolDiagLevel(Warning))));
459}
460
461#undef CHECK_VAL
462
463} // namespace test
464} // namespace tidy
465} // namespace clang
466

source code of clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp