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