xref: /llvm-project/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp (revision ae9bf17697d2245be707e93125f18d09eaf77aa9)
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           "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 
96 TEST(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(MemoryBuffer);
105 
106   EXPECT_TRUE(!!Options);
107   EXPECT_EQ("-*,misc-*\nllvm-*\n-clang-*,\ngoogle-*\n", *Options->Checks);
108 }
109 
110 TEST(ParseConfiguration, MergeConfigurations) {
111   llvm::ErrorOr<ClangTidyOptions> Options1 =
112       parseConfiguration(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(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(*Options2, 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 
161 namespace {
162 class DiagCollecter {
163 public:
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(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(D.getLoc(), 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(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(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 
213 private:
214   std::vector<Diag> Diags;
215 };
216 
217 MATCHER_P(DiagMessage, M, "") { return arg.Message == M; }
218 MATCHER_P(DiagKind, K, "") { return arg.Kind == K; }
219 MATCHER_P(DiagPos, P, "") { return arg.Pos == P; }
220 MATCHER_P(DiagRange, P, "") { return arg.Range && *arg.Range == P; }
221 } // namespace
222 
223 using ::testing::AllOf;
224 using ::testing::ElementsAre;
225 using ::testing::UnorderedElementsAre;
226 
227 TEST(ParseConfiguration, CollectDiags) {
228   DiagCollecter Collector;
229   auto ParseWithDiags = [&](llvm::StringRef Buffer) {
230     return parseConfigurationWithDiags(llvm::MemoryBufferRef(Buffer, "Options"),
231                                        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 
267 namespace {
268 class TestCheck : public ClangTidyCheck {
269 public:
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 
298 MATCHER_P(ToolDiagMessage, M, "") { return arg.Message.Message == M; }
299 MATCHER_P(ToolDiagLevel, L, "") { return arg.DiagLevel == L; }
300 
301 } // namespace
302 
303 } // namespace test
304 
305 static constexpr auto Warning = tooling::Diagnostic::Warning;
306 static constexpr auto Error = tooling::Diagnostic::Error;
307 
308 static void PrintTo(const ClangTidyError &Err, ::std::ostream *OS) {
309   *OS << (Err.DiagLevel == Error ? "error: " : "warning: ")
310       << Err.Message.Message;
311 }
312 
313 namespace test {
314 
315 TEST(CheckOptionsValidation, MissingOptions) {
316   ClangTidyOptions Options;
317   ClangTidyContext Context(std::make_unique<DefaultOptionsProvider>(
318       ClangTidyGlobalOptions(), 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 
330 TEST(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       ClangTidyGlobalOptions(), 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 
395 TEST(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       ClangTidyGlobalOptions(), 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   EXPECT_FALSE(TestCheck.getIntLocal<Colours>("ValidWrongCase").has_value());
421   EXPECT_FALSE(TestCheck.getIntLocal<Colours>("NearMiss").has_value());
422   EXPECT_FALSE(TestCheck.getIntGlobal<Colours>("GlobalInvalid").has_value());
423   EXPECT_FALSE(
424       TestCheck.getIntGlobal<Colours>("GlobalValidWrongCase").has_value());
425   EXPECT_FALSE(TestCheck.getIntGlobal<Colours>("GlobalNearMiss").has_value());
426 
427   EXPECT_FALSE(TestCheck.getIntLocal<Colours>("Invalid").has_value());
428   EXPECT_THAT(
429       DiagConsumer.take(),
430       UnorderedElementsAre(
431           AllOf(ToolDiagMessage("invalid configuration value "
432                                 "'Scarlet' for option 'test.Invalid'"),
433                 ToolDiagLevel(Warning)),
434           AllOf(ToolDiagMessage("invalid configuration value 'rED' for option "
435                                 "'test.ValidWrongCase'; did you mean 'Red'?"),
436                 ToolDiagLevel(Warning)),
437           AllOf(
438               ToolDiagMessage("invalid configuration value 'Oragne' for option "
439                               "'test.NearMiss'; did you mean 'Orange'?"),
440               ToolDiagLevel(Warning)),
441           AllOf(ToolDiagMessage("invalid configuration value "
442                                 "'Purple' for option 'GlobalInvalid'"),
443                 ToolDiagLevel(Warning)),
444           AllOf(
445               ToolDiagMessage("invalid configuration value 'vIOLET' for option "
446                               "'GlobalValidWrongCase'; did you mean 'Violet'?"),
447               ToolDiagLevel(Warning)),
448           AllOf(
449               ToolDiagMessage("invalid configuration value 'Yelow' for option "
450                               "'GlobalNearMiss'; did you mean 'Yellow'?"),
451               ToolDiagLevel(Warning))));
452 }
453 
454 #undef CHECK_VAL
455 
456 } // namespace test
457 } // namespace tidy
458 } // namespace clang
459