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