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