#include "ClangTidyOptions.h" #include "ClangTidyCheck.h" #include "ClangTidyDiagnosticConsumer.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/Testing/Annotations/Annotations.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include namespace clang { namespace tidy { enum class Colours { Red, Orange, Yellow, Green, Blue, Indigo, Violet }; template <> struct OptionEnumMapping { static llvm::ArrayRef> getEnumMapping() { static constexpr std::pair Mapping[] = { {Colours::Red, "Red"}, {Colours::Orange, "Orange"}, {Colours::Yellow, "Yellow"}, {Colours::Green, "Green"}, {Colours::Blue, "Blue"}, {Colours::Indigo, "Indigo"}, {Colours::Violet, "Violet"}}; return ArrayRef(Mapping); } }; namespace test { TEST(ParseLineFilter, EmptyFilter) { ClangTidyGlobalOptions Options; EXPECT_FALSE(parseLineFilter("", Options)); EXPECT_TRUE(Options.LineFilter.empty()); EXPECT_FALSE(parseLineFilter("[]", Options)); EXPECT_TRUE(Options.LineFilter.empty()); } TEST(ParseLineFilter, InvalidFilter) { ClangTidyGlobalOptions Options; EXPECT_TRUE(!!parseLineFilter("asdf", Options)); EXPECT_TRUE(Options.LineFilter.empty()); EXPECT_TRUE(!!parseLineFilter("[{}]", Options)); EXPECT_TRUE(!!parseLineFilter("[{\"name\":\"\"}]", Options)); EXPECT_TRUE( !!parseLineFilter("[{\"name\":\"test\",\"lines\":[[1]]}]", Options)); EXPECT_TRUE( !!parseLineFilter("[{\"name\":\"test\",\"lines\":[[1,2,3]]}]", Options)); EXPECT_TRUE( !!parseLineFilter("[{\"name\":\"test\",\"lines\":[[1,-1]]}]", Options)); } TEST(ParseLineFilter, ValidFilter) { ClangTidyGlobalOptions Options; std::error_code Error = parseLineFilter( "[{\"name\":\"file1.cpp\",\"lines\":[[3,15],[20,30],[42,42]]}," "{\"name\":\"file2.h\"}," "{\"name\":\"file3.cc\",\"lines\":[[100,1000]]}]", Options); EXPECT_FALSE(Error); EXPECT_EQ(3u, Options.LineFilter.size()); EXPECT_EQ("file1.cpp", Options.LineFilter[0].Name); EXPECT_EQ(3u, Options.LineFilter[0].LineRanges.size()); EXPECT_EQ(3u, Options.LineFilter[0].LineRanges[0].first); EXPECT_EQ(15u, Options.LineFilter[0].LineRanges[0].second); EXPECT_EQ(20u, Options.LineFilter[0].LineRanges[1].first); EXPECT_EQ(30u, Options.LineFilter[0].LineRanges[1].second); EXPECT_EQ(42u, Options.LineFilter[0].LineRanges[2].first); EXPECT_EQ(42u, Options.LineFilter[0].LineRanges[2].second); EXPECT_EQ("file2.h", Options.LineFilter[1].Name); EXPECT_EQ(0u, Options.LineFilter[1].LineRanges.size()); EXPECT_EQ("file3.cc", Options.LineFilter[2].Name); EXPECT_EQ(1u, Options.LineFilter[2].LineRanges.size()); EXPECT_EQ(100u, Options.LineFilter[2].LineRanges[0].first); EXPECT_EQ(1000u, Options.LineFilter[2].LineRanges[0].second); } TEST(ParseConfiguration, ValidConfiguration) { llvm::ErrorOr Options = parseConfiguration(llvm::MemoryBufferRef( "Checks: \"-*,misc-*\"\n" "HeaderFileExtensions: [\"\",\"h\",\"hh\",\"hpp\",\"hxx\"]\n" "ImplementationFileExtensions: [\"c\",\"cc\",\"cpp\",\"cxx\"]\n" "HeaderFilterRegex: \".*\"\n" "User: some.user", "Options")); EXPECT_TRUE(!!Options); EXPECT_EQ("-*,misc-*", *Options->Checks); EXPECT_EQ(std::vector({"", "h", "hh", "hpp", "hxx"}), *Options->HeaderFileExtensions); EXPECT_EQ(std::vector({"c", "cc", "cpp", "cxx"}), *Options->ImplementationFileExtensions); EXPECT_EQ(".*", *Options->HeaderFilterRegex); EXPECT_EQ("some.user", *Options->User); } TEST(ParseConfiguration, ChecksSeparatedByNewlines) { auto MemoryBuffer = llvm::MemoryBufferRef("Checks: |\n" " -*,misc-*\n" " llvm-*\n" " -clang-*,\n" " google-*", "Options"); auto Options = parseConfiguration(MemoryBuffer); EXPECT_TRUE(!!Options); EXPECT_EQ("-*,misc-*\nllvm-*\n-clang-*,\ngoogle-*\n", *Options->Checks); } TEST(ParseConfiguration, MergeConfigurations) { llvm::ErrorOr Options1 = parseConfiguration(llvm::MemoryBufferRef(R"( Checks: "check1,check2" HeaderFileExtensions: ["h","hh"] ImplementationFileExtensions: ["c","cc"] HeaderFilterRegex: "filter1" User: user1 ExtraArgs: ['arg1', 'arg2'] ExtraArgsBefore: ['arg-before1', 'arg-before2'] UseColor: false SystemHeaders: false )", "Options1")); ASSERT_TRUE(!!Options1); llvm::ErrorOr Options2 = parseConfiguration(llvm::MemoryBufferRef(R"( Checks: "check3,check4" HeaderFileExtensions: ["hpp","hxx"] ImplementationFileExtensions: ["cpp","cxx"] HeaderFilterRegex: "filter2" User: user2 ExtraArgs: ['arg3', 'arg4'] ExtraArgsBefore: ['arg-before3', 'arg-before4'] UseColor: true SystemHeaders: true )", "Options2")); ASSERT_TRUE(!!Options2); ClangTidyOptions Options = Options1->merge(*Options2, 0); EXPECT_EQ("check1,check2,check3,check4", *Options.Checks); EXPECT_EQ(std::vector({"hpp", "hxx"}), *Options.HeaderFileExtensions); EXPECT_EQ(std::vector({"cpp", "cxx"}), *Options.ImplementationFileExtensions); EXPECT_EQ("filter2", *Options.HeaderFilterRegex); EXPECT_EQ("user2", *Options.User); ASSERT_TRUE(Options.ExtraArgs.has_value()); EXPECT_EQ("arg1,arg2,arg3,arg4", llvm::join(Options.ExtraArgs->begin(), Options.ExtraArgs->end(), ",")); ASSERT_TRUE(Options.ExtraArgsBefore.has_value()); EXPECT_EQ("arg-before1,arg-before2,arg-before3,arg-before4", llvm::join(Options.ExtraArgsBefore->begin(), Options.ExtraArgsBefore->end(), ",")); ASSERT_TRUE(Options.UseColor.has_value()); EXPECT_TRUE(*Options.UseColor); ASSERT_TRUE(Options.SystemHeaders.has_value()); EXPECT_TRUE(*Options.SystemHeaders); } namespace { class DiagCollecter { public: struct Diag { private: static size_t posToOffset(const llvm::SMLoc Loc, const llvm::SourceMgr *Src) { return Loc.getPointer() - Src->getMemoryBuffer(Src->FindBufferContainingLoc(Loc)) ->getBufferStart(); } public: Diag(const llvm::SMDiagnostic &D) : Message(D.getMessage()), Kind(D.getKind()), Pos(posToOffset(D.getLoc(), D.getSourceMgr())) { if (!D.getRanges().empty()) { // Ranges are stored as column numbers on the line that has the error. unsigned Offset = Pos - D.getColumnNo(); Range.emplace(); Range->Begin = Offset + D.getRanges().front().first, Range->End = Offset + D.getRanges().front().second; } } std::string Message; llvm::SourceMgr::DiagKind Kind; size_t Pos; std::optional Range; friend void PrintTo(const Diag &D, std::ostream *OS) { *OS << (D.Kind == llvm::SourceMgr::DK_Error ? "error: " : "warning: ") << D.Message << "@" << llvm::to_string(D.Pos); if (D.Range) *OS << ":[" << D.Range->Begin << ", " << D.Range->End << ")"; } }; DiagCollecter() = default; DiagCollecter(const DiagCollecter &) = delete; std::function getCallback(bool Clear = true) & { if (Clear) Diags.clear(); return [&](const llvm::SMDiagnostic &Diag) { Diags.emplace_back(Diag); }; } std::function getCallback(bool Clear = true) && = delete; llvm::ArrayRef getDiags() const { return Diags; } private: std::vector Diags; }; MATCHER_P(DiagMessage, M, "") { return arg.Message == M; } MATCHER_P(DiagKind, K, "") { return arg.Kind == K; } MATCHER_P(DiagPos, P, "") { return arg.Pos == P; } MATCHER_P(DiagRange, P, "") { return arg.Range && *arg.Range == P; } } // namespace using ::testing::AllOf; using ::testing::ElementsAre; using ::testing::UnorderedElementsAre; TEST(ParseConfiguration, CollectDiags) { DiagCollecter Collector; auto ParseWithDiags = [&](llvm::StringRef Buffer) { return parseConfigurationWithDiags(llvm::MemoryBufferRef(Buffer, "Options"), Collector.getCallback()); }; llvm::Annotations Options(R"( [[Check]]: llvm-include-order )"); llvm::ErrorOr ParsedOpt = ParseWithDiags(Options.code()); EXPECT_TRUE(!ParsedOpt); EXPECT_THAT(Collector.getDiags(), testing::ElementsAre(AllOf(DiagMessage("unknown key 'Check'"), DiagKind(llvm::SourceMgr::DK_Error), DiagPos(Options.range().Begin), DiagRange(Options.range())))); Options = llvm::Annotations(R"( UseColor: [[NotABool]] )"); ParsedOpt = ParseWithDiags(Options.code()); EXPECT_TRUE(!ParsedOpt); EXPECT_THAT(Collector.getDiags(), testing::ElementsAre(AllOf(DiagMessage("invalid boolean"), DiagKind(llvm::SourceMgr::DK_Error), DiagPos(Options.range().Begin), DiagRange(Options.range())))); Options = llvm::Annotations(R"( SystemHeaders: [[NotABool]] )"); ParsedOpt = ParseWithDiags(Options.code()); EXPECT_TRUE(!ParsedOpt); EXPECT_THAT(Collector.getDiags(), testing::ElementsAre(AllOf(DiagMessage("invalid boolean"), DiagKind(llvm::SourceMgr::DK_Error), DiagPos(Options.range().Begin), DiagRange(Options.range())))); } namespace { class TestCheck : public ClangTidyCheck { public: TestCheck(ClangTidyContext *Context) : ClangTidyCheck("test", Context) {} template auto getLocal(Args &&... Arguments) { return Options.get(std::forward(Arguments)...); } template auto getGlobal(Args &&... Arguments) { return Options.getLocalOrGlobal(std::forward(Arguments)...); } template auto getIntLocal(Args &&... Arguments) { return Options.get(std::forward(Arguments)...); } template auto getIntGlobal(Args &&... Arguments) { return Options.getLocalOrGlobal(std::forward(Arguments)...); } }; #define CHECK_VAL(Value, Expected) \ do { \ auto Item = Value; \ ASSERT_TRUE(!!Item); \ EXPECT_EQ(*Item, Expected); \ } while (false) MATCHER_P(ToolDiagMessage, M, "") { return arg.Message.Message == M; } MATCHER_P(ToolDiagLevel, L, "") { return arg.DiagLevel == L; } } // namespace } // namespace test static constexpr auto Warning = tooling::Diagnostic::Warning; static constexpr auto Error = tooling::Diagnostic::Error; static void PrintTo(const ClangTidyError &Err, ::std::ostream *OS) { *OS << (Err.DiagLevel == Error ? "error: " : "warning: ") << Err.Message.Message; } namespace test { TEST(CheckOptionsValidation, MissingOptions) { ClangTidyOptions Options; ClangTidyContext Context(std::make_unique( ClangTidyGlobalOptions(), Options)); ClangTidyDiagnosticConsumer DiagConsumer(Context); DiagnosticsEngine DE(new DiagnosticIDs(), new DiagnosticOptions, &DiagConsumer, false); Context.setDiagnosticsEngine(&DE); TestCheck TestCheck(&Context); EXPECT_FALSE(TestCheck.getLocal("Opt")); EXPECT_EQ(TestCheck.getLocal("Opt", "Unknown"), "Unknown"); // Missing options aren't errors. EXPECT_TRUE(DiagConsumer.take().empty()); } TEST(CheckOptionsValidation, ValidIntOptions) { ClangTidyOptions Options; auto &CheckOptions = Options.CheckOptions; CheckOptions["test.IntExpected"] = "1"; CheckOptions["test.IntInvalid1"] = "1WithMore"; CheckOptions["test.IntInvalid2"] = "NoInt"; CheckOptions["GlobalIntExpected"] = "1"; CheckOptions["GlobalIntInvalid"] = "NoInt"; CheckOptions["test.DefaultedIntInvalid"] = "NoInt"; CheckOptions["test.BoolITrueValue"] = "1"; CheckOptions["test.BoolIFalseValue"] = "0"; CheckOptions["test.BoolTrueValue"] = "true"; CheckOptions["test.BoolFalseValue"] = "false"; CheckOptions["test.BoolTrueShort"] = "Y"; CheckOptions["test.BoolFalseShort"] = "N"; CheckOptions["test.BoolUnparseable"] = "Nothing"; ClangTidyContext Context(std::make_unique( ClangTidyGlobalOptions(), Options)); ClangTidyDiagnosticConsumer DiagConsumer(Context); DiagnosticsEngine DE(new DiagnosticIDs(), new DiagnosticOptions, &DiagConsumer, false); Context.setDiagnosticsEngine(&DE); TestCheck TestCheck(&Context); CHECK_VAL(TestCheck.getIntLocal("IntExpected"), 1); CHECK_VAL(TestCheck.getIntGlobal("GlobalIntExpected"), 1); EXPECT_FALSE(TestCheck.getIntLocal("IntInvalid1").has_value()); EXPECT_FALSE(TestCheck.getIntLocal("IntInvalid2").has_value()); EXPECT_FALSE(TestCheck.getIntGlobal("GlobalIntInvalid").has_value()); ASSERT_EQ(TestCheck.getIntLocal("DefaultedIntInvalid", 1), 1); CHECK_VAL(TestCheck.getIntLocal("BoolITrueValue"), true); CHECK_VAL(TestCheck.getIntLocal("BoolIFalseValue"), false); CHECK_VAL(TestCheck.getIntLocal("BoolTrueValue"), true); CHECK_VAL(TestCheck.getIntLocal("BoolFalseValue"), false); CHECK_VAL(TestCheck.getIntLocal("BoolTrueShort"), true); CHECK_VAL(TestCheck.getIntLocal("BoolFalseShort"), false); EXPECT_FALSE(TestCheck.getIntLocal("BoolUnparseable")); EXPECT_THAT( DiagConsumer.take(), UnorderedElementsAre( AllOf(ToolDiagMessage( "invalid configuration value '1WithMore' for option " "'test.IntInvalid1'; expected an integer"), ToolDiagLevel(Warning)), AllOf( ToolDiagMessage("invalid configuration value 'NoInt' for option " "'test.IntInvalid2'; expected an integer"), ToolDiagLevel(Warning)), AllOf( ToolDiagMessage("invalid configuration value 'NoInt' for option " "'GlobalIntInvalid'; expected an integer"), ToolDiagLevel(Warning)), AllOf(ToolDiagMessage( "invalid configuration value 'NoInt' for option " "'test.DefaultedIntInvalid'; expected an integer"), ToolDiagLevel(Warning)), AllOf(ToolDiagMessage( "invalid configuration value 'Nothing' for option " "'test.BoolUnparseable'; expected a bool"), ToolDiagLevel(Warning)))); } TEST(ValidConfiguration, ValidEnumOptions) { ClangTidyOptions Options; auto &CheckOptions = Options.CheckOptions; CheckOptions["test.Valid"] = "Red"; CheckOptions["test.Invalid"] = "Scarlet"; CheckOptions["test.ValidWrongCase"] = "rED"; CheckOptions["test.NearMiss"] = "Oragne"; CheckOptions["GlobalValid"] = "Violet"; CheckOptions["GlobalInvalid"] = "Purple"; CheckOptions["GlobalValidWrongCase"] = "vIOLET"; CheckOptions["GlobalNearMiss"] = "Yelow"; ClangTidyContext Context(std::make_unique( ClangTidyGlobalOptions(), Options)); ClangTidyDiagnosticConsumer DiagConsumer(Context); DiagnosticsEngine DE(new DiagnosticIDs(), new DiagnosticOptions, &DiagConsumer, false); Context.setDiagnosticsEngine(&DE); TestCheck TestCheck(&Context); CHECK_VAL(TestCheck.getIntLocal("Valid"), Colours::Red); CHECK_VAL(TestCheck.getIntGlobal("GlobalValid"), Colours::Violet); CHECK_VAL( TestCheck.getIntLocal("ValidWrongCase", /*IgnoreCase*/ true), Colours::Red); CHECK_VAL(TestCheck.getIntGlobal("GlobalValidWrongCase", /*IgnoreCase*/ true), Colours::Violet); EXPECT_FALSE(TestCheck.getIntLocal("ValidWrongCase").has_value()); EXPECT_FALSE(TestCheck.getIntLocal("NearMiss").has_value()); EXPECT_FALSE(TestCheck.getIntGlobal("GlobalInvalid").has_value()); EXPECT_FALSE( TestCheck.getIntGlobal("GlobalValidWrongCase").has_value()); EXPECT_FALSE(TestCheck.getIntGlobal("GlobalNearMiss").has_value()); EXPECT_FALSE(TestCheck.getIntLocal("Invalid").has_value()); EXPECT_THAT( DiagConsumer.take(), UnorderedElementsAre( AllOf(ToolDiagMessage("invalid configuration value " "'Scarlet' for option 'test.Invalid'"), ToolDiagLevel(Warning)), AllOf(ToolDiagMessage("invalid configuration value 'rED' for option " "'test.ValidWrongCase'; did you mean 'Red'?"), ToolDiagLevel(Warning)), AllOf( ToolDiagMessage("invalid configuration value 'Oragne' for option " "'test.NearMiss'; did you mean 'Orange'?"), ToolDiagLevel(Warning)), AllOf(ToolDiagMessage("invalid configuration value " "'Purple' for option 'GlobalInvalid'"), ToolDiagLevel(Warning)), AllOf( ToolDiagMessage("invalid configuration value 'vIOLET' for option " "'GlobalValidWrongCase'; did you mean 'Violet'?"), ToolDiagLevel(Warning)), AllOf( ToolDiagMessage("invalid configuration value 'Yelow' for option " "'GlobalNearMiss'; did you mean 'Yellow'?"), ToolDiagLevel(Warning)))); } #undef CHECK_VAL } // namespace test } // namespace tidy } // namespace clang