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