//===-- ClangMoveTests.cpp - clang-move 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 "Move.h" #include "unittests/Tooling/RewriterTestContext.h" #include "clang/Format/Format.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/Tooling/Refactoring.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/StringRef.h" #include "gmock/gmock-matchers.h" #include "gtest/gtest.h" #include #include namespace clang { namespace move { namespace { const char TestHeader[] = "namespace a {\n" "class C1; // test\n" "template class C2;\n" "namespace b {\n" "// This is a Foo class\n" "// which is used in\n" "// test.\n" "class Foo {\n" "public:\n" " void f();\n" "\n" "private:\n" " C1 *c1;\n" " static int b;\n" "}; // abc\n" "\n" "class Foo2 {\n" "public:\n" " int f();\n" "};\n" "} // namespace b\n" "} // namespace a\n"; const char TestCC[] = "#include \"foo.h\"\n" "namespace a {\n" "namespace b {\n" "namespace {\n" "// comment1.\n" "void f1() {}\n" "/// comment2.\n" "int kConstInt1 = 0;\n" "} // namespace\n" "\n" "/* comment 3*/\n" "static int kConstInt2 = 1;\n" "\n" "/** comment4\n" " */\n" "static int help() {\n" " int a = 0;\n" " return a;\n" "}\n" "\n" "// comment5\n" "// comment5\n" "void Foo::f() {\n" " f1();\n" " kConstInt1;\n" " kConstInt2;\n" " help();\n" "}\n" "\n" "/////////////\n" "// comment //\n" "/////////////\n" "int Foo::b = 2;\n" "int Foo2::f() {\n" " kConstInt1;\n" " kConstInt2;\n" " help();\n" " f1();\n" " return 1;\n" "}\n" "} // namespace b\n" "} // namespace a\n"; const char ExpectedTestHeader[] = "namespace a {\n" "class C1; // test\n" "template class C2;\n" "namespace b {\n" "\n" "class Foo2 {\n" "public:\n" " int f();\n" "};\n" "} // namespace b\n" "} // namespace a\n"; const char ExpectedTestCC[] = "#include \"foo.h\"\n" "namespace a {\n" "namespace b {\n" "namespace {\n" "// comment1.\n" "void f1() {}\n" "/// comment2.\n" "int kConstInt1 = 0;\n" "} // namespace\n" "\n" "/* comment 3*/\n" "static int kConstInt2 = 1;\n" "\n" "/** comment4\n" " */\n" "static int help() {\n" " int a = 0;\n" " return a;\n" "}\n" "\n" "int Foo2::f() {\n" " kConstInt1;\n" " kConstInt2;\n" " help();\n" " f1();\n" " return 1;\n" "}\n" "} // namespace b\n" "} // namespace a\n"; const char ExpectedNewHeader[] = "#ifndef NEW_FOO_H\n" "#define NEW_FOO_H\n" "\n" "namespace a {\n" "class C1; // test\n" "\n" "template class C2;\n" "namespace b {\n" "// This is a Foo class\n" "// which is used in\n" "// test.\n" "class Foo {\n" "public:\n" " void f();\n" "\n" "private:\n" " C1 *c1;\n" " static int b;\n" "}; // abc\n" "} // namespace b\n" "} // namespace a\n" "\n" "#endif // NEW_FOO_H\n"; const char ExpectedNewCC[] = "namespace a {\n" "namespace b {\n" "namespace {\n" "// comment1.\n" "void f1() {}\n" "\n" "/// comment2.\n" "int kConstInt1 = 0;\n" "} // namespace\n" "\n" "/* comment 3*/\n" "static int kConstInt2 = 1;\n" "\n" "/** comment4\n" " */\n" "static int help() {\n" " int a = 0;\n" " return a;\n" "}\n" "\n" "// comment5\n" "// comment5\n" "void Foo::f() {\n" " f1();\n" " kConstInt1;\n" " kConstInt2;\n" " help();\n" "}\n" "\n" "/////////////\n" "// comment //\n" "/////////////\n" "int Foo::b = 2;\n" "} // namespace b\n" "} // namespace a\n"; #ifdef _WIN32 const char WorkingDir[] = "C:\\test"; #else const char WorkingDir[] = "/test"; #endif const char TestHeaderName[] = "foo.h"; const char TestCCName[] = "foo.cc"; std::map runClangMoveOnCode(const move::MoveDefinitionSpec &Spec, const char *const Header = TestHeader, const char *const CC = TestCC, DeclarationReporter *const Reporter = nullptr) { clang::RewriterTestContext Context; Context.InMemoryFileSystem->setCurrentWorkingDirectory(WorkingDir); std::map FileToFileID; auto CreateFiles = [&Context, &FileToFileID](llvm::StringRef Name, llvm::StringRef Code) { if (!Name.empty()) { FileToFileID[Name] = Context.createInMemoryFile(Name, Code); } }; CreateFiles(Spec.NewCC, ""); CreateFiles(Spec.NewHeader, ""); CreateFiles(TestHeaderName, Header); CreateFiles(TestCCName, CC); std::map FileToReplacements; ClangMoveContext MoveContext = {Spec, FileToReplacements, WorkingDir, "LLVM", Reporter != nullptr}; auto Factory = std::make_unique( &MoveContext, Reporter); // std::string IncludeArg = Twine("-I" + WorkingDir; tooling::runToolOnCodeWithArgs( Factory->create(), CC, Context.InMemoryFileSystem, {"-std=c++11", "-fparse-all-comments", "-I."}, TestCCName, "clang-move", std::make_shared()); formatAndApplyAllReplacements(FileToReplacements, Context.Rewrite, "llvm"); // The Key is file name, value is the new code after moving the class. std::map Results; for (const auto &It : FileToReplacements) { // The path may come out as "./foo.h", normalize to "foo.h". SmallString<32> FilePath (It.first); llvm::sys::path::remove_dots(FilePath); Results[FilePath.str().str()] = Context.getRewrittenText(FileToFileID[FilePath]); } return Results; } TEST(ClangMove, MoveHeaderAndCC) { move::MoveDefinitionSpec Spec; Spec.Names = {std::string("a::b::Foo")}; Spec.OldHeader = "foo.h"; Spec.OldCC = "foo.cc"; Spec.NewHeader = "new_foo.h"; Spec.NewCC = "new_foo.cc"; std::string ExpectedHeader = "#include \"" + Spec.NewHeader + "\"\n\n"; auto Results = runClangMoveOnCode(Spec); EXPECT_EQ(ExpectedTestHeader, Results[Spec.OldHeader]); EXPECT_EQ(ExpectedTestCC, Results[Spec.OldCC]); EXPECT_EQ(ExpectedNewHeader, Results[Spec.NewHeader]); EXPECT_EQ(ExpectedHeader + ExpectedNewCC, Results[Spec.NewCC]); } TEST(ClangMove, MoveHeaderOnly) { move::MoveDefinitionSpec Spec; Spec.Names = {std::string("a::b::Foo")}; Spec.OldHeader = "foo.h"; Spec.NewHeader = "new_foo.h"; auto Results = runClangMoveOnCode(Spec); EXPECT_EQ(2u, Results.size()); EXPECT_EQ(ExpectedTestHeader, Results[Spec.OldHeader]); EXPECT_EQ(ExpectedNewHeader, Results[Spec.NewHeader]); } TEST(ClangMove, MoveCCOnly) { move::MoveDefinitionSpec Spec; Spec.Names = {std::string("a::b::Foo")}; Spec.OldCC = "foo.cc"; Spec.NewCC = "new_foo.cc"; std::string ExpectedHeader = "#include \"foo.h\"\n\n"; auto Results = runClangMoveOnCode(Spec); EXPECT_EQ(2u, Results.size()); EXPECT_EQ(ExpectedTestCC, Results[Spec.OldCC]); EXPECT_EQ(ExpectedHeader + ExpectedNewCC, Results[Spec.NewCC]); } TEST(ClangMove, MoveNonExistClass) { move::MoveDefinitionSpec Spec; Spec.Names = {std::string("NonExistFoo")}; Spec.OldHeader = "foo.h"; Spec.OldCC = "foo.cc"; Spec.NewHeader = "new_foo.h"; Spec.NewCC = "new_foo.cc"; auto Results = runClangMoveOnCode(Spec); EXPECT_EQ(0u, Results.size()); } TEST(ClangMove, HeaderIncludeSelf) { move::MoveDefinitionSpec Spec; Spec.Names = {std::string("Foo")}; Spec.OldHeader = "foo.h"; Spec.OldCC = "foo.cc"; Spec.NewHeader = "new_foo.h"; Spec.NewCC = "new_foo.cc"; const char TestHeader[] = "#ifndef FOO_H\n" "#define FOO_H\n" "#include \"foo.h\"\n" "class Foo {};\n" "#endif\n"; const char TestCode[] = "#include \"foo.h\""; const char ExpectedNewHeader[] = "#ifndef FOO_H\n" "#define FOO_H\n" "#include \"new_foo.h\"\n" "class Foo {};\n" "#endif\n"; const char ExpectedNewCC[] = "#include \"new_foo.h\""; auto Results = runClangMoveOnCode(Spec, TestHeader, TestCode); EXPECT_EQ("", Results[Spec.OldHeader]); EXPECT_EQ(ExpectedNewHeader, Results[Spec.NewHeader]); EXPECT_EQ(ExpectedNewCC, Results[Spec.NewCC]); } TEST(ClangMove, MoveAll) { std::vector TestHeaders = { "class A {\npublic:\n int f();\n};", // forward declaration. "class B;\nclass A {\npublic:\n int f();\n};", // template forward declaration. "template class B;\nclass A {\npublic:\n int f();\n};", "namespace a {}\nclass A {\npublic:\n int f();\n};", "namespace a {}\nusing namespace a;\nclass A {\npublic:\n int f();\n};", }; const char Code[] = "#include \"foo.h\"\nint A::f() { return 0; }"; move::MoveDefinitionSpec Spec; Spec.Names.push_back("A"); Spec.OldHeader = "foo.h"; Spec.OldCC = "foo.cc"; Spec.NewHeader = "new_foo.h"; Spec.NewCC = "new_foo.cc"; for (const auto& Header : TestHeaders) { auto Results = runClangMoveOnCode(Spec, Header.c_str(), Code); EXPECT_EQ(Header, Results[Spec.NewHeader]); EXPECT_EQ("", Results[Spec.OldHeader]); EXPECT_EQ("", Results[Spec.OldCC]); } } TEST(ClangMove, MoveAllMultipleClasses) { move::MoveDefinitionSpec Spec; std::vector TestHeaders = { "class C;\nclass A {\npublic:\n int f();\n};\nclass B {};", "class C;\nclass B;\nclass A {\npublic:\n int f();\n};\nclass B {};", }; const char Code[] = "#include \"foo.h\"\nint A::f() { return 0; }"; Spec.Names = {std::string("A"), std::string("B")}; Spec.OldHeader = "foo.h"; Spec.OldCC = "foo.cc"; Spec.NewHeader = "new_foo.h"; Spec.NewCC = "new_foo.cc"; for (const auto& Header : TestHeaders) { auto Results = runClangMoveOnCode(Spec, Header.c_str(), Code); EXPECT_EQ(Header, Results[Spec.NewHeader]); EXPECT_EQ("", Results[Spec.OldHeader]); EXPECT_EQ("", Results[Spec.OldCC]); } } TEST(ClangMove, DontMoveAll) { const char ExpectedHeader[] = "#ifndef NEW_FOO_H\n" "#define NEW_FOO_H\n" "\n" "class A {\npublic:\n int f();\n};\n" "\n" "#endif // NEW_FOO_H\n"; const char Code[] = "#include \"foo.h\"\nint A::f() { return 0; }"; std::vector TestHeaders = { "class B {};\nclass A {\npublic:\n int f();\n};\n", "void f() {};\nclass A {\npublic:\n int f();\n};\n", }; move::MoveDefinitionSpec Spec; Spec.Names.push_back("A"); Spec.OldHeader = "foo.h"; Spec.OldCC = "foo.cc"; Spec.NewHeader = "new_foo.h"; Spec.NewCC = "new_foo.cc"; for (const auto& Header : TestHeaders) { auto Results = runClangMoveOnCode(Spec, Header.c_str(), Code); EXPECT_EQ(ExpectedHeader, Results[Spec.NewHeader]); // The expected old header should not contain class A definition. std::string ExpectedOldHeader = Header.substr(0, Header.size() - 32); EXPECT_EQ(ExpectedOldHeader, Results[Spec.OldHeader]); } } TEST(ClangMove, IgnoreMacroSymbolsAndMoveAll) { const char TestCode[] = "#include \"foo.h\""; std::vector TestHeaders = { "#define DEFINE_Foo int Foo = 1;\nDEFINE_Foo;\nclass Bar {};\n", "#define DEFINE(x) int var_##x = 1;\nDEFINE(foo);\nclass Bar {};\n", }; move::MoveDefinitionSpec Spec; Spec.Names.push_back("Bar"); Spec.OldHeader = "foo.h"; Spec.OldCC = "foo.cc"; Spec.NewHeader = "new_foo.h"; Spec.NewCC = "new_foo.cc"; for (const auto& Header : TestHeaders) { auto Results = runClangMoveOnCode(Spec, Header.c_str(), TestCode); EXPECT_EQ("", Results[Spec.OldHeader]); EXPECT_EQ(Header, Results[Spec.NewHeader]); } } TEST(ClangMove, MacroInFunction) { const char TestHeader[] = "#define INT int\n" "class A {\npublic:\n int f();\n};\n" "class B {};\n"; const char TestCode[] = "#include \"foo.h\"\n" "INT A::f() { return 0; }\n"; const char ExpectedNewCode[] = "#include \"new_foo.h\"\n\n" "INT A::f() { return 0; }\n"; move::MoveDefinitionSpec Spec; Spec.Names.push_back("A"); Spec.OldHeader = "foo.h"; Spec.OldCC = "foo.cc"; Spec.NewHeader = "new_foo.h"; Spec.NewCC = "new_foo.cc"; auto Results = runClangMoveOnCode(Spec, TestHeader, TestCode); EXPECT_EQ(ExpectedNewCode, Results[Spec.NewCC]); } TEST(ClangMove, DefinitionInMacro) { const char TestHeader[] = "#define DEF(CLASS) void CLASS##_::f() {}\n" "#define DEF2(CLASS, ...) void CLASS##_::f2() {}\n" "class A_ {\nvoid f();\nvoid f2();\n};\n" "class B {};\n"; const char TestCode[] = "#include \"foo.h\"\n" "DEF(A)\n\n" "DEF2(A,\n" " 123)\n"; const char ExpectedNewCode[] = "#include \"new_foo.h\"\n\n" "DEF(A)\n\n" "DEF2(A, 123)\n"; move::MoveDefinitionSpec Spec; Spec.Names.push_back("A_"); Spec.OldHeader = "foo.h"; Spec.OldCC = "foo.cc"; Spec.NewHeader = "new_foo.h"; Spec.NewCC = "new_foo.cc"; auto Results = runClangMoveOnCode(Spec, TestHeader, TestCode); EXPECT_EQ(ExpectedNewCode, Results[Spec.NewCC]); } TEST(ClangMove, WellFormattedCode) { const std::string CommonHeader = "namespace a {\n" "namespace b {\n" "namespace c {\n" "class C;\n" "\n" "class A {\npublic:\n void f();\n void f2();\n};\n" "} // namespace c\n" "} // namespace b\n" "\n" "namespace d {\n" "namespace e {\n" "class B {\npublic:\n void f();\n};\n" "} // namespace e\n" "} // namespace d\n" "} // namespace a\n"; const std::string CommonCode = "\n" "namespace a {\n" "namespace b {\n" "namespace c {\n" "void A::f() {}\n" "\n" "void A::f2() {}\n" "} // namespace c\n" "} // namespace b\n" "\n" "namespace d {\n" "namespace e {\n" "void B::f() {}\n" "} // namespace e\n" "} // namespace d\n" "} // namespace a\n"; // Add dummy class to prevent behavior of moving all declarations from header. const std::string TestHeader = CommonHeader + "class D {};\n"; const std::string TestCode = "#include \"foo.h\"\n" + CommonCode; const std::string ExpectedNewHeader = "#ifndef NEW_FOO_H\n" "#define NEW_FOO_H\n" "\n" + CommonHeader + "\n" "#endif // NEW_FOO_H\n"; const std::string ExpectedNewCC = "#include \"new_foo.h\"\n" + CommonCode; move::MoveDefinitionSpec Spec; Spec.Names.push_back("a::b::c::A"); Spec.Names.push_back("a::d::e::B"); Spec.OldHeader = "foo.h"; Spec.OldCC = "foo.cc"; Spec.NewHeader = "new_foo.h"; Spec.NewCC = "new_foo.cc"; auto Results = runClangMoveOnCode(Spec, TestHeader.c_str(), TestCode.c_str()); EXPECT_EQ(ExpectedNewCC, Results[Spec.NewCC]); EXPECT_EQ(ExpectedNewHeader, Results[Spec.NewHeader]); } TEST(ClangMove, AddDependentNewHeader) { const char TestHeader[] = "class A {};\n" "class B {};\n"; const char TestCode[] = "#include \"foo.h\"\n"; const char ExpectedOldHeader[] = "#include \"new_foo.h\"\nclass B {};\n"; const char ExpectedNewHeader[] = "#ifndef NEW_FOO_H\n" "#define NEW_FOO_H\n" "\n" "class A {};\n" "\n" "#endif // NEW_FOO_H\n"; move::MoveDefinitionSpec Spec; Spec.Names.push_back("A"); Spec.OldHeader = "foo.h"; Spec.OldCC = "foo.cc"; Spec.NewHeader = "new_foo.h"; Spec.NewCC = "new_foo.cc"; Spec.OldDependOnNew = true; auto Results = runClangMoveOnCode(Spec, TestHeader, TestCode); EXPECT_EQ(ExpectedOldHeader, Results[Spec.OldHeader]); EXPECT_EQ(ExpectedNewHeader, Results[Spec.NewHeader]); } TEST(ClangMove, AddDependentOldHeader) { const char TestHeader[] = "class A {};\n" "class B {};\n"; const char TestCode[] = "#include \"foo.h\"\n"; const char ExpectedNewHeader[] = "#ifndef NEW_FOO_H\n" "#define NEW_FOO_H\n" "\n" "#include \"foo.h\"\n" "\n" "class B {};\n" "\n" "#endif // NEW_FOO_H\n"; const char ExpectedOldHeader[] = "class A {};\n"; move::MoveDefinitionSpec Spec; Spec.Names.push_back("B"); Spec.OldHeader = "foo.h"; Spec.OldCC = "foo.cc"; Spec.NewHeader = "new_foo.h"; Spec.NewCC = "new_foo.cc"; Spec.NewDependOnOld = true; auto Results = runClangMoveOnCode(Spec, TestHeader, TestCode); EXPECT_EQ(ExpectedNewHeader, Results[Spec.NewHeader]); EXPECT_EQ(ExpectedOldHeader, Results[Spec.OldHeader]); } TEST(ClangMove, DumpDecls) { const char TestHeader[] = "template \n" "class A {\n" " public:\n" " void f();\n" " template void h();\n" " static int b;\n" "};\n" "\n" "template void A::f() {}\n" "\n" "template \n" "template \n" "void A::h() {}\n" "\n" "template int A::b = 2;\n" "\n" "template <> class A {};\n" "\n" "class B {};\n" "\n" "namespace a {\n" "class Move1 {};\n" "void f1() {}\n" "template " "void f2(T t);\n" "} // namespace a\n" "\n" "class ForwardClass;\n" "namespace a {\n" "namespace b {\n" "class Move1 { public : void f(); };\n" "void f() {}\n" "enum E1 { Green };\n" "enum class E2 { Red };\n" "typedef int Int2;\n" "typedef A A_d;" "using Int = int;\n" "template \n" "using AA = A;\n" "extern int kGlobalInt;\n" "extern const char* const kGlobalStr;\n" "} // namespace b\n" "} // namespace a\n" "#define DEFINE_FOO class Foo {};\n" "DEFINE_FOO\n"; const char TestCode[] = "#include \"foo.h\"\n"; move::MoveDefinitionSpec Spec; Spec.Names.push_back("B"); Spec.OldHeader = "foo.h"; Spec.OldCC = "foo.cc"; Spec.NewHeader = "new_foo.h"; Spec.NewCC = "new_foo.cc"; DeclarationReporter Reporter; std::vector ExpectedDeclarations = { {"A", "Class", true}, {"B", "Class", false}, {"a::Move1", "Class", false}, {"a::f1", "Function", false}, {"a::f2", "Function", true}, {"a::b::Move1", "Class", false}, {"a::b::f", "Function", false}, {"a::b::E1", "Enum", false}, {"a::b::E2", "Enum", false}, {"a::b::Int2", "TypeAlias", false}, {"a::b::A_d", "TypeAlias", false}, {"a::b::Int", "TypeAlias", false}, {"a::b::AA", "TypeAlias", true}, {"a::b::kGlobalInt", "Variable", false}, {"a::b::kGlobalStr", "Variable", false}}; runClangMoveOnCode(Spec, TestHeader, TestCode, &Reporter); std::vector Results; for (const auto &DelPair : Reporter.getDeclarationList()) Results.push_back(DelPair); EXPECT_THAT(ExpectedDeclarations, testing::UnorderedElementsAreArray(Results)); } } // namespace } // namespace move } // namespace clang