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 // FIXME: Remove this test case when Replacements is implemented as std::vector 124 // instead of std::set. The other ReplacementTest tests will need to be updated 125 // at that point as well. 126 TEST_F(ReplacementTest, VectorCanApplyReplacements) { 127 FileID ID = Context.createInMemoryFile("input.cpp", 128 "line1\nline2\nline3\nline4"); 129 std::vector<Replacement> Replaces; 130 Replaces.push_back(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), 131 5, "replaced")); 132 Replaces.push_back( 133 Replacement(Context.Sources, Context.getLocation(ID, 3, 1), 5, "other")); 134 EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite)); 135 EXPECT_EQ("line1\nreplaced\nother\nline4", Context.getRewrittenText(ID)); 136 } 137 138 TEST_F(ReplacementTest, SkipsDuplicateReplacements) { 139 FileID ID = Context.createInMemoryFile("input.cpp", 140 "line1\nline2\nline3\nline4"); 141 Replacements Replaces; 142 Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), 143 5, "replaced")); 144 Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), 145 5, "replaced")); 146 Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), 147 5, "replaced")); 148 EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite)); 149 EXPECT_EQ("line1\nreplaced\nline3\nline4", Context.getRewrittenText(ID)); 150 } 151 152 TEST_F(ReplacementTest, ApplyAllFailsIfOneApplyFails) { 153 // This test depends on the value of the file name of an invalid source 154 // location being in the range ]a, z[. 155 FileID IDa = Context.createInMemoryFile("a.cpp", "text"); 156 FileID IDz = Context.createInMemoryFile("z.cpp", "text"); 157 Replacements Replaces; 158 Replaces.insert(Replacement(Context.Sources, Context.getLocation(IDa, 1, 1), 159 4, "a")); 160 Replaces.insert(Replacement(Context.Sources, SourceLocation(), 161 5, "2")); 162 Replaces.insert(Replacement(Context.Sources, Context.getLocation(IDz, 1, 1), 163 4, "z")); 164 EXPECT_FALSE(applyAllReplacements(Replaces, Context.Rewrite)); 165 EXPECT_EQ("a", Context.getRewrittenText(IDa)); 166 EXPECT_EQ("z", Context.getRewrittenText(IDz)); 167 } 168 169 TEST(ShiftedCodePositionTest, FindsNewCodePosition) { 170 Replacements Replaces; 171 Replaces.insert(Replacement("", 0, 1, "")); 172 Replaces.insert(Replacement("", 4, 3, " ")); 173 // Assume ' int i;' is turned into 'int i;' and cursor is located at '|'. 174 EXPECT_EQ(0u, shiftedCodePosition(Replaces, 0)); // |int i; 175 EXPECT_EQ(0u, shiftedCodePosition(Replaces, 1)); // |nt i; 176 EXPECT_EQ(1u, shiftedCodePosition(Replaces, 2)); // i|t i; 177 EXPECT_EQ(2u, shiftedCodePosition(Replaces, 3)); // in| i; 178 EXPECT_EQ(3u, shiftedCodePosition(Replaces, 4)); // int| i; 179 EXPECT_EQ(3u, shiftedCodePosition(Replaces, 5)); // int | i; 180 EXPECT_EQ(3u, shiftedCodePosition(Replaces, 6)); // int |i; 181 EXPECT_EQ(4u, shiftedCodePosition(Replaces, 7)); // int |; 182 EXPECT_EQ(5u, shiftedCodePosition(Replaces, 8)); // int i| 183 } 184 185 // FIXME: Remove this test case when Replacements is implemented as std::vector 186 // instead of std::set. The other ReplacementTest tests will need to be updated 187 // at that point as well. 188 TEST(ShiftedCodePositionTest, VectorFindsNewCodePositionWithInserts) { 189 std::vector<Replacement> Replaces; 190 Replaces.push_back(Replacement("", 0, 1, "")); 191 Replaces.push_back(Replacement("", 4, 3, " ")); 192 // Assume ' int i;' is turned into 'int i;' and cursor is located at '|'. 193 EXPECT_EQ(0u, shiftedCodePosition(Replaces, 0)); // |int i; 194 EXPECT_EQ(0u, shiftedCodePosition(Replaces, 1)); // |nt i; 195 EXPECT_EQ(1u, shiftedCodePosition(Replaces, 2)); // i|t i; 196 EXPECT_EQ(2u, shiftedCodePosition(Replaces, 3)); // in| i; 197 EXPECT_EQ(3u, shiftedCodePosition(Replaces, 4)); // int| i; 198 EXPECT_EQ(3u, shiftedCodePosition(Replaces, 5)); // int | i; 199 EXPECT_EQ(3u, shiftedCodePosition(Replaces, 6)); // int |i; 200 EXPECT_EQ(4u, shiftedCodePosition(Replaces, 7)); // int |; 201 EXPECT_EQ(5u, shiftedCodePosition(Replaces, 8)); // int i| 202 } 203 204 TEST(ShiftedCodePositionTest, FindsNewCodePositionWithInserts) { 205 Replacements Replaces; 206 Replaces.insert(Replacement("", 4, 0, "\"\n\"")); 207 // Assume '"12345678"' is turned into '"1234"\n"5678"'. 208 EXPECT_EQ(3u, shiftedCodePosition(Replaces, 3)); // "123|5678" 209 EXPECT_EQ(7u, shiftedCodePosition(Replaces, 4)); // "1234|678" 210 EXPECT_EQ(8u, shiftedCodePosition(Replaces, 5)); // "12345|78" 211 } 212 213 TEST(ShiftedCodePositionTest, FindsNewCodePositionInReplacedText) { 214 Replacements Replaces; 215 // Replace the first four characters with "abcd". 216 Replaces.insert(Replacement("", 0, 4, "abcd")); 217 for (unsigned i = 0; i < 3; ++i) 218 EXPECT_EQ(i, shiftedCodePosition(Replaces, i)); 219 } 220 221 class FlushRewrittenFilesTest : public ::testing::Test { 222 public: 223 FlushRewrittenFilesTest() {} 224 225 ~FlushRewrittenFilesTest() override { 226 for (llvm::StringMap<std::string>::iterator I = TemporaryFiles.begin(), 227 E = TemporaryFiles.end(); 228 I != E; ++I) { 229 llvm::StringRef Name = I->second; 230 std::error_code EC = llvm::sys::fs::remove(Name); 231 (void)EC; 232 assert(!EC); 233 } 234 } 235 236 FileID createFile(llvm::StringRef Name, llvm::StringRef Content) { 237 SmallString<1024> Path; 238 int FD; 239 std::error_code EC = llvm::sys::fs::createTemporaryFile(Name, "", FD, Path); 240 assert(!EC); 241 (void)EC; 242 243 llvm::raw_fd_ostream OutStream(FD, true); 244 OutStream << Content; 245 OutStream.close(); 246 const FileEntry *File = Context.Files.getFile(Path); 247 assert(File != nullptr); 248 249 StringRef Found = 250 TemporaryFiles.insert(std::make_pair(Name, Path.str())).first->second; 251 assert(Found == Path); 252 (void)Found; 253 return Context.Sources.createFileID(File, SourceLocation(), SrcMgr::C_User); 254 } 255 256 std::string getFileContentFromDisk(llvm::StringRef Name) { 257 std::string Path = TemporaryFiles.lookup(Name); 258 assert(!Path.empty()); 259 // We need to read directly from the FileManager without relaying through 260 // a FileEntry, as otherwise we'd read through an already opened file 261 // descriptor, which might not see the changes made. 262 // FIXME: Figure out whether there is a way to get the SourceManger to 263 // reopen the file. 264 auto FileBuffer = Context.Files.getBufferForFile(Path); 265 return (*FileBuffer)->getBuffer(); 266 } 267 268 llvm::StringMap<std::string> TemporaryFiles; 269 RewriterTestContext Context; 270 }; 271 272 TEST_F(FlushRewrittenFilesTest, StoresChangesOnDisk) { 273 FileID ID = createFile("input.cpp", "line1\nline2\nline3\nline4"); 274 Replacements Replaces; 275 Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), 276 5, "replaced")); 277 EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite)); 278 EXPECT_FALSE(Context.Rewrite.overwriteChangedFiles()); 279 EXPECT_EQ("line1\nreplaced\nline3\nline4", 280 getFileContentFromDisk("input.cpp")); 281 } 282 283 namespace { 284 template <typename T> 285 class TestVisitor : public clang::RecursiveASTVisitor<T> { 286 public: 287 bool runOver(StringRef Code) { 288 return runToolOnCode(new TestAction(this), Code); 289 } 290 291 protected: 292 clang::SourceManager *SM; 293 clang::ASTContext *Context; 294 295 private: 296 class FindConsumer : public clang::ASTConsumer { 297 public: 298 FindConsumer(TestVisitor *Visitor) : Visitor(Visitor) {} 299 300 void HandleTranslationUnit(clang::ASTContext &Context) override { 301 Visitor->TraverseDecl(Context.getTranslationUnitDecl()); 302 } 303 304 private: 305 TestVisitor *Visitor; 306 }; 307 308 class TestAction : public clang::ASTFrontendAction { 309 public: 310 TestAction(TestVisitor *Visitor) : Visitor(Visitor) {} 311 312 std::unique_ptr<clang::ASTConsumer> 313 CreateASTConsumer(clang::CompilerInstance &compiler, 314 llvm::StringRef dummy) override { 315 Visitor->SM = &compiler.getSourceManager(); 316 Visitor->Context = &compiler.getASTContext(); 317 /// TestConsumer will be deleted by the framework calling us. 318 return llvm::make_unique<FindConsumer>(Visitor); 319 } 320 321 private: 322 TestVisitor *Visitor; 323 }; 324 }; 325 } // end namespace 326 327 void expectReplacementAt(const Replacement &Replace, 328 StringRef File, unsigned Offset, unsigned Length) { 329 ASSERT_TRUE(Replace.isApplicable()); 330 EXPECT_EQ(File, Replace.getFilePath()); 331 EXPECT_EQ(Offset, Replace.getOffset()); 332 EXPECT_EQ(Length, Replace.getLength()); 333 } 334 335 class ClassDeclXVisitor : public TestVisitor<ClassDeclXVisitor> { 336 public: 337 bool VisitCXXRecordDecl(CXXRecordDecl *Record) { 338 if (Record->getName() == "X") { 339 Replace = Replacement(*SM, Record, ""); 340 } 341 return true; 342 } 343 Replacement Replace; 344 }; 345 346 TEST(Replacement, CanBeConstructedFromNode) { 347 ClassDeclXVisitor ClassDeclX; 348 EXPECT_TRUE(ClassDeclX.runOver(" class X;")); 349 expectReplacementAt(ClassDeclX.Replace, "input.cc", 5, 7); 350 } 351 352 TEST(Replacement, ReplacesAtSpellingLocation) { 353 ClassDeclXVisitor ClassDeclX; 354 EXPECT_TRUE(ClassDeclX.runOver("#define A(Y) Y\nA(class X);")); 355 expectReplacementAt(ClassDeclX.Replace, "input.cc", 17, 7); 356 } 357 358 class CallToFVisitor : public TestVisitor<CallToFVisitor> { 359 public: 360 bool VisitCallExpr(CallExpr *Call) { 361 if (Call->getDirectCallee()->getName() == "F") { 362 Replace = Replacement(*SM, Call, ""); 363 } 364 return true; 365 } 366 Replacement Replace; 367 }; 368 369 TEST(Replacement, FunctionCall) { 370 CallToFVisitor CallToF; 371 EXPECT_TRUE(CallToF.runOver("void F(); void G() { F(); }")); 372 expectReplacementAt(CallToF.Replace, "input.cc", 21, 3); 373 } 374 375 TEST(Replacement, TemplatedFunctionCall) { 376 CallToFVisitor CallToF; 377 EXPECT_TRUE(CallToF.runOver( 378 "template <typename T> void F(); void G() { F<int>(); }")); 379 expectReplacementAt(CallToF.Replace, "input.cc", 43, 8); 380 } 381 382 class NestedNameSpecifierAVisitor 383 : public TestVisitor<NestedNameSpecifierAVisitor> { 384 public: 385 bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc NNSLoc) { 386 if (NNSLoc.getNestedNameSpecifier()) { 387 if (const NamespaceDecl* NS = NNSLoc.getNestedNameSpecifier()->getAsNamespace()) { 388 if (NS->getName() == "a") { 389 Replace = Replacement(*SM, &NNSLoc, "", Context->getLangOpts()); 390 } 391 } 392 } 393 return TestVisitor<NestedNameSpecifierAVisitor>::TraverseNestedNameSpecifierLoc( 394 NNSLoc); 395 } 396 Replacement Replace; 397 }; 398 399 TEST(Replacement, ColonColon) { 400 NestedNameSpecifierAVisitor VisitNNSA; 401 EXPECT_TRUE(VisitNNSA.runOver("namespace a { void f() { ::a::f(); } }")); 402 expectReplacementAt(VisitNNSA.Replace, "input.cc", 25, 5); 403 } 404 405 TEST(Range, overlaps) { 406 EXPECT_TRUE(Range(10, 10).overlapsWith(Range(0, 11))); 407 EXPECT_TRUE(Range(0, 11).overlapsWith(Range(10, 10))); 408 EXPECT_FALSE(Range(10, 10).overlapsWith(Range(0, 10))); 409 EXPECT_FALSE(Range(0, 10).overlapsWith(Range(10, 10))); 410 EXPECT_TRUE(Range(0, 10).overlapsWith(Range(2, 6))); 411 EXPECT_TRUE(Range(2, 6).overlapsWith(Range(0, 10))); 412 } 413 414 TEST(Range, contains) { 415 EXPECT_TRUE(Range(0, 10).contains(Range(0, 10))); 416 EXPECT_TRUE(Range(0, 10).contains(Range(2, 6))); 417 EXPECT_FALSE(Range(2, 6).contains(Range(0, 10))); 418 EXPECT_FALSE(Range(0, 10).contains(Range(0, 11))); 419 } 420 421 TEST(DeduplicateTest, removesDuplicates) { 422 std::vector<Replacement> Input; 423 Input.push_back(Replacement("fileA", 50, 0, " foo ")); 424 Input.push_back(Replacement("fileA", 10, 3, " bar ")); 425 Input.push_back(Replacement("fileA", 10, 2, " bar ")); // Length differs 426 Input.push_back(Replacement("fileA", 9, 3, " bar ")); // Offset differs 427 Input.push_back(Replacement("fileA", 50, 0, " foo ")); // Duplicate 428 Input.push_back(Replacement("fileA", 51, 3, " bar ")); 429 Input.push_back(Replacement("fileB", 51, 3, " bar ")); // Filename differs! 430 Input.push_back(Replacement("fileB", 60, 1, " bar ")); 431 Input.push_back(Replacement("fileA", 60, 2, " bar ")); 432 Input.push_back(Replacement("fileA", 51, 3, " moo ")); // Replacement text 433 // differs! 434 435 std::vector<Replacement> Expected; 436 Expected.push_back(Replacement("fileA", 9, 3, " bar ")); 437 Expected.push_back(Replacement("fileA", 10, 2, " bar ")); 438 Expected.push_back(Replacement("fileA", 10, 3, " bar ")); 439 Expected.push_back(Replacement("fileA", 50, 0, " foo ")); 440 Expected.push_back(Replacement("fileA", 51, 3, " bar ")); 441 Expected.push_back(Replacement("fileA", 51, 3, " moo ")); 442 Expected.push_back(Replacement("fileB", 60, 1, " bar ")); 443 Expected.push_back(Replacement("fileA", 60, 2, " bar ")); 444 445 std::vector<Range> Conflicts; // Ignored for this test 446 deduplicate(Input, Conflicts); 447 448 EXPECT_EQ(3U, Conflicts.size()); 449 EXPECT_EQ(Expected, Input); 450 } 451 452 TEST(DeduplicateTest, detectsConflicts) { 453 { 454 std::vector<Replacement> Input; 455 Input.push_back(Replacement("fileA", 0, 5, " foo ")); 456 Input.push_back(Replacement("fileA", 0, 5, " foo ")); // Duplicate not a 457 // conflict. 458 Input.push_back(Replacement("fileA", 2, 6, " bar ")); 459 Input.push_back(Replacement("fileA", 7, 3, " moo ")); 460 461 std::vector<Range> Conflicts; 462 deduplicate(Input, Conflicts); 463 464 // One duplicate is removed and the remaining three items form one 465 // conflicted range. 466 ASSERT_EQ(3u, Input.size()); 467 ASSERT_EQ(1u, Conflicts.size()); 468 ASSERT_EQ(0u, Conflicts.front().getOffset()); 469 ASSERT_EQ(3u, Conflicts.front().getLength()); 470 } 471 { 472 std::vector<Replacement> Input; 473 474 // Expected sorted order is shown. It is the sorted order to which the 475 // returned conflict info refers to. 476 Input.push_back(Replacement("fileA", 0, 5, " foo ")); // 0 477 Input.push_back(Replacement("fileA", 5, 5, " bar ")); // 1 478 Input.push_back(Replacement("fileA", 6, 0, " bar ")); // 3 479 Input.push_back(Replacement("fileA", 5, 5, " moo ")); // 2 480 Input.push_back(Replacement("fileA", 7, 2, " bar ")); // 4 481 Input.push_back(Replacement("fileA", 15, 5, " golf ")); // 5 482 Input.push_back(Replacement("fileA", 16, 5, " bag ")); // 6 483 Input.push_back(Replacement("fileA", 10, 3, " club ")); // 7 484 485 // #3 is special in that it is completely contained by another conflicting 486 // Replacement. #4 ensures #3 hasn't messed up the conflicting range size. 487 488 std::vector<Range> Conflicts; 489 deduplicate(Input, Conflicts); 490 491 // No duplicates 492 ASSERT_EQ(8u, Input.size()); 493 ASSERT_EQ(2u, Conflicts.size()); 494 ASSERT_EQ(1u, Conflicts[0].getOffset()); 495 ASSERT_EQ(4u, Conflicts[0].getLength()); 496 ASSERT_EQ(6u, Conflicts[1].getOffset()); 497 ASSERT_EQ(2u, Conflicts[1].getLength()); 498 } 499 } 500 501 class MergeReplacementsTest : public ::testing::Test { 502 protected: 503 void mergeAndTestRewrite(StringRef Code, StringRef Intermediate, 504 StringRef Result, const Replacements &First, 505 const Replacements &Second) { 506 // These are mainly to verify the test itself and make it easier to read. 507 std::string AfterFirst = applyAllReplacements(Code, First); 508 std::string InSequenceRewrite = applyAllReplacements(AfterFirst, Second); 509 EXPECT_EQ(Intermediate, AfterFirst); 510 EXPECT_EQ(Result, InSequenceRewrite); 511 512 tooling::Replacements Merged = mergeReplacements(First, Second); 513 std::string MergedRewrite = applyAllReplacements(Code, Merged); 514 EXPECT_EQ(InSequenceRewrite, MergedRewrite); 515 if (InSequenceRewrite != MergedRewrite) 516 for (tooling::Replacement M : Merged) 517 llvm::errs() << M.getOffset() << " " << M.getLength() << " " 518 << M.getReplacementText() << "\n"; 519 } 520 void mergeAndTestRewrite(StringRef Code, const Replacements &First, 521 const Replacements &Second) { 522 std::string InSequenceRewrite = 523 applyAllReplacements(applyAllReplacements(Code, First), Second); 524 tooling::Replacements Merged = mergeReplacements(First, Second); 525 std::string MergedRewrite = applyAllReplacements(Code, Merged); 526 EXPECT_EQ(InSequenceRewrite, MergedRewrite); 527 if (InSequenceRewrite != MergedRewrite) 528 for (tooling::Replacement M : Merged) 529 llvm::errs() << M.getOffset() << " " << M.getLength() << " " 530 << M.getReplacementText() << "\n"; 531 } 532 }; 533 534 TEST_F(MergeReplacementsTest, Offsets) { 535 mergeAndTestRewrite("aaa", "aabab", "cacabab", 536 {{"", 2, 0, "b"}, {"", 3, 0, "b"}}, 537 {{"", 0, 0, "c"}, {"", 1, 0, "c"}}); 538 mergeAndTestRewrite("aaa", "babaa", "babacac", 539 {{"", 0, 0, "b"}, {"", 1, 0, "b"}}, 540 {{"", 4, 0, "c"}, {"", 5, 0, "c"}}); 541 mergeAndTestRewrite("aaaa", "aaa", "aac", {{"", 1, 1, ""}}, 542 {{"", 2, 1, "c"}}); 543 544 mergeAndTestRewrite("aa", "bbabba", "bbabcba", 545 {{"", 0, 0, "bb"}, {"", 1, 0, "bb"}}, {{"", 4, 0, "c"}}); 546 } 547 548 TEST_F(MergeReplacementsTest, Concatenations) { 549 // Basic concatenations. It is important to merge these into a single 550 // replacement to ensure the correct order. 551 EXPECT_EQ((Replacements{{"", 0, 0, "ab"}}), 552 mergeReplacements({{"", 0, 0, "a"}}, {{"", 1, 0, "b"}})); 553 EXPECT_EQ((Replacements{{"", 0, 0, "ba"}}), 554 mergeReplacements({{"", 0, 0, "a"}}, {{"", 0, 0, "b"}})); 555 mergeAndTestRewrite("", "a", "ab", {{"", 0, 0, "a"}}, {{"", 1, 0, "b"}}); 556 mergeAndTestRewrite("", "a", "ba", {{"", 0, 0, "a"}}, {{"", 0, 0, "b"}}); 557 } 558 559 TEST_F(MergeReplacementsTest, NotChangingLengths) { 560 mergeAndTestRewrite("aaaa", "abba", "acca", {{"", 1, 2, "bb"}}, 561 {{"", 1, 2, "cc"}}); 562 mergeAndTestRewrite("aaaa", "abba", "abcc", {{"", 1, 2, "bb"}}, 563 {{"", 2, 2, "cc"}}); 564 mergeAndTestRewrite("aaaa", "abba", "ccba", {{"", 1, 2, "bb"}}, 565 {{"", 0, 2, "cc"}}); 566 mergeAndTestRewrite("aaaaaa", "abbdda", "abccda", 567 {{"", 1, 2, "bb"}, {"", 3, 2, "dd"}}, {{"", 2, 2, "cc"}}); 568 } 569 570 TEST_F(MergeReplacementsTest, OverlappingRanges) { 571 mergeAndTestRewrite("aaa", "bbd", "bcbcd", 572 {{"", 0, 1, "bb"}, {"", 1, 2, "d"}}, 573 {{"", 1, 0, "c"}, {"", 2, 0, "c"}}); 574 575 mergeAndTestRewrite("aaaa", "aabbaa", "acccca", {{"", 2, 0, "bb"}}, 576 {{"", 1, 4, "cccc"}}); 577 mergeAndTestRewrite("aaaa", "aababa", "acccca", 578 {{"", 2, 0, "b"}, {"", 3, 0, "b"}}, {{"", 1, 4, "cccc"}}); 579 mergeAndTestRewrite("aaaaaa", "abbbba", "abba", {{"", 1, 4, "bbbb"}}, 580 {{"", 2, 2, ""}}); 581 mergeAndTestRewrite("aaaa", "aa", "cc", {{"", 1, 1, ""}, {"", 2, 1, ""}}, 582 {{"", 0, 2, "cc"}}); 583 mergeAndTestRewrite("aa", "abbba", "abcbcba", {{"", 1, 0, "bbb"}}, 584 {{"", 2, 0, "c"}, {"", 3, 0, "c"}}); 585 586 mergeAndTestRewrite("aaa", "abbab", "ccdd", 587 {{"", 0, 1, ""}, {"", 2, 0, "bb"}, {"", 3, 0, "b"}}, 588 {{"", 0, 2, "cc"}, {"", 2, 3, "dd"}}); 589 mergeAndTestRewrite("aa", "babbab", "ccdd", 590 {{"", 0, 0, "b"}, {"", 1, 0, "bb"}, {"", 2, 0, "b"}}, 591 {{"", 0, 3, "cc"}, {"", 3, 3, "dd"}}); 592 } 593 594 } // end namespace tooling 595 } // end namespace clang 596