381 lines · cpp
1//===--- IncludeCleanerTest.cpp - clang-tidy -----------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "ClangTidyDiagnosticConsumer.h"10#include "ClangTidyOptions.h"11#include "ClangTidyTest.h"12#include "misc/IncludeCleanerCheck.h"13#include "llvm/ADT/SmallString.h"14#include "llvm/ADT/StringRef.h"15#include "llvm/Support/FormatVariadic.h"16#include "llvm/Support/Path.h"17#include "llvm/Support/Regex.h"18#include "llvm/Testing/Annotations/Annotations.h"19#include "gmock/gmock.h"20#include "gtest/gtest.h"21#include <initializer_list>22 23#include <optional>24#include <vector>25 26using namespace clang::tidy::misc;27 28namespace clang {29namespace tidy {30namespace test {31namespace {32 33std::string34appendPathFileSystemIndependent(std::initializer_list<std::string> Segments) {35 llvm::SmallString<32> Result;36 for (const auto &Segment : Segments)37 llvm::sys::path::append(Result, llvm::sys::path::Style::native, Segment);38 return std::string(Result.str());39}40 41TEST(IncludeCleanerCheckTest, BasicUnusedIncludes) {42 const char *PreCode = R"(43#include "bar.h"44#include <vector>45#include "bar.h"46)";47 const char *PostCode = "\n";48 49 std::vector<ClangTidyError> Errors;50 EXPECT_EQ(PostCode,51 runCheckOnCode<IncludeCleanerCheck>(52 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(),53 {{"bar.h", "#pragma once"}, {"vector", "#pragma once"}}));54}55 56TEST(IncludeCleanerCheckTest, SuppressUnusedIncludes) {57 const char *PreCode = R"(58#include "bar.h"59#include "foo/qux.h"60#include "baz/qux/qux.h"61#include <vector>62#include <list>63)";64 65 const char *PostCode = R"(66#include "bar.h"67#include "foo/qux.h"68#include <vector>69#include <list>70)";71 72 std::vector<ClangTidyError> Errors;73 ClangTidyOptions Opts;74 Opts.CheckOptions["test-check-0.IgnoreHeaders"] = llvm::StringRef{75 llvm::formatv("bar.h;{0};{1};vector;<list>;",76 llvm::Regex::escape(77 appendPathFileSystemIndependent({"foo", "qux.h"})),78 llvm::Regex::escape(79 appendPathFileSystemIndependent({"baz", "qux"})))};80 EXPECT_EQ(81 PostCode,82 runCheckOnCode<IncludeCleanerCheck>(83 PreCode, &Errors, "file.cpp", {}, Opts,84 {{"bar.h", "#pragma once"},85 {"vector", "#pragma once"},86 {"list", "#pragma once"},87 {appendPathFileSystemIndependent({"foo", "qux.h"}), "#pragma once"},88 {appendPathFileSystemIndependent({"baz", "qux", "qux.h"}),89 "#pragma once"}}));90}91 92TEST(IncludeCleanerCheckTest, BasicMissingIncludes) {93 const char *PreCode = R"(94#include "bar.h"95 96int BarResult = bar();97int BazResult = baz();98)";99 const char *PostCode = R"(100#include "bar.h"101#include "baz.h"102 103int BarResult = bar();104int BazResult = baz();105)";106 107 std::vector<ClangTidyError> Errors;108 EXPECT_EQ(PostCode, runCheckOnCode<IncludeCleanerCheck>(109 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(),110 {{"bar.h", R"(#pragma once111 #include "baz.h"112 int bar();113 )"},114 {"baz.h", R"(#pragma once115 int baz();116 )"}}));117}118 119TEST(IncludeCleanerCheckTest, DedupsMissingIncludes) {120 llvm::Annotations Code(R"(121#include "baz.h" // IWYU pragma: keep122 123int BarResult1 = $diag1^bar();124int BarResult2 = $diag2^bar();)");125 126 {127 std::vector<ClangTidyError> Errors;128 runCheckOnCode<IncludeCleanerCheck>(Code.code(), &Errors, "file.cpp", {},129 ClangTidyOptions(),130 {{"baz.h", R"(#pragma once131 #include "bar.h"132 )"},133 {"bar.h", R"(#pragma once134 int bar();135 )"}});136 ASSERT_THAT(Errors.size(), testing::Eq(1U));137 EXPECT_EQ(Errors.front().Message.Message,138 "no header providing \"bar\" is directly included");139 EXPECT_EQ(Errors.front().Message.FileOffset, Code.point("diag1"));140 }141 {142 std::vector<ClangTidyError> Errors;143 ClangTidyOptions Opts;144 Opts.CheckOptions["test-check-0.DeduplicateFindings"] = "false";145 runCheckOnCode<IncludeCleanerCheck>(Code.code(), &Errors, "file.cpp", {},146 Opts,147 {{"baz.h", R"(#pragma once148 #include "bar.h"149 )"},150 {"bar.h", R"(#pragma once151 int bar();152 )"}});153 ASSERT_THAT(Errors.size(), testing::Eq(2U));154 EXPECT_EQ(Errors.front().Message.Message,155 "no header providing \"bar\" is directly included");156 EXPECT_EQ(Errors.front().Message.FileOffset, Code.point("diag1"));157 EXPECT_EQ(Errors.back().Message.Message,158 "no header providing \"bar\" is directly included");159 EXPECT_EQ(Errors.back().Message.FileOffset, Code.point("diag2"));160 }161}162 163TEST(IncludeCleanerCheckTest, SuppressMissingIncludes) {164 const char *PreCode = R"(165#include "bar.h"166 167int BarResult = bar();168int BazResult = baz();169int QuxResult = qux();170int PrivResult = test();171std::vector x;172)";173 174 ClangTidyOptions Opts;175 Opts.CheckOptions["test-check-0.IgnoreHeaders"] = llvm::StringRef{176 "public.h;<vector>;baz.h;" +177 llvm::Regex::escape(appendPathFileSystemIndependent({"foo", "qux.h"}))};178 std::vector<ClangTidyError> Errors;179 EXPECT_EQ(PreCode, runCheckOnCode<IncludeCleanerCheck>(180 PreCode, &Errors, "file.cpp", {}, Opts,181 {{"bar.h", R"(#pragma once182 #include "baz.h"183 #include "foo/qux.h"184 #include "private.h"185 int bar();186 namespace std { struct vector {}; }187 )"},188 {"baz.h", R"(#pragma once189 int baz();190 )"},191 {"private.h", R"(#pragma once192 // IWYU pragma: private, include "public.h"193 int test();194 )"},195 {appendPathFileSystemIndependent({"foo", "qux.h"}),196 R"(#pragma once197 int qux();198 )"}}));199}200 201TEST(IncludeCleanerCheckTest, MultipleTimeMissingInclude) {202 const char *PreCode = R"(203#include "bar.h"204 205int BarResult = bar();206int BazResult_0 = baz_0();207int BazResult_1 = baz_1();208)";209 const char *PostCode = R"(210#include "bar.h"211#include "baz.h"212 213int BarResult = bar();214int BazResult_0 = baz_0();215int BazResult_1 = baz_1();216)";217 218 std::vector<ClangTidyError> Errors;219 EXPECT_EQ(PostCode, runCheckOnCode<IncludeCleanerCheck>(220 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(),221 {{"bar.h", R"(#pragma once222 #include "baz.h"223 int bar();224 )"},225 {"baz.h", R"(#pragma once226 int baz_0();227 int baz_1();228 )"}}));229}230 231TEST(IncludeCleanerCheckTest, SystemMissingIncludes) {232 const char *PreCode = R"(233#include <vector>234 235std::string HelloString;236std::vector Vec;237)";238 const char *PostCode = R"(239#include <string>240#include <vector>241 242std::string HelloString;243std::vector Vec;244)";245 246 std::vector<ClangTidyError> Errors;247 EXPECT_EQ(PostCode, runCheckOnCode<IncludeCleanerCheck>(248 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(),249 {{"string", R"(#pragma once250 namespace std { class string {}; }251 )"},252 {"vector", R"(#pragma once253 #include <string>254 namespace std { class vector {}; }255 )"}}));256}257 258TEST(IncludeCleanerCheckTest, PragmaMissingIncludes) {259 const char *PreCode = R"(260#include "bar.h"261 262int BarResult = bar();263int FooBarResult = foobar();264)";265 const char *PostCode = R"(266#include "bar.h"267#include "public.h"268 269int BarResult = bar();270int FooBarResult = foobar();271)";272 273 std::vector<ClangTidyError> Errors;274 EXPECT_EQ(PostCode, runCheckOnCode<IncludeCleanerCheck>(275 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(),276 {{"bar.h", R"(#pragma once277 #include "private.h"278 int bar();279 )"},280 {"private.h", R"(#pragma once281 // IWYU pragma: private, include "public.h"282 int foobar();283 )"}}));284}285 286TEST(IncludeCleanerCheckTest, DeclFromMacroExpansion) {287 const char *PreCode = R"(288#include "foo.h"289 290DECLARE(myfunc) {291 int a;292}293)";294 295 std::vector<ClangTidyError> Errors;296 EXPECT_EQ(PreCode, runCheckOnCode<IncludeCleanerCheck>(297 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(),298 {{"foo.h",299 R"(#pragma once300 #define DECLARE(X) void X()301 )"}}));302 303 PreCode = R"(304#include "foo.h"305 306DECLARE {307 int a;308}309)";310 311 EXPECT_EQ(PreCode, runCheckOnCode<IncludeCleanerCheck>(312 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(),313 {{"foo.h",314 R"(#pragma once315 #define DECLARE void myfunc()316 )"}}));317}318 319TEST(IncludeCleanerCheckTest, UnusedIncludes) {320 const char *PreCode = R"(321#include "bar.h")";322 323 {324 std::vector<ClangTidyError> Errors;325 runCheckOnCode<IncludeCleanerCheck>(PreCode, &Errors, "file.cpp", {},326 ClangTidyOptions(),327 {{"bar.h", "#pragma once"}});328 ASSERT_THAT(Errors.size(), testing::Eq(1U));329 EXPECT_EQ(Errors.front().Message.Message,330 "included header bar.h is not used directly");331 }332 {333 std::vector<ClangTidyError> Errors;334 ClangTidyOptions Opts;335 Opts.CheckOptions["test-check-0.UnusedIncludes"] = "false";336 runCheckOnCode<IncludeCleanerCheck>(PreCode, &Errors, "file.cpp", {}, Opts,337 {{"bar.h", "#pragma once"}});338 ASSERT_THAT(Errors.size(), testing::Eq(0U));339 }340}341 342TEST(IncludeCleanerCheckTest, MissingIncludes) {343 const char *PreCode = R"(344#include "baz.h" // IWYU pragma: keep345 346int BarResult1 = bar();)";347 348 {349 std::vector<ClangTidyError> Errors;350 runCheckOnCode<IncludeCleanerCheck>(PreCode, &Errors, "file.cpp", {},351 ClangTidyOptions(),352 {{"baz.h", R"(#pragma once353 #include "bar.h"354 )"},355 {"bar.h", R"(#pragma once356 int bar();357 )"}});358 ASSERT_THAT(Errors.size(), testing::Eq(1U));359 EXPECT_EQ(Errors.front().Message.Message,360 "no header providing \"bar\" is directly included");361 }362 {363 std::vector<ClangTidyError> Errors;364 ClangTidyOptions Opts;365 Opts.CheckOptions["test-check-0.MissingIncludes"] = "false";366 runCheckOnCode<IncludeCleanerCheck>(PreCode, &Errors, "file.cpp", {}, Opts,367 {{"baz.h", R"(#pragma once368 #include "bar.h"369 )"},370 {"bar.h", R"(#pragma once371 int bar();372 )"}});373 ASSERT_THAT(Errors.size(), testing::Eq(0U));374 }375}376 377} // namespace378} // namespace test379} // namespace tidy380} // namespace clang381