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