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