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