summaryrefslogtreecommitdiffstats
path: root/unittests/clang-include-fixer/IncludeFixerTest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'unittests/clang-include-fixer/IncludeFixerTest.cpp')
-rw-r--r--unittests/clang-include-fixer/IncludeFixerTest.cpp371
1 files changed, 371 insertions, 0 deletions
diff --git a/unittests/clang-include-fixer/IncludeFixerTest.cpp b/unittests/clang-include-fixer/IncludeFixerTest.cpp
new file mode 100644
index 00000000..ab7ef797
--- /dev/null
+++ b/unittests/clang-include-fixer/IncludeFixerTest.cpp
@@ -0,0 +1,371 @@
+//===-- IncludeFixerTest.cpp - Include fixer unit tests -------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "InMemorySymbolIndex.h"
+#include "IncludeFixer.h"
+#include "SymbolIndexManager.h"
+#include "unittests/Tooling/RewriterTestContext.h"
+#include "clang/Tooling/Tooling.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace include_fixer {
+namespace {
+
+using find_all_symbols::SymbolInfo;
+using find_all_symbols::SymbolAndSignals;
+
+static bool runOnCode(tooling::ToolAction *ToolAction, StringRef Code,
+ StringRef FileName,
+ const std::vector<std::string> &ExtraArgs) {
+ llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
+ new llvm::vfs::InMemoryFileSystem);
+ llvm::IntrusiveRefCntPtr<FileManager> Files(
+ new FileManager(FileSystemOptions(), InMemoryFileSystem));
+ // FIXME: Investigate why -fms-compatibility breaks tests.
+ std::vector<std::string> Args = {"include_fixer", "-fsyntax-only",
+ "-fno-ms-compatibility", FileName};
+ Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end());
+ tooling::ToolInvocation Invocation(
+ Args, ToolAction, Files.get(),
+ std::make_shared<PCHContainerOperations>());
+
+ InMemoryFileSystem->addFile(FileName, 0,
+ llvm::MemoryBuffer::getMemBuffer(Code));
+
+ InMemoryFileSystem->addFile("foo.h", 0,
+ llvm::MemoryBuffer::getMemBuffer("\n"));
+ InMemoryFileSystem->addFile("dir/bar.h", 0,
+ llvm::MemoryBuffer::getMemBuffer("\n"));
+ InMemoryFileSystem->addFile("dir/otherdir/qux.h", 0,
+ llvm::MemoryBuffer::getMemBuffer("\n"));
+ InMemoryFileSystem->addFile("header.h", 0,
+ llvm::MemoryBuffer::getMemBuffer("bar b;"));
+ return Invocation.run();
+}
+
+static std::string runIncludeFixer(
+ StringRef Code,
+ const std::vector<std::string> &ExtraArgs = std::vector<std::string>()) {
+ std::vector<SymbolAndSignals> Symbols = {
+ {SymbolInfo("string", SymbolInfo::SymbolKind::Class, "<string>",
+ {{SymbolInfo::ContextType::Namespace, "std"}}),
+ SymbolInfo::Signals{}},
+ {SymbolInfo("sting", SymbolInfo::SymbolKind::Class, "\"sting\"",
+ {{SymbolInfo::ContextType::Namespace, "std"}}),
+ SymbolInfo::Signals{}},
+ {SymbolInfo("foo", SymbolInfo::SymbolKind::Class,
+ "\"dir/otherdir/qux.h\"",
+ {{SymbolInfo::ContextType::Namespace, "b"},
+ {SymbolInfo::ContextType::Namespace, "a"}}),
+ SymbolInfo::Signals{}},
+ {SymbolInfo("bar", SymbolInfo::SymbolKind::Class, "\"bar.h\"",
+ {{SymbolInfo::ContextType::Namespace, "b"},
+ {SymbolInfo::ContextType::Namespace, "a"}}),
+ SymbolInfo::Signals{}},
+ {SymbolInfo("bar", SymbolInfo::SymbolKind::Class, "\"bar2.h\"",
+ {{SymbolInfo::ContextType::Namespace, "c"},
+ {SymbolInfo::ContextType::Namespace, "a"}}),
+ SymbolInfo::Signals{}},
+ {SymbolInfo("Green", SymbolInfo::SymbolKind::Class, "\"color.h\"",
+ {{SymbolInfo::ContextType::EnumDecl, "Color"},
+ {SymbolInfo::ContextType::Namespace, "b"},
+ {SymbolInfo::ContextType::Namespace, "a"}}),
+ SymbolInfo::Signals{}},
+ {SymbolInfo("Vector", SymbolInfo::SymbolKind::Class, "\"Vector.h\"",
+ {{SymbolInfo::ContextType::Namespace, "__a"},
+ {SymbolInfo::ContextType::Namespace, "a"}}),
+ SymbolInfo::Signals{/*Seen=*/2, 0}},
+ {SymbolInfo("Vector", SymbolInfo::SymbolKind::Class, "\"Vector.h\"",
+ {{SymbolInfo::ContextType::Namespace, "a"}}),
+ SymbolInfo::Signals{/*Seen=*/2, 0}},
+ {SymbolInfo("StrCat", SymbolInfo::SymbolKind::Class, "\"strcat.h\"",
+ {{SymbolInfo::ContextType::Namespace, "str"}}),
+ SymbolInfo::Signals{}},
+ {SymbolInfo("str", SymbolInfo::SymbolKind::Class, "\"str.h\"", {}),
+ SymbolInfo::Signals{}},
+ {SymbolInfo("foo2", SymbolInfo::SymbolKind::Class, "\"foo2.h\"", {}),
+ SymbolInfo::Signals{}},
+ };
+ auto SymbolIndexMgr = llvm::make_unique<SymbolIndexManager>();
+ SymbolIndexMgr->addSymbolIndex(
+ [=]() { return llvm::make_unique<InMemorySymbolIndex>(Symbols); });
+
+ std::vector<IncludeFixerContext> FixerContexts;
+ IncludeFixerActionFactory Factory(*SymbolIndexMgr, FixerContexts, "llvm");
+ std::string FakeFileName = "input.cc";
+ runOnCode(&Factory, Code, FakeFileName, ExtraArgs);
+ assert(FixerContexts.size() == 1);
+ if (FixerContexts.front().getHeaderInfos().empty())
+ return Code;
+ auto Replaces = createIncludeFixerReplacements(Code, FixerContexts.front());
+ EXPECT_TRUE(static_cast<bool>(Replaces))
+ << llvm::toString(Replaces.takeError()) << "\n";
+ if (!Replaces)
+ return "";
+ RewriterTestContext Context;
+ FileID ID = Context.createInMemoryFile(FakeFileName, Code);
+ tooling::applyAllReplacements(*Replaces, Context.Rewrite);
+ return Context.getRewrittenText(ID);
+}
+
+TEST(IncludeFixer, Typo) {
+ EXPECT_EQ("#include <string>\nstd::string foo;\n",
+ runIncludeFixer("std::string foo;\n"));
+
+ EXPECT_EQ("// comment\n#include \"foo.h\"\n#include <string>\n"
+ "std::string foo;\n#include \"dir/bar.h\"\n",
+ runIncludeFixer("// comment\n#include \"foo.h\"\nstd::string foo;\n"
+ "#include \"dir/bar.h\"\n"));
+
+ EXPECT_EQ("#include \"foo.h\"\n#include <string>\nstd::string foo;\n",
+ runIncludeFixer("#include \"foo.h\"\nstd::string foo;\n"));
+
+ EXPECT_EQ(
+ "#include \"foo.h\"\n#include <string>\nstd::string::size_type foo;\n",
+ runIncludeFixer("#include \"foo.h\"\nstd::string::size_type foo;\n"));
+
+ EXPECT_EQ("#include <string>\nstd::string foo;\n",
+ runIncludeFixer("string foo;\n"));
+
+ // Should not match std::string.
+ EXPECT_EQ("::string foo;\n", runIncludeFixer("::string foo;\n"));
+}
+
+TEST(IncludeFixer, IncompleteType) {
+ EXPECT_EQ(
+ "#include \"foo.h\"\n#include <string>\n"
+ "namespace std {\nclass string;\n}\nstd::string foo;\n",
+ runIncludeFixer("#include \"foo.h\"\n"
+ "namespace std {\nclass string;\n}\nstring foo;\n"));
+
+ EXPECT_EQ("#include <string>\n"
+ "class string;\ntypedef string foo;\nfoo f;\n",
+ runIncludeFixer("class string;\ntypedef string foo;\nfoo f;\n"));
+}
+
+TEST(IncludeFixer, MinimizeInclude) {
+ std::vector<std::string> IncludePath = {"-Idir/"};
+ EXPECT_EQ("#include \"otherdir/qux.h\"\na::b::foo bar;\n",
+ runIncludeFixer("a::b::foo bar;\n", IncludePath));
+
+ IncludePath = {"-isystemdir"};
+ EXPECT_EQ("#include <otherdir/qux.h>\na::b::foo bar;\n",
+ runIncludeFixer("a::b::foo bar;\n", IncludePath));
+
+ IncludePath = {"-iquotedir"};
+ EXPECT_EQ("#include \"otherdir/qux.h\"\na::b::foo bar;\n",
+ runIncludeFixer("a::b::foo bar;\n", IncludePath));
+
+ IncludePath = {"-Idir", "-Idir/otherdir"};
+ EXPECT_EQ("#include \"qux.h\"\na::b::foo bar;\n",
+ runIncludeFixer("a::b::foo bar;\n", IncludePath));
+}
+
+TEST(IncludeFixer, NestedName) {
+ EXPECT_EQ("#include \"dir/otherdir/qux.h\"\n"
+ "int x = a::b::foo(0);\n",
+ runIncludeFixer("int x = a::b::foo(0);\n"));
+
+ // FIXME: Handle simple macros.
+ EXPECT_EQ("#define FOO a::b::foo\nint x = FOO;\n",
+ runIncludeFixer("#define FOO a::b::foo\nint x = FOO;\n"));
+ EXPECT_EQ("#define FOO(x) a::##x\nint x = FOO(b::foo);\n",
+ runIncludeFixer("#define FOO(x) a::##x\nint x = FOO(b::foo);\n"));
+
+ // The empty namespace is cleaned up by clang-format after clang-include-fixer
+ // finishes.
+ EXPECT_EQ("#include \"dir/otherdir/qux.h\"\n"
+ "\nint a = a::b::foo(0);\n",
+ runIncludeFixer("namespace a {}\nint a = a::b::foo(0);\n"));
+}
+
+TEST(IncludeFixer, MultipleMissingSymbols) {
+ EXPECT_EQ("#include <string>\nstd::string bar;\nstd::sting foo;\n",
+ runIncludeFixer("std::string bar;\nstd::sting foo;\n"));
+}
+
+TEST(IncludeFixer, ScopedNamespaceSymbols) {
+ EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}",
+ runIncludeFixer("namespace a {\nb::bar b;\n}"));
+ EXPECT_EQ("#include \"bar.h\"\nnamespace A {\na::b::bar b;\n}",
+ runIncludeFixer("namespace A {\na::b::bar b;\n}"));
+ EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nvoid func() { b::bar b; }\n} "
+ "// namespace a",
+ runIncludeFixer("namespace a {\nvoid func() { b::bar b; }\n}"));
+ EXPECT_EQ("namespace A { c::b::bar b; }\n",
+ runIncludeFixer("namespace A { c::b::bar b; }\n"));
+ // FIXME: The header should not be added here. Remove this after we support
+ // full match.
+ EXPECT_EQ("#include \"bar.h\"\nnamespace A {\na::b::bar b;\n}",
+ runIncludeFixer("namespace A {\nb::bar b;\n}"));
+
+ // Finds candidates for "str::StrCat".
+ EXPECT_EQ("#include \"strcat.h\"\nnamespace foo2 {\nstr::StrCat b;\n}",
+ runIncludeFixer("namespace foo2 {\nstr::StrCat b;\n}"));
+ // str::StrCat2 doesn't exist.
+ // In these two cases, StrCat2 is a nested class of class str.
+ EXPECT_EQ("#include \"str.h\"\nnamespace foo2 {\nstr::StrCat2 b;\n}",
+ runIncludeFixer("namespace foo2 {\nstr::StrCat2 b;\n}"));
+ EXPECT_EQ("#include \"str.h\"\nnamespace ns {\nstr::StrCat2 b;\n}",
+ runIncludeFixer("namespace ns {\nstr::StrCat2 b;\n}"));
+}
+
+TEST(IncludeFixer, EnumConstantSymbols) {
+ EXPECT_EQ("#include \"color.h\"\nint test = a::b::Green;\n",
+ runIncludeFixer("int test = a::b::Green;\n"));
+}
+
+TEST(IncludeFixer, IgnoreSymbolFromHeader) {
+ std::string Code = "#include \"header.h\"";
+ EXPECT_EQ(Code, runIncludeFixer(Code));
+}
+
+// FIXME: add test cases for inserting and sorting multiple headers when
+// clang-include-fixer supports multiple headers insertion.
+TEST(IncludeFixer, InsertAndSortSingleHeader) {
+ // Insert one header.
+ std::string Code = "#include \"a.h\"\n"
+ "#include \"foo.h\"\n"
+ "\n"
+ "namespace a {\nb::bar b;\n}\n";
+ std::string Expected = "#include \"a.h\"\n"
+ "#include \"bar.h\"\n"
+ "#include \"foo.h\"\n"
+ "\n"
+ "namespace a {\nb::bar b;\n}\n";
+ EXPECT_EQ(Expected, runIncludeFixer(Code));
+}
+
+TEST(IncludeFixer, DoNotDeleteMatchedSymbol) {
+ EXPECT_EQ("#include \"Vector.h\"\na::Vector v;",
+ runIncludeFixer("a::Vector v;"));
+}
+
+TEST(IncludeFixer, FixNamespaceQualifiers) {
+ EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n",
+ runIncludeFixer("b::bar b;\n"));
+ EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n",
+ runIncludeFixer("a::b::bar b;\n"));
+ EXPECT_EQ("#include \"bar.h\"\na::b::bar b;\n",
+ runIncludeFixer("bar b;\n"));
+ EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}\n",
+ runIncludeFixer("namespace a {\nb::bar b;\n}\n"));
+ EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar b;\n}\n",
+ runIncludeFixer("namespace a {\nbar b;\n}\n"));
+ EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nnamespace b{\nbar b;\n}\n} "
+ "// namespace a\n",
+ runIncludeFixer("namespace a {\nnamespace b{\nbar b;\n}\n}\n"));
+ EXPECT_EQ("c::b::bar b;\n",
+ runIncludeFixer("c::b::bar b;\n"));
+ EXPECT_EQ("#include \"bar.h\"\nnamespace d {\na::b::bar b;\n}\n",
+ runIncludeFixer("namespace d {\nbar b;\n}\n"));
+ EXPECT_EQ("#include \"bar2.h\"\nnamespace c {\na::c::bar b;\n}\n",
+ runIncludeFixer("namespace c {\nbar b;\n}\n"));
+
+ // Test common qualifers reduction.
+ EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nnamespace d {\nb::bar b;\n}\n} "
+ "// namespace a\n",
+ runIncludeFixer("namespace a {\nnamespace d {\nbar b;\n}\n}\n"));
+ EXPECT_EQ("#include \"bar.h\"\nnamespace d {\nnamespace a {\na::b::bar "
+ "b;\n}\n} // namespace d\n",
+ runIncludeFixer("namespace d {\nnamespace a {\nbar b;\n}\n}\n"));
+
+ // Test nested classes.
+ EXPECT_EQ("#include \"bar.h\"\nnamespace d {\na::b::bar::t b;\n}\n",
+ runIncludeFixer("namespace d {\nbar::t b;\n}\n"));
+ EXPECT_EQ("#include \"bar.h\"\nnamespace c {\na::b::bar::t b;\n}\n",
+ runIncludeFixer("namespace c {\nbar::t b;\n}\n"));
+ EXPECT_EQ("#include \"bar.h\"\nnamespace a {\nb::bar::t b;\n}\n",
+ runIncludeFixer("namespace a {\nbar::t b;\n}\n"));
+
+ EXPECT_EQ("#include \"color.h\"\nint test = a::b::Green;\n",
+ runIncludeFixer("int test = Green;\n"));
+ EXPECT_EQ("#include \"color.h\"\nnamespace d {\nint test = a::b::Green;\n}\n",
+ runIncludeFixer("namespace d {\nint test = Green;\n}\n"));
+ EXPECT_EQ("#include \"color.h\"\nnamespace a {\nint test = b::Green;\n}\n",
+ runIncludeFixer("namespace a {\nint test = Green;\n}\n"));
+
+ // Test global scope operator.
+ EXPECT_EQ("#include \"bar.h\"\n::a::b::bar b;\n",
+ runIncludeFixer("::a::b::bar b;\n"));
+ EXPECT_EQ("#include \"bar.h\"\nnamespace a {\n::a::b::bar b;\n}\n",
+ runIncludeFixer("namespace a {\n::a::b::bar b;\n}\n"));
+}
+
+TEST(IncludeFixer, FixNamespaceQualifiersForAllInstances) {
+ const char TestCode[] = R"(
+namespace a {
+bar b;
+int func1() {
+ bar a;
+ bar *p = new bar();
+ return 0;
+}
+} // namespace a
+
+namespace a {
+bar func2() {
+ bar f;
+ return f;
+}
+} // namespace a
+
+// Non-fixed cases:
+void f() {
+ bar b;
+}
+
+namespace a {
+namespace c {
+ bar b;
+} // namespace c
+} // namespace a
+)";
+
+ const char ExpectedCode[] = R"(
+#include "bar.h"
+namespace a {
+b::bar b;
+int func1() {
+ b::bar a;
+ b::bar *p = new b::bar();
+ return 0;
+}
+} // namespace a
+
+namespace a {
+b::bar func2() {
+ b::bar f;
+ return f;
+}
+} // namespace a
+
+// Non-fixed cases:
+void f() {
+ bar b;
+}
+
+namespace a {
+namespace c {
+ bar b;
+} // namespace c
+} // namespace a
+)";
+
+ EXPECT_EQ(ExpectedCode, runIncludeFixer(TestCode));
+}
+
+TEST(IncludeFixer, DontAddQualifiersForMissingCompleteType) {
+ EXPECT_EQ("#include \"bar.h\"\nclass bar;\nvoid f() {\nbar* b;\nb->f();\n}",
+ runIncludeFixer("class bar;\nvoid f() {\nbar* b;\nb->f();\n}"));
+}
+
+} // namespace
+} // namespace include_fixer
+} // namespace clang