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