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