Home | History | Annotate | Download | only in Tooling
      1 //===- unittest/Tooling/RefactoringTest.cpp - Refactoring unit tests ------===//
      2 //
      3 //                     The LLVM Compiler Infrastructure
      4 //
      5 // This file is distributed under the University of Illinois Open Source
      6 // License. See LICENSE.TXT for details.
      7 //
      8 //===----------------------------------------------------------------------===//
      9 
     10 #include "RewriterTestContext.h"
     11 #include "clang/AST/ASTConsumer.h"
     12 #include "clang/AST/ASTContext.h"
     13 #include "clang/AST/DeclCXX.h"
     14 #include "clang/AST/DeclGroup.h"
     15 #include "clang/AST/RecursiveASTVisitor.h"
     16 #include "clang/Basic/Diagnostic.h"
     17 #include "clang/Basic/DiagnosticOptions.h"
     18 #include "clang/Basic/FileManager.h"
     19 #include "clang/Basic/LangOptions.h"
     20 #include "clang/Basic/SourceManager.h"
     21 #include "clang/Frontend/CompilerInstance.h"
     22 #include "clang/Frontend/FrontendAction.h"
     23 #include "clang/Frontend/TextDiagnosticPrinter.h"
     24 #include "clang/Rewrite/Core/Rewriter.h"
     25 #include "clang/Tooling/Refactoring.h"
     26 #include "clang/Tooling/Tooling.h"
     27 #include "llvm/ADT/SmallString.h"
     28 #include "llvm/Support/Path.h"
     29 #include "gtest/gtest.h"
     30 
     31 namespace clang {
     32 namespace tooling {
     33 
     34 class ReplacementTest : public ::testing::Test {
     35  protected:
     36   Replacement createReplacement(SourceLocation Start, unsigned Length,
     37                                 llvm::StringRef ReplacementText) {
     38     return Replacement(Context.Sources, Start, Length, ReplacementText);
     39   }
     40 
     41   RewriterTestContext Context;
     42 };
     43 
     44 TEST_F(ReplacementTest, CanDeleteAllText) {
     45   FileID ID = Context.createInMemoryFile("input.cpp", "text");
     46   SourceLocation Location = Context.getLocation(ID, 1, 1);
     47   Replacement Replace(createReplacement(Location, 4, ""));
     48   EXPECT_TRUE(Replace.apply(Context.Rewrite));
     49   EXPECT_EQ("", Context.getRewrittenText(ID));
     50 }
     51 
     52 TEST_F(ReplacementTest, CanDeleteAllTextInTextWithNewlines) {
     53   FileID ID = Context.createInMemoryFile("input.cpp", "line1\nline2\nline3");
     54   SourceLocation Location = Context.getLocation(ID, 1, 1);
     55   Replacement Replace(createReplacement(Location, 17, ""));
     56   EXPECT_TRUE(Replace.apply(Context.Rewrite));
     57   EXPECT_EQ("", Context.getRewrittenText(ID));
     58 }
     59 
     60 TEST_F(ReplacementTest, CanAddText) {
     61   FileID ID = Context.createInMemoryFile("input.cpp", "");
     62   SourceLocation Location = Context.getLocation(ID, 1, 1);
     63   Replacement Replace(createReplacement(Location, 0, "result"));
     64   EXPECT_TRUE(Replace.apply(Context.Rewrite));
     65   EXPECT_EQ("result", Context.getRewrittenText(ID));
     66 }
     67 
     68 TEST_F(ReplacementTest, CanReplaceTextAtPosition) {
     69   FileID ID = Context.createInMemoryFile("input.cpp",
     70                                          "line1\nline2\nline3\nline4");
     71   SourceLocation Location = Context.getLocation(ID, 2, 3);
     72   Replacement Replace(createReplacement(Location, 12, "x"));
     73   EXPECT_TRUE(Replace.apply(Context.Rewrite));
     74   EXPECT_EQ("line1\nlixne4", Context.getRewrittenText(ID));
     75 }
     76 
     77 TEST_F(ReplacementTest, CanReplaceTextAtPositionMultipleTimes) {
     78   FileID ID = Context.createInMemoryFile("input.cpp",
     79                                          "line1\nline2\nline3\nline4");
     80   SourceLocation Location1 = Context.getLocation(ID, 2, 3);
     81   Replacement Replace1(createReplacement(Location1, 12, "x\ny\n"));
     82   EXPECT_TRUE(Replace1.apply(Context.Rewrite));
     83   EXPECT_EQ("line1\nlix\ny\nne4", Context.getRewrittenText(ID));
     84 
     85   // Since the original source has not been modified, the (4, 4) points to the
     86   // 'e' in the original content.
     87   SourceLocation Location2 = Context.getLocation(ID, 4, 4);
     88   Replacement Replace2(createReplacement(Location2, 1, "f"));
     89   EXPECT_TRUE(Replace2.apply(Context.Rewrite));
     90   EXPECT_EQ("line1\nlix\ny\nnf4", Context.getRewrittenText(ID));
     91 }
     92 
     93 TEST_F(ReplacementTest, ApplyFailsForNonExistentLocation) {
     94   Replacement Replace("nonexistent-file.cpp", 0, 1, "");
     95   EXPECT_FALSE(Replace.apply(Context.Rewrite));
     96 }
     97 
     98 TEST_F(ReplacementTest, CanRetrivePath) {
     99   Replacement Replace("/path/to/file.cpp", 0, 1, "");
    100   EXPECT_EQ("/path/to/file.cpp", Replace.getFilePath());
    101 }
    102 
    103 TEST_F(ReplacementTest, ReturnsInvalidPath) {
    104   Replacement Replace1(Context.Sources, SourceLocation(), 0, "");
    105   EXPECT_TRUE(Replace1.getFilePath().empty());
    106 
    107   Replacement Replace2;
    108   EXPECT_TRUE(Replace2.getFilePath().empty());
    109 }
    110 
    111 TEST_F(ReplacementTest, CanApplyReplacements) {
    112   FileID ID = Context.createInMemoryFile("input.cpp",
    113                                          "line1\nline2\nline3\nline4");
    114   Replacements Replaces;
    115   Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1),
    116                               5, "replaced"));
    117   Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 3, 1),
    118                               5, "other"));
    119   EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite));
    120   EXPECT_EQ("line1\nreplaced\nother\nline4", Context.getRewrittenText(ID));
    121 }
    122 
    123 TEST_F(ReplacementTest, SkipsDuplicateReplacements) {
    124   FileID ID = Context.createInMemoryFile("input.cpp",
    125                                          "line1\nline2\nline3\nline4");
    126   Replacements Replaces;
    127   Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1),
    128                               5, "replaced"));
    129   Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1),
    130                               5, "replaced"));
    131   Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1),
    132                               5, "replaced"));
    133   EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite));
    134   EXPECT_EQ("line1\nreplaced\nline3\nline4", Context.getRewrittenText(ID));
    135 }
    136 
    137 TEST_F(ReplacementTest, ApplyAllFailsIfOneApplyFails) {
    138   // This test depends on the value of the file name of an invalid source
    139   // location being in the range ]a, z[.
    140   FileID IDa = Context.createInMemoryFile("a.cpp", "text");
    141   FileID IDz = Context.createInMemoryFile("z.cpp", "text");
    142   Replacements Replaces;
    143   Replaces.insert(Replacement(Context.Sources, Context.getLocation(IDa, 1, 1),
    144                               4, "a"));
    145   Replaces.insert(Replacement(Context.Sources, SourceLocation(),
    146                               5, "2"));
    147   Replaces.insert(Replacement(Context.Sources, Context.getLocation(IDz, 1, 1),
    148                               4, "z"));
    149   EXPECT_FALSE(applyAllReplacements(Replaces, Context.Rewrite));
    150   EXPECT_EQ("a", Context.getRewrittenText(IDa));
    151   EXPECT_EQ("z", Context.getRewrittenText(IDz));
    152 }
    153 
    154 class FlushRewrittenFilesTest : public ::testing::Test {
    155  public:
    156   FlushRewrittenFilesTest() {
    157     std::string ErrorInfo;
    158     TemporaryDirectory = llvm::sys::Path::GetTemporaryDirectory(&ErrorInfo);
    159     assert(ErrorInfo.empty());
    160   }
    161 
    162   ~FlushRewrittenFilesTest() {
    163     std::string ErrorInfo;
    164     TemporaryDirectory.eraseFromDisk(true, &ErrorInfo);
    165     assert(ErrorInfo.empty());
    166   }
    167 
    168   FileID createFile(llvm::StringRef Name, llvm::StringRef Content) {
    169     SmallString<1024> Path(TemporaryDirectory.str());
    170     llvm::sys::path::append(Path, Name);
    171     std::string ErrorInfo;
    172     llvm::raw_fd_ostream OutStream(Path.c_str(),
    173                                    ErrorInfo, llvm::raw_fd_ostream::F_Binary);
    174     assert(ErrorInfo.empty());
    175     OutStream << Content;
    176     OutStream.close();
    177     const FileEntry *File = Context.Files.getFile(Path);
    178     assert(File != NULL);
    179     return Context.Sources.createFileID(File, SourceLocation(), SrcMgr::C_User);
    180   }
    181 
    182   std::string getFileContentFromDisk(llvm::StringRef Name) {
    183     SmallString<1024> Path(TemporaryDirectory.str());
    184     llvm::sys::path::append(Path, Name);
    185     // We need to read directly from the FileManager without relaying through
    186     // a FileEntry, as otherwise we'd read through an already opened file
    187     // descriptor, which might not see the changes made.
    188     // FIXME: Figure out whether there is a way to get the SourceManger to
    189     // reopen the file.
    190     return Context.Files.getBufferForFile(Path, NULL)->getBuffer();
    191   }
    192 
    193   llvm::sys::Path TemporaryDirectory;
    194   RewriterTestContext Context;
    195 };
    196 
    197 TEST_F(FlushRewrittenFilesTest, StoresChangesOnDisk) {
    198   FileID ID = createFile("input.cpp", "line1\nline2\nline3\nline4");
    199   Replacements Replaces;
    200   Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1),
    201                               5, "replaced"));
    202   EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite));
    203   EXPECT_FALSE(Context.Rewrite.overwriteChangedFiles());
    204   EXPECT_EQ("line1\nreplaced\nline3\nline4",
    205             getFileContentFromDisk("input.cpp"));
    206 }
    207 
    208 namespace {
    209 template <typename T>
    210 class TestVisitor : public clang::RecursiveASTVisitor<T> {
    211 public:
    212   bool runOver(StringRef Code) {
    213     return runToolOnCode(new TestAction(this), Code);
    214   }
    215 
    216 protected:
    217   clang::SourceManager *SM;
    218 
    219 private:
    220   class FindConsumer : public clang::ASTConsumer {
    221   public:
    222     FindConsumer(TestVisitor *Visitor) : Visitor(Visitor) {}
    223 
    224     virtual void HandleTranslationUnit(clang::ASTContext &Context) {
    225       Visitor->TraverseDecl(Context.getTranslationUnitDecl());
    226     }
    227 
    228   private:
    229     TestVisitor *Visitor;
    230   };
    231 
    232   class TestAction : public clang::ASTFrontendAction {
    233   public:
    234     TestAction(TestVisitor *Visitor) : Visitor(Visitor) {}
    235 
    236     virtual clang::ASTConsumer* CreateASTConsumer(
    237         clang::CompilerInstance& compiler, llvm::StringRef dummy) {
    238       Visitor->SM = &compiler.getSourceManager();
    239       /// TestConsumer will be deleted by the framework calling us.
    240       return new FindConsumer(Visitor);
    241     }
    242 
    243   private:
    244     TestVisitor *Visitor;
    245   };
    246 };
    247 } // end namespace
    248 
    249 void expectReplacementAt(const Replacement &Replace,
    250                          StringRef File, unsigned Offset, unsigned Length) {
    251   ASSERT_TRUE(Replace.isApplicable());
    252   EXPECT_EQ(File, Replace.getFilePath());
    253   EXPECT_EQ(Offset, Replace.getOffset());
    254   EXPECT_EQ(Length, Replace.getLength());
    255 }
    256 
    257 class ClassDeclXVisitor : public TestVisitor<ClassDeclXVisitor> {
    258 public:
    259   bool VisitCXXRecordDecl(CXXRecordDecl *Record) {
    260     if (Record->getName() == "X") {
    261       Replace = Replacement(*SM, Record, "");
    262     }
    263     return true;
    264   }
    265   Replacement Replace;
    266 };
    267 
    268 TEST(Replacement, CanBeConstructedFromNode) {
    269   ClassDeclXVisitor ClassDeclX;
    270   EXPECT_TRUE(ClassDeclX.runOver("     class X;"));
    271   expectReplacementAt(ClassDeclX.Replace, "input.cc", 5, 7);
    272 }
    273 
    274 TEST(Replacement, ReplacesAtSpellingLocation) {
    275   ClassDeclXVisitor ClassDeclX;
    276   EXPECT_TRUE(ClassDeclX.runOver("#define A(Y) Y\nA(class X);"));
    277   expectReplacementAt(ClassDeclX.Replace, "input.cc", 17, 7);
    278 }
    279 
    280 class CallToFVisitor : public TestVisitor<CallToFVisitor> {
    281 public:
    282   bool VisitCallExpr(CallExpr *Call) {
    283     if (Call->getDirectCallee()->getName() == "F") {
    284       Replace = Replacement(*SM, Call, "");
    285     }
    286     return true;
    287   }
    288   Replacement Replace;
    289 };
    290 
    291 TEST(Replacement, FunctionCall) {
    292   CallToFVisitor CallToF;
    293   EXPECT_TRUE(CallToF.runOver("void F(); void G() { F(); }"));
    294   expectReplacementAt(CallToF.Replace, "input.cc", 21, 3);
    295 }
    296 
    297 TEST(Replacement, TemplatedFunctionCall) {
    298   CallToFVisitor CallToF;
    299   EXPECT_TRUE(CallToF.runOver(
    300         "template <typename T> void F(); void G() { F<int>(); }"));
    301   expectReplacementAt(CallToF.Replace, "input.cc", 43, 8);
    302 }
    303 
    304 } // end namespace tooling
    305 } // end namespace clang
    306