diff options
author | Guillaume Papin <guillaume.papin@epitech.eu> | 2013-08-22 14:49:07 +0000 |
---|---|---|
committer | Guillaume Papin <guillaume.papin@epitech.eu> | 2013-08-22 14:49:07 +0000 |
commit | effabf96916b766b9aa2324daf006b4da281fa57 (patch) | |
tree | e4de1a5e50f9080c5b249bbfea48fc1064575199 /clang-tools-extra/unittests | |
parent | 5371e90f7a0f21676318d42574b52522d06d79b3 (diff) | |
download | bcm5719-llvm-effabf96916b766b9aa2324daf006b4da281fa57.tar.gz bcm5719-llvm-effabf96916b766b9aa2324daf006b4da281fa57.zip |
cpp11-migrate: Add a class to support include directives modifications
The IncludeDirectives class helps with detecting and modifying #include
directives. For now it allows the users to add angled-includes in a source file.
This is a start for this class that will evolve in the future to add more
functionality.
This should fix the reverted commit r188791 (buildbot failures on Windows).
llvm-svn: 189017
Diffstat (limited to 'clang-tools-extra/unittests')
3 files changed, 427 insertions, 7 deletions
diff --git a/clang-tools-extra/unittests/cpp11-migrate/CMakeLists.txt b/clang-tools-extra/unittests/cpp11-migrate/CMakeLists.txt index 1f647d5dc19..fcf678d3490 100644 --- a/clang-tools-extra/unittests/cpp11-migrate/CMakeLists.txt +++ b/clang-tools-extra/unittests/cpp11-migrate/CMakeLists.txt @@ -13,6 +13,7 @@ add_extra_unittest(Cpp11MigrateTests PerfSupportTest.cpp
TransformTest.cpp
UniqueHeaderNameTest.cpp
+ IncludeDirectivesTest.cpp
)
target_link_libraries(Cpp11MigrateTests
diff --git a/clang-tools-extra/unittests/cpp11-migrate/IncludeDirectivesTest.cpp b/clang-tools-extra/unittests/cpp11-migrate/IncludeDirectivesTest.cpp new file mode 100644 index 00000000000..269ea377326 --- /dev/null +++ b/clang-tools-extra/unittests/cpp11-migrate/IncludeDirectivesTest.cpp @@ -0,0 +1,413 @@ +//===- cpp11-migrate/IncludeDirectivesTest.cpp ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Core/IncludeDirectives.h" +#include "gtest/gtest.h" +#include "VirtualFileHelper.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "llvm/Support/Path.h" + +using namespace llvm; +using namespace clang; + +/// \brief A convenience method around \c tooling::runToolOnCodeWithArgs() that +/// adds the current directory to the include search paths. +static void applyActionOnCode(FrontendAction *ToolAction, StringRef Code) { + SmallString<128> CurrentDir; + ASSERT_FALSE(llvm::sys::fs::current_path(CurrentDir)); + + // Add the current directory to the header search paths so angled includes can + // find them. + std::vector<std::string> Args; + Args.push_back("-I"); + Args.push_back(CurrentDir.str().str()); + + // mapVirtualFile() needs absolute path for the input file as well. + SmallString<128> InputFile(CurrentDir); + sys::path::append(InputFile, "input.cc"); + + ASSERT_TRUE( + tooling::runToolOnCodeWithArgs(ToolAction, Code, Args, InputFile.str())); +} + +namespace { +class TestAddIncludeAction : public PreprocessOnlyAction { +public: + TestAddIncludeAction(StringRef Include, tooling::Replacements &Replaces, + const char *HeaderToModify = 0) + : Include(Include), Replaces(Replaces), HeaderToModify(HeaderToModify) { + // some headers that the tests can include + mapVirtualHeader("foo-inner.h", "#pragma once\n"); + mapVirtualHeader("foo.h", "#pragma once\n" + "#include <virtual/foo-inner.h>\n"); + mapVirtualHeader("bar-inner.h", "#pragma once\n"); + mapVirtualHeader("bar.h", "#pragma once\n" + "#include <virtual/bar-inner.h>\n"); + mapVirtualHeader("xmacro.def", "X(Val1)\n" + "X(Val2)\n" + "X(Val3)\n"); + } + + /// \brief Make \p FileName an absolute path. + /// + /// Header files are mapped in the 'virtual/' subdirectory of the current + /// working directory. The current working directory is used because it's + /// important to map files with absolute paths. + /// + /// When used in conjunction with \c applyActionOnCode() (which adds the + /// current working directory to the header search paths) it is possible to + /// refer to the headers by using 'virtual/\<FileName\>'. + std::string makeHeaderFileName(StringRef FileName) const { + SmallString<128> Path; + llvm::error_code EC = llvm::sys::fs::current_path(Path); + assert(!EC); + (void)EC; + + sys::path::append(Path, "virtual"); + sys::path::append(Path, FileName); + return Path.str().str(); + } + + /// \brief Map additional header files. + /// + /// \sa makeHeaderFileName() + void mapVirtualHeader(StringRef FileName, StringRef Content) { + VFHelper.mapFile(makeHeaderFileName(FileName), Content); + } + +private: + virtual bool BeginSourceFileAction(CompilerInstance &CI, + StringRef FileName) LLVM_OVERRIDE { + if (!PreprocessOnlyAction::BeginSourceFileAction(CI, FileName)) + return false; + VFHelper.mapVirtualFiles(CI.getSourceManager()); + + FileToModify = + HeaderToModify ? makeHeaderFileName(HeaderToModify) : FileName.str(); + + FileIncludes.reset(new IncludeDirectives(CI)); + return true; + } + + virtual void EndSourceFileAction() LLVM_OVERRIDE { + const tooling::Replacement &Replace = + FileIncludes->addAngledInclude(FileToModify, Include); + if (Replace.isApplicable()) + Replaces.insert(Replace); + } + + StringRef Include; + VirtualFileHelper VFHelper; + tooling::Replacements &Replaces; + OwningPtr<IncludeDirectives> FileIncludes; + std::string FileToModify; + // if non-null, add the include directives in this file instead of the main + // file. + const char *HeaderToModify; +}; + +std::string addIncludeInCode(StringRef Include, StringRef Code) { + tooling::Replacements Replaces; + + applyActionOnCode(new TestAddIncludeAction(Include, Replaces), Code); + + if (::testing::Test::HasFailure()) + return "<<unexpected error from applyActionOnCode()>>"; + + return tooling::applyAllReplacements(Code, Replaces); +} +} // end anonymous namespace + +TEST(IncludeDirectivesTest2, endOfLinesVariants) { + EXPECT_EQ("#include <virtual/foo.h>\n" + "#include <bar>\n", + addIncludeInCode("bar", "#include <virtual/foo.h>\n")); + EXPECT_EQ("#include <virtual/foo.h>\r\n" + "#include <bar>\r\n", + addIncludeInCode("bar", "#include <virtual/foo.h>\r\n")); + EXPECT_EQ("#include <virtual/foo.h>\r" + "#include <bar>\r", + addIncludeInCode("bar", "#include <virtual/foo.h>\r")); +} + +TEST(IncludeDirectivesTest, ppToken) { + EXPECT_EQ("#define FOO <virtual/foo.h>\n" + "#include FOO\n" + "#include <bar>\n" + "int i;\n", + addIncludeInCode("bar", "#define FOO <virtual/foo.h>\n" + "#include FOO\n" + "int i;\n")); +} + +TEST(IncludeDirectivesTest, noFileHeader) { + EXPECT_EQ("#include <bar>\n" + "\n" + "int foo;\n", + addIncludeInCode("bar", "int foo;\n")); +} + +TEST(IncludeDirectivesTest, commentBeforeTopMostCode) { + EXPECT_EQ("#include <bar>\n" + "\n" + "// Foo\n" + "int foo;\n", + addIncludeInCode("bar", "// Foo\n" + "int foo;\n")); +} + +TEST(IncludeDirectivesTest, multiLineComment) { + EXPECT_EQ("#include <virtual/foo.h> /* \n */\n" + "#include <bar>\n", + addIncludeInCode("bar", "#include <virtual/foo.h> /* \n */\n")); + EXPECT_EQ("#include <virtual/foo.h> /* \n */" + "\n#include <bar>", + addIncludeInCode("bar", "#include <virtual/foo.h> /* \n */")); +} + +TEST(IncludeDirectivesTest, multilineCommentWithTrailingSpace) { + EXPECT_EQ("#include <virtual/foo.h> /*\n*/ \n" + "#include <bar>\n", + addIncludeInCode("bar", "#include <virtual/foo.h> /*\n*/ \n")); + EXPECT_EQ("#include <virtual/foo.h> /*\n*/ " + "\n#include <bar>", + addIncludeInCode("bar", "#include <virtual/foo.h> /*\n*/ ")); +} + +TEST(IncludeDirectivesTest, fileHeaders) { + EXPECT_EQ("// this is a header\n" + "// some license stuff here\n" + "\n" + "#include <bar>\n" + "\n" + "/// \\brief Foo\n" + "int foo;\n", + addIncludeInCode("bar", "// this is a header\n" + "// some license stuff here\n" + "\n" + "/// \\brief Foo\n" + "int foo;\n")); +} + +TEST(IncludeDirectivesTest, preferablyAngledNextToAngled) { + EXPECT_EQ("#include <virtual/foo.h>\n" + "#include <bar>\n" + "#include \"virtual/bar.h\"\n", + addIncludeInCode("bar", "#include <virtual/foo.h>\n" + "#include \"virtual/bar.h\"\n")); + EXPECT_EQ("#include \"virtual/foo.h\"\n" + "#include \"virtual/bar.h\"\n" + "#include <bar>\n", + addIncludeInCode("bar", "#include \"virtual/foo.h\"\n" + "#include \"virtual/bar.h\"\n")); +} + +TEST(IncludeDirectivesTest, avoidDuplicates) { + EXPECT_EQ("#include <virtual/foo.h>\n", + addIncludeInCode("virtual/foo.h", "#include <virtual/foo.h>\n")); +} + +// Tests includes in the middle of the code are ignored. +TEST(IncludeDirectivesTest, ignoreHeadersMeantForMultipleInclusion) { + std::string Expected = "#include \"virtual/foo.h\"\n" + "#include <bar>\n" + "\n" + "enum Kind {\n" + "#define X(A) K_##A,\n" + "#include \"virtual/xmacro.def\"\n" + "#undef X\n" + " K_NUM_KINDS\n" + "};\n"; + std::string Result = + addIncludeInCode("bar", "#include \"virtual/foo.h\"\n" + "\n" + "enum Kind {\n" + "#define X(A) K_##A,\n" + "#include \"virtual/xmacro.def\"\n" + "#undef X\n" + " K_NUM_KINDS\n" + "};\n"); + EXPECT_EQ(Expected, Result); +} + +namespace { +TestAddIncludeAction *makeIndirectTestsAction(const char *HeaderToModify, + tooling::Replacements &Replaces) { + StringRef IncludeToAdd = "virtual/c.h"; + TestAddIncludeAction *TestAction = + new TestAddIncludeAction(IncludeToAdd, Replaces, HeaderToModify); + TestAction->mapVirtualHeader("c.h", "#pragma once\n"); + TestAction->mapVirtualHeader("a.h", "#pragma once\n" + "#include <virtual/c.h>\n"); + TestAction->mapVirtualHeader("b.h", "#pragma once\n"); + return TestAction; +} +} // end anonymous namespace + +TEST(IncludeDirectivesTest, indirectIncludes) { + // In TestAddIncludeAction 'foo.h' includes 'foo-inner.h'. Check that we + // aren't including foo-inner.h again. + EXPECT_EQ( + "#include <virtual/foo.h>\n", + addIncludeInCode("virtual/foo-inner.h", "#include <virtual/foo.h>\n")); + + tooling::Replacements Replaces; + StringRef Code = "#include <virtual/a.h>\n" + "#include <virtual/b.h>\n"; + + // a.h already includes c.h + { + FrontendAction *Action = makeIndirectTestsAction("a.h", Replaces); + ASSERT_NO_FATAL_FAILURE(applyActionOnCode(Action, Code)); + EXPECT_EQ(unsigned(0), Replaces.size()); + } + + // c.h is included before b.h but b.h doesn't include c.h directly, so check + // that it will be inserted. + { + FrontendAction *Action = makeIndirectTestsAction("b.h", Replaces); + ASSERT_NO_FATAL_FAILURE(applyActionOnCode(Action, Code)); + EXPECT_EQ("#include <virtual/c.h>\n\n\n", + tooling::applyAllReplacements("\n", Replaces)); + } +} + +/// \brief Convenience method to test header guards detection implementation. +static std::string addIncludeInGuardedHeader(StringRef IncludeToAdd, + StringRef GuardedHeaderCode) { + const char *GuardedHeaderName = "guarded.h"; + tooling::Replacements Replaces; + TestAddIncludeAction *TestAction = + new TestAddIncludeAction(IncludeToAdd, Replaces, GuardedHeaderName); + TestAction->mapVirtualHeader(GuardedHeaderName, GuardedHeaderCode); + + applyActionOnCode(TestAction, "#include <virtual/guarded.h>\n"); + if (::testing::Test::HasFailure()) + return "<<unexpected error from applyActionOnCode()>>"; + + return tooling::applyAllReplacements(GuardedHeaderCode, Replaces); +} + +TEST(IncludeDirectivesTest, insertInsideIncludeGuard) { + EXPECT_EQ("#ifndef GUARD_H\n" + "#define GUARD_H\n" + "\n" + "#include <foo>\n" + "\n" + "struct foo {};\n" + "\n" + "#endif // GUARD_H\n", + addIncludeInGuardedHeader("foo", "#ifndef GUARD_H\n" + "#define GUARD_H\n" + "\n" + "struct foo {};\n" + "\n" + "#endif // GUARD_H\n")); +} + +TEST(IncludeDirectivesTest, guardAndHeader) { + EXPECT_EQ("// File header\n" + "\n" + "#ifndef GUARD_H\n" + "#define GUARD_H\n" + "\n" + "#include <foo>\n" + "\n" + "struct foo {};\n" + "\n" + "#endif // GUARD_H\n", + addIncludeInGuardedHeader("foo", "// File header\n" + "\n" + "#ifndef GUARD_H\n" + "#define GUARD_H\n" + "\n" + "struct foo {};\n" + "\n" + "#endif // GUARD_H\n")); +} + +TEST(IncludeDirectivesTest, fullHeaderFitsAsAPreamble) { + EXPECT_EQ("#ifndef GUARD_H\n" + "#define GUARD_H\n" + "\n" + "#include <foo>\n" + "\n" + "#define FOO 1\n" + "\n" + "#endif // GUARD_H\n", + addIncludeInGuardedHeader("foo", "#ifndef GUARD_H\n" + "#define GUARD_H\n" + "\n" + "#define FOO 1\n" + "\n" + "#endif // GUARD_H\n")); +} + +TEST(IncludeDirectivesTest, codeBeforeIfndef) { + EXPECT_EQ("#include <foo>\n" + "\n" + "int bar;\n" + "\n" + "#ifndef GUARD_H\n" + "#define GUARD_H\n" + "\n" + "struct foo;" + "\n" + "#endif // GUARD_H\n", + addIncludeInGuardedHeader("foo", "int bar;\n" + "\n" + "#ifndef GUARD_H\n" + "#define GUARD_H\n" + "\n" + "struct foo;" + "\n" + "#endif // GUARD_H\n")); +} + +TEST(IncludeDirectivesTest, codeAfterEndif) { + EXPECT_EQ("#include <foo>\n" + "\n" + "#ifndef GUARD_H\n" + "#define GUARD_H\n" + "\n" + "struct foo;" + "\n" + "#endif // GUARD_H\n" + "\n" + "int bar;\n", + addIncludeInGuardedHeader("foo", "#ifndef GUARD_H\n" + "#define GUARD_H\n" + "\n" + "struct foo;" + "\n" + "#endif // GUARD_H\n" + "\n" + "int bar;\n")); +} + +TEST(IncludeDirectivesTest, headerGuardWithInclude) { + EXPECT_EQ("#ifndef GUARD_H\n" + "#define GUARD_H\n" + "\n" + "#include <virtual/bar.h>\n" + "#include <foo>\n" + "\n" + "struct foo;\n" + "\n" + "#endif // GUARD_H\n", + addIncludeInGuardedHeader("foo", "#ifndef GUARD_H\n" + "#define GUARD_H\n" + "\n" + "#include <virtual/bar.h>\n" + "\n" + "struct foo;\n" + "\n" + "#endif // GUARD_H\n")); +} diff --git a/clang-tools-extra/unittests/cpp11-migrate/VirtualFileHelper.h b/clang-tools-extra/unittests/cpp11-migrate/VirtualFileHelper.h index 4ae29fbe0c0..093ace1d0dc 100644 --- a/clang-tools-extra/unittests/cpp11-migrate/VirtualFileHelper.h +++ b/clang-tools-extra/unittests/cpp11-migrate/VirtualFileHelper.h @@ -27,8 +27,8 @@ namespace clang { /// map virtual files conveniently. class VirtualFileHelper { struct VirtualFile { - llvm::StringRef FileName; - llvm::StringRef Code; + std::string FileName; + std::string Code; }; public: @@ -49,15 +49,21 @@ public: /// mapped to it. SourceManager &getNewSourceManager() { Sources.reset(new SourceManager(Diagnostics, Files)); - for (llvm::SmallVectorImpl<VirtualFile>::iterator I = VirtualFiles.begin(), - E = VirtualFiles.end(); + mapVirtualFiles(*Sources); + return *Sources; + } + + /// \brief Map the virtual file contents in the given \c SourceManager. + void mapVirtualFiles(SourceManager &SM) const { + for (llvm::SmallVectorImpl<VirtualFile>::const_iterator + I = VirtualFiles.begin(), + E = VirtualFiles.end(); I != E; ++I) { llvm::MemoryBuffer *Buf = llvm::MemoryBuffer::getMemBuffer(I->Code); - const FileEntry *Entry = Files.getVirtualFile( + const FileEntry *Entry = SM.getFileManager().getVirtualFile( I->FileName, Buf->getBufferSize(), /*ModificationTime=*/0); - Sources->overrideFileContents(Entry, Buf); + SM.overrideFileContents(Entry, Buf); } - return *Sources; } private: |