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