go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/diff/main_test.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package diff 9 10 import ( 11 "bytes" 12 "fmt" 13 "strconv" 14 "strings" 15 "testing" 16 "time" 17 "unicode/utf8" 18 19 "go.charczuk.com/sdk/assert" 20 ) 21 22 func TestDiffCommonPrefix(t *testing.T) { 23 type TestCase struct { 24 Name string 25 26 Text1 string 27 Text2 string 28 29 Expected int 30 } 31 32 dmp := New() 33 34 for i, tc := range []TestCase{ 35 {"Null", "abc", "xyz", 0}, 36 {"Non-null", "1234abcdef", "1234xyz", 4}, 37 {"Whole", "1234", "1234xyz", 4}, 38 } { 39 actual := dmp.diffCommonPrefix(tc.Text1, tc.Text2) 40 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name)) 41 } 42 } 43 44 func TestCommonPrefixLength(t *testing.T) { 45 type TestCase struct { 46 Text1 string 47 Text2 string 48 49 Expected int 50 } 51 52 for i, tc := range []TestCase{ 53 {"abc", "xyz", 0}, 54 {"1234abcdef", "1234xyz", 4}, 55 {"1234", "1234xyz", 4}, 56 } { 57 actual := commonPrefixLength([]rune(tc.Text1), []rune(tc.Text2)) 58 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc)) 59 } 60 } 61 62 func TestDiffCommonSuffix(t *testing.T) { 63 type TestCase struct { 64 Name string 65 66 Text1 string 67 Text2 string 68 69 Expected int 70 } 71 72 dmp := New() 73 74 for i, tc := range []TestCase{ 75 {"Null", "abc", "xyz", 0}, 76 {"Non-null", "abcdef1234", "xyz1234", 4}, 77 {"Whole", "1234", "xyz1234", 4}, 78 } { 79 actual := dmp.diffCommonSuffix(tc.Text1, tc.Text2) 80 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name)) 81 } 82 } 83 84 func TestCommonSuffixLength(t *testing.T) { 85 type TestCase struct { 86 Text1 string 87 Text2 string 88 89 Expected int 90 } 91 92 for i, tc := range []TestCase{ 93 {"abc", "xyz", 0}, 94 {"abcdef1234", "xyz1234", 4}, 95 {"1234", "xyz1234", 4}, 96 {"123", "a3", 1}, 97 } { 98 actual := commonSuffixLength([]rune(tc.Text1), []rune(tc.Text2)) 99 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc)) 100 } 101 } 102 103 func TestDiffCommonOverlap(t *testing.T) { 104 type TestCase struct { 105 Name string 106 107 Text1 string 108 Text2 string 109 110 Expected int 111 } 112 113 dmp := New() 114 115 for i, tc := range []TestCase{ 116 {"Null", "", "abcd", 0}, 117 {"Whole", "abc", "abcd", 3}, 118 {"Null", "123456", "abcd", 0}, 119 {"Null", "123456xxx", "xxxabcd", 3}, 120 // Some overly clever languages (C#) may treat ligatures as equal to their component letters, e.g. U+FB01 == 'fi' 121 {"Unicode", "fi", "\ufb01i", 0}, 122 } { 123 actual := dmp.diffCommonOverlap(tc.Text1, tc.Text2) 124 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name)) 125 } 126 } 127 128 func TestDiffHalfMatch(t *testing.T) { 129 type TestCase struct { 130 Text1 string 131 Text2 string 132 133 Expected []string 134 } 135 136 dmp := New() 137 dmp.Timeout = 1 138 139 for i, tc := range []TestCase{ 140 // No match 141 {"1234567890", "abcdef", nil}, 142 {"12345", "23", nil}, 143 144 // Single Match 145 {"1234567890", "a345678z", []string{"12", "90", "a", "z", "345678"}}, 146 {"a345678z", "1234567890", []string{"a", "z", "12", "90", "345678"}}, 147 {"abc56789z", "1234567890", []string{"abc", "z", "1234", "0", "56789"}}, 148 {"a23456xyz", "1234567890", []string{"a", "xyz", "1", "7890", "23456"}}, 149 150 // Multiple Matches 151 {"121231234123451234123121", "a1234123451234z", []string{"12123", "123121", "a", "z", "1234123451234"}}, 152 {"x-=-=-=-=-=-=-=-=-=-=-=-=", "xx-=-=-=-=-=-=-=", []string{"", "-=-=-=-=-=", "x", "", "x-=-=-=-=-=-=-="}}, 153 {"-=-=-=-=-=-=-=-=-=-=-=-=y", "-=-=-=-=-=-=-=yy", []string{"-=-=-=-=-=", "", "", "y", "-=-=-=-=-=-=-=y"}}, 154 155 // Non-optimal halfmatch, ptimal diff would be -q+x=H-i+e=lloHe+Hu=llo-Hew+y not -qHillo+x=HelloHe-w+Hulloy 156 {"qHilloHelloHew", "xHelloHeHulloy", []string{"qHillo", "w", "x", "Hulloy", "HelloHe"}}, 157 } { 158 actual := dmp.DiffHalfMatch(tc.Text1, tc.Text2) 159 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc)) 160 } 161 162 dmp.Timeout = 0 163 164 for i, tc := range []TestCase{ 165 // Optimal no halfmatch 166 {"qHilloHelloHew", "xHelloHeHulloy", nil}, 167 } { 168 actual := dmp.DiffHalfMatch(tc.Text1, tc.Text2) 169 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc)) 170 } 171 } 172 173 func TestDiffBisectSplit(t *testing.T) { 174 type TestCase struct { 175 Text1 string 176 Text2 string 177 } 178 179 dmp := New() 180 181 for _, tc := range []TestCase{ 182 {"STUV\x05WX\x05YZ\x05[", "WĺĻļ\x05YZ\x05ĽľĿŀZ"}, 183 } { 184 diffs := dmp.diffBisectSplit([]rune(tc.Text1), 185 []rune(tc.Text2), 7, 6, time.Now().Add(time.Hour)) 186 187 for _, d := range diffs { 188 assert.ItsEqual(t, true, utf8.ValidString(d.Text)) 189 } 190 191 // TODO define the expected outcome 192 } 193 } 194 195 func TestDiffLinesToChars(t *testing.T) { 196 type TestCase struct { 197 Text1 string 198 Text2 string 199 200 ExpectedChars1 string 201 ExpectedChars2 string 202 ExpectedLines []string 203 } 204 205 dmp := New() 206 207 for i, tc := range []TestCase{ 208 {"", "alpha\r\nbeta\r\n\r\n\r\n", "", "1,2,3,3", []string{"", "alpha\r\n", "beta\r\n", "\r\n"}}, 209 {"a", "b", "1", "2", []string{"", "a", "b"}}, 210 // Omit final newline. 211 {"alpha\nbeta\nalpha", "", "1,2,3", "", []string{"", "alpha\n", "beta\n", "alpha"}}, 212 } { 213 actualChars1, actualChars2, actualLines := dmp.diffLinesToChars(tc.Text1, tc.Text2) 214 assert.ItsEqual(t, tc.ExpectedChars1, actualChars1, fmt.Sprintf("Test case #%d, %#v", i, tc)) 215 assert.ItsEqual(t, tc.ExpectedChars2, actualChars2, fmt.Sprintf("Test case #%d, %#v", i, tc)) 216 assert.ItsEqual(t, tc.ExpectedLines, actualLines, fmt.Sprintf("Test case #%d, %#v", i, tc)) 217 } 218 219 // More than 256 to reveal any 8-bit limitations. 220 n := 300 221 lineList := []string{ 222 "", // Account for the initial empty element of the lines array. 223 } 224 var charList []string 225 for x := 1; x < n+1; x++ { 226 lineList = append(lineList, strconv.Itoa(x)+"\n") 227 charList = append(charList, strconv.Itoa(x)) 228 } 229 lines := strings.Join(lineList, "") 230 chars := strings.Join(charList[:], ",") 231 assert.ItsEqual(t, n, len(strings.Split(chars, ","))) 232 233 actualChars1, actualChars2, actualLines := dmp.diffLinesToChars(lines, "") 234 assert.ItsEqual(t, chars, actualChars1) 235 assert.ItsEqual(t, "", actualChars2) 236 assert.ItsEqual(t, lineList, actualLines) 237 } 238 239 func TestDiffCharsToLines(t *testing.T) { 240 type TestCase struct { 241 Diffs []Diff 242 Lines []string 243 244 Expected []Diff 245 } 246 247 dmp := New() 248 249 for i, tc := range []TestCase{ 250 { 251 Diffs: []Diff{ 252 {DiffEqual, "1,2,1"}, 253 {DiffInsert, "2,1,2"}, 254 }, 255 Lines: []string{"", "alpha\n", "beta\n"}, 256 257 Expected: []Diff{ 258 {DiffEqual, "alpha\nbeta\nalpha\n"}, 259 {DiffInsert, "beta\nalpha\nbeta\n"}, 260 }, 261 }, 262 } { 263 actual := dmp.diffCharsToLines(tc.Diffs, tc.Lines) 264 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc)) 265 } 266 267 // More than 256 to reveal any 8-bit limitations. 268 n := 300 269 lineList := []string{ 270 "", // Account for the initial empty element of the lines array. 271 } 272 charList := []string{} 273 for x := 1; x <= n; x++ { 274 lineList = append(lineList, strconv.Itoa(x)+"\n") 275 charList = append(charList, strconv.Itoa(x)) 276 } 277 assert.ItsEqual(t, n, len(charList)) 278 chars := strings.Join(charList[:], ",") 279 280 actual := dmp.diffCharsToLines([]Diff{{DiffDelete, chars}}, lineList) 281 assert.ItsEqual(t, []Diff{{DiffDelete, strings.Join(lineList, "")}}, actual) 282 } 283 284 func TestDiffCleanupMerge(t *testing.T) { 285 type TestCase struct { 286 Name string 287 288 Diffs []Diff 289 290 Expected []Diff 291 } 292 293 dmp := New() 294 295 for i, tc := range []TestCase{ 296 { 297 "Null case", 298 []Diff{}, 299 []Diff{}, 300 }, 301 { 302 "No Diff case", 303 []Diff{{DiffEqual, "a"}, {DiffDelete, "b"}, {DiffInsert, "c"}}, 304 []Diff{{DiffEqual, "a"}, {DiffDelete, "b"}, {DiffInsert, "c"}}, 305 }, 306 { 307 "Merge equalities", 308 []Diff{{DiffEqual, "a"}, {DiffEqual, "b"}, {DiffEqual, "c"}}, 309 []Diff{{DiffEqual, "abc"}}, 310 }, 311 { 312 "Merge deletions", 313 []Diff{{DiffDelete, "a"}, {DiffDelete, "b"}, {DiffDelete, "c"}}, 314 []Diff{{DiffDelete, "abc"}}, 315 }, 316 { 317 "Merge insertions", 318 []Diff{{DiffInsert, "a"}, {DiffInsert, "b"}, {DiffInsert, "c"}}, 319 []Diff{{DiffInsert, "abc"}}, 320 }, 321 { 322 "Merge interweave", 323 []Diff{{DiffDelete, "a"}, {DiffInsert, "b"}, {DiffDelete, "c"}, {DiffInsert, "d"}, {DiffEqual, "e"}, {DiffEqual, "f"}}, 324 []Diff{{DiffDelete, "ac"}, {DiffInsert, "bd"}, {DiffEqual, "ef"}}, 325 }, 326 { 327 "Prefix and suffix detection", 328 []Diff{{DiffDelete, "a"}, {DiffInsert, "abc"}, {DiffDelete, "dc"}}, 329 []Diff{{DiffEqual, "a"}, {DiffDelete, "d"}, {DiffInsert, "b"}, {DiffEqual, "c"}}, 330 }, 331 { 332 "Prefix and suffix detection with equalities", 333 []Diff{{DiffEqual, "x"}, {DiffDelete, "a"}, {DiffInsert, "abc"}, {DiffDelete, "dc"}, {DiffEqual, "y"}}, 334 []Diff{{DiffEqual, "xa"}, {DiffDelete, "d"}, {DiffInsert, "b"}, {DiffEqual, "cy"}}, 335 }, 336 { 337 "Same test as above but with unicode (\u0101 will appear in diffs with at least 257 unique lines)", 338 []Diff{{DiffEqual, "x"}, {DiffDelete, "\u0101"}, {DiffInsert, "\u0101bc"}, {DiffDelete, "dc"}, {DiffEqual, "y"}}, 339 []Diff{{DiffEqual, "x\u0101"}, {DiffDelete, "d"}, {DiffInsert, "b"}, {DiffEqual, "cy"}}, 340 }, 341 { 342 "Slide edit left", 343 []Diff{{DiffEqual, "a"}, {DiffInsert, "ba"}, {DiffEqual, "c"}}, 344 []Diff{{DiffInsert, "ab"}, {DiffEqual, "ac"}}, 345 }, 346 { 347 "Slide edit right", 348 []Diff{{DiffEqual, "c"}, {DiffInsert, "ab"}, {DiffEqual, "a"}}, 349 []Diff{{DiffEqual, "ca"}, {DiffInsert, "ba"}}, 350 }, 351 { 352 "Slide edit left recursive", 353 []Diff{{DiffEqual, "a"}, {DiffDelete, "b"}, {DiffEqual, "c"}, {DiffDelete, "ac"}, {DiffEqual, "x"}}, 354 []Diff{{DiffDelete, "abc"}, {DiffEqual, "acx"}}, 355 }, 356 { 357 "Slide edit right recursive", 358 []Diff{{DiffEqual, "x"}, {DiffDelete, "ca"}, {DiffEqual, "c"}, {DiffDelete, "b"}, {DiffEqual, "a"}}, 359 []Diff{{DiffEqual, "xca"}, {DiffDelete, "cba"}}, 360 }, 361 } { 362 actual := dmp.diffCleanupMerge(tc.Diffs) 363 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name)) 364 } 365 } 366 367 func TestDiffCleanupSemanticLossless(t *testing.T) { 368 type TestCase struct { 369 Name string 370 371 Diffs []Diff 372 373 Expected []Diff 374 } 375 376 dmp := New() 377 378 for i, tc := range []TestCase{ 379 { 380 "Null case", 381 []Diff{}, 382 []Diff{}, 383 }, 384 { 385 "Blank lines", 386 []Diff{ 387 {DiffEqual, "AAA\r\n\r\nBBB"}, 388 {DiffInsert, "\r\nDDD\r\n\r\nBBB"}, 389 {DiffEqual, "\r\nEEE"}, 390 }, 391 []Diff{ 392 {DiffEqual, "AAA\r\n\r\n"}, 393 {DiffInsert, "BBB\r\nDDD\r\n\r\n"}, 394 {DiffEqual, "BBB\r\nEEE"}, 395 }, 396 }, 397 { 398 "Line boundaries", 399 []Diff{ 400 {DiffEqual, "AAA\r\nBBB"}, 401 {DiffInsert, " DDD\r\nBBB"}, 402 {DiffEqual, " EEE"}, 403 }, 404 []Diff{ 405 {DiffEqual, "AAA\r\n"}, 406 {DiffInsert, "BBB DDD\r\n"}, 407 {DiffEqual, "BBB EEE"}, 408 }, 409 }, 410 { 411 "Word boundaries", 412 []Diff{ 413 {DiffEqual, "The c"}, 414 {DiffInsert, "ow and the c"}, 415 {DiffEqual, "at."}, 416 }, 417 []Diff{ 418 {DiffEqual, "The "}, 419 {DiffInsert, "cow and the "}, 420 {DiffEqual, "cat."}, 421 }, 422 }, 423 { 424 "Alphanumeric boundaries", 425 []Diff{ 426 {DiffEqual, "The-c"}, 427 {DiffInsert, "ow-and-the-c"}, 428 {DiffEqual, "at."}, 429 }, 430 []Diff{ 431 {DiffEqual, "The-"}, 432 {DiffInsert, "cow-and-the-"}, 433 {DiffEqual, "cat."}, 434 }, 435 }, 436 { 437 "Hitting the start", 438 []Diff{ 439 {DiffEqual, "a"}, 440 {DiffDelete, "a"}, 441 {DiffEqual, "ax"}, 442 }, 443 []Diff{ 444 {DiffDelete, "a"}, 445 {DiffEqual, "aax"}, 446 }, 447 }, 448 { 449 "Hitting the end", 450 []Diff{ 451 {DiffEqual, "xa"}, 452 {DiffDelete, "a"}, 453 {DiffEqual, "a"}, 454 }, 455 []Diff{ 456 {DiffEqual, "xaa"}, 457 {DiffDelete, "a"}, 458 }, 459 }, 460 { 461 "Sentence boundaries", 462 []Diff{ 463 {DiffEqual, "The xxx. The "}, 464 {DiffInsert, "zzz. The "}, 465 {DiffEqual, "yyy."}, 466 }, 467 []Diff{ 468 {DiffEqual, "The xxx."}, 469 {DiffInsert, " The zzz."}, 470 {DiffEqual, " The yyy."}, 471 }, 472 }, 473 { 474 "UTF-8 strings", 475 []Diff{ 476 {DiffEqual, "The ♕. The "}, 477 {DiffInsert, "♔. The "}, 478 {DiffEqual, "♖."}, 479 }, 480 []Diff{ 481 {DiffEqual, "The ♕."}, 482 {DiffInsert, " The ♔."}, 483 {DiffEqual, " The ♖."}, 484 }, 485 }, 486 { 487 "Rune boundaries", 488 []Diff{ 489 {DiffEqual, "♕♕"}, 490 {DiffInsert, "♔♔"}, 491 {DiffEqual, "♖♖"}, 492 }, 493 []Diff{ 494 {DiffEqual, "♕♕"}, 495 {DiffInsert, "♔♔"}, 496 {DiffEqual, "♖♖"}, 497 }, 498 }, 499 } { 500 actual := dmp.diffCleanupSemanticLossless(tc.Diffs) 501 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name)) 502 } 503 } 504 505 func TestDiffCleanupSemantic(t *testing.T) { 506 type TestCase struct { 507 Name string 508 509 Diffs []Diff 510 511 Expected []Diff 512 } 513 514 dmp := New() 515 516 for i, tc := range []TestCase{ 517 { 518 "Null case", 519 []Diff{}, 520 []Diff{}, 521 }, 522 { 523 "No elimination #1", 524 []Diff{ 525 {DiffDelete, "ab"}, 526 {DiffInsert, "cd"}, 527 {DiffEqual, "12"}, 528 {DiffDelete, "e"}, 529 }, 530 []Diff{ 531 {DiffDelete, "ab"}, 532 {DiffInsert, "cd"}, 533 {DiffEqual, "12"}, 534 {DiffDelete, "e"}, 535 }, 536 }, 537 { 538 "No elimination #2", 539 []Diff{ 540 {DiffDelete, "abc"}, 541 {DiffInsert, "ABC"}, 542 {DiffEqual, "1234"}, 543 {DiffDelete, "wxyz"}, 544 }, 545 []Diff{ 546 {DiffDelete, "abc"}, 547 {DiffInsert, "ABC"}, 548 {DiffEqual, "1234"}, 549 {DiffDelete, "wxyz"}, 550 }, 551 }, 552 { 553 "No elimination #3", 554 []Diff{ 555 {DiffEqual, "2016-09-01T03:07:1"}, 556 {DiffInsert, "5.15"}, 557 {DiffEqual, "4"}, 558 {DiffDelete, "."}, 559 {DiffEqual, "80"}, 560 {DiffInsert, "0"}, 561 {DiffEqual, "78"}, 562 {DiffDelete, "3074"}, 563 {DiffEqual, "1Z"}, 564 }, 565 []Diff{ 566 {DiffEqual, "2016-09-01T03:07:1"}, 567 {DiffInsert, "5.15"}, 568 {DiffEqual, "4"}, 569 {DiffDelete, "."}, 570 {DiffEqual, "80"}, 571 {DiffInsert, "0"}, 572 {DiffEqual, "78"}, 573 {DiffDelete, "3074"}, 574 {DiffEqual, "1Z"}, 575 }, 576 }, 577 { 578 "Simple elimination", 579 []Diff{ 580 {DiffDelete, "a"}, 581 {DiffEqual, "b"}, 582 {DiffDelete, "c"}, 583 }, 584 []Diff{ 585 {DiffDelete, "abc"}, 586 {DiffInsert, "b"}, 587 }, 588 }, 589 { 590 "Backpass elimination", 591 []Diff{ 592 {DiffDelete, "ab"}, 593 {DiffEqual, "cd"}, 594 {DiffDelete, "e"}, 595 {DiffEqual, "f"}, 596 {DiffInsert, "g"}, 597 }, 598 []Diff{ 599 {DiffDelete, "abcdef"}, 600 {DiffInsert, "cdfg"}, 601 }, 602 }, 603 { 604 "Multiple eliminations", 605 []Diff{ 606 {DiffInsert, "1"}, 607 {DiffEqual, "A"}, 608 {DiffDelete, "B"}, 609 {DiffInsert, "2"}, 610 {DiffEqual, "_"}, 611 {DiffInsert, "1"}, 612 {DiffEqual, "A"}, 613 {DiffDelete, "B"}, 614 {DiffInsert, "2"}, 615 }, 616 []Diff{ 617 {DiffDelete, "AB_AB"}, 618 {DiffInsert, "1A2_1A2"}, 619 }, 620 }, 621 { 622 "Word boundaries", 623 []Diff{ 624 {DiffEqual, "The c"}, 625 {DiffDelete, "ow and the c"}, 626 {DiffEqual, "at."}, 627 }, 628 []Diff{ 629 {DiffEqual, "The "}, 630 {DiffDelete, "cow and the "}, 631 {DiffEqual, "cat."}, 632 }, 633 }, 634 { 635 "No overlap elimination", 636 []Diff{ 637 {DiffDelete, "abcxx"}, 638 {DiffInsert, "xxdef"}, 639 }, 640 []Diff{ 641 {DiffDelete, "abcxx"}, 642 {DiffInsert, "xxdef"}, 643 }, 644 }, 645 { 646 "Overlap elimination", 647 []Diff{ 648 {DiffDelete, "abcxxx"}, 649 {DiffInsert, "xxxdef"}, 650 }, 651 []Diff{ 652 {DiffDelete, "abc"}, 653 {DiffEqual, "xxx"}, 654 {DiffInsert, "def"}, 655 }, 656 }, 657 { 658 "Reverse overlap elimination", 659 []Diff{ 660 {DiffDelete, "xxxabc"}, 661 {DiffInsert, "defxxx"}, 662 }, 663 []Diff{ 664 {DiffInsert, "def"}, 665 {DiffEqual, "xxx"}, 666 {DiffDelete, "abc"}, 667 }, 668 }, 669 { 670 "Two overlap eliminations", 671 []Diff{ 672 {DiffDelete, "abcd1212"}, 673 {DiffInsert, "1212efghi"}, 674 {DiffEqual, "----"}, 675 {DiffDelete, "A3"}, 676 {DiffInsert, "3BC"}, 677 }, 678 []Diff{ 679 {DiffDelete, "abcd"}, 680 {DiffEqual, "1212"}, 681 {DiffInsert, "efghi"}, 682 {DiffEqual, "----"}, 683 {DiffDelete, "A"}, 684 {DiffEqual, "3"}, 685 {DiffInsert, "BC"}, 686 }, 687 }, 688 { 689 "Test case for adapting DiffCleanupSemantic to be equal to the Python version #19", 690 []Diff{ 691 {DiffEqual, "James McCarthy "}, 692 {DiffDelete, "close to "}, 693 {DiffEqual, "sign"}, 694 {DiffDelete, "ing"}, 695 {DiffInsert, "s"}, 696 {DiffEqual, " new "}, 697 {DiffDelete, "E"}, 698 {DiffInsert, "fi"}, 699 {DiffEqual, "ve"}, 700 {DiffInsert, "-yea"}, 701 {DiffEqual, "r"}, 702 {DiffDelete, "ton"}, 703 {DiffEqual, " deal"}, 704 {DiffInsert, " at Everton"}, 705 }, 706 []Diff{ 707 {DiffEqual, "James McCarthy "}, 708 {DiffDelete, "close to "}, 709 {DiffEqual, "sign"}, 710 {DiffDelete, "ing"}, 711 {DiffInsert, "s"}, 712 {DiffEqual, " new "}, 713 {DiffInsert, "five-year deal at "}, 714 {DiffEqual, "Everton"}, 715 {DiffDelete, " deal"}, 716 }, 717 }, 718 { 719 "Taken from python / CPP library", 720 []Diff{ 721 {DiffInsert, "星球大戰:新的希望 "}, 722 {DiffEqual, "star wars: "}, 723 {DiffDelete, "episodio iv - un"}, 724 {DiffEqual, "a n"}, 725 {DiffDelete, "u"}, 726 {DiffEqual, "e"}, 727 {DiffDelete, "va"}, 728 {DiffInsert, "w"}, 729 {DiffEqual, " "}, 730 {DiffDelete, "es"}, 731 {DiffInsert, "ho"}, 732 {DiffEqual, "pe"}, 733 {DiffDelete, "ranza"}, 734 }, 735 []Diff{ 736 {DiffInsert, "星球大戰:新的希望 "}, 737 {DiffEqual, "star wars: "}, 738 {DiffDelete, "episodio iv - una nueva esperanza"}, 739 {DiffInsert, "a new hope"}, 740 }, 741 }, 742 { 743 "panic", 744 []Diff{ 745 {DiffInsert, "킬러 인 "}, 746 {DiffEqual, "리커버리"}, 747 {DiffDelete, " 보이즈"}, 748 }, 749 []Diff{ 750 {DiffInsert, "킬러 인 "}, 751 {DiffEqual, "리커버리"}, 752 {DiffDelete, " 보이즈"}, 753 }, 754 }, 755 } { 756 actual := dmp.diffCleanupSemantic(tc.Diffs) 757 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name)) 758 } 759 } 760 761 func TestDiffCleanupEfficiency(t *testing.T) { 762 type TestCase struct { 763 Name string 764 765 Diffs []Diff 766 767 Expected []Diff 768 } 769 770 dmp := New() 771 dmp.EditCost = 4 772 773 for i, tc := range []TestCase{ 774 { 775 "Null case", 776 []Diff{}, 777 []Diff{}, 778 }, 779 { 780 "No elimination", 781 []Diff{ 782 {DiffDelete, "ab"}, 783 {DiffInsert, "12"}, 784 {DiffEqual, "wxyz"}, 785 {DiffDelete, "cd"}, 786 {DiffInsert, "34"}, 787 }, 788 []Diff{ 789 {DiffDelete, "ab"}, 790 {DiffInsert, "12"}, 791 {DiffEqual, "wxyz"}, 792 {DiffDelete, "cd"}, 793 {DiffInsert, "34"}, 794 }, 795 }, 796 { 797 "Four-edit elimination", 798 []Diff{ 799 {DiffDelete, "ab"}, 800 {DiffInsert, "12"}, 801 {DiffEqual, "xyz"}, 802 {DiffDelete, "cd"}, 803 {DiffInsert, "34"}, 804 }, 805 []Diff{ 806 {DiffDelete, "abxyzcd"}, 807 {DiffInsert, "12xyz34"}, 808 }, 809 }, 810 { 811 "Three-edit elimination", 812 []Diff{ 813 {DiffInsert, "12"}, 814 {DiffEqual, "x"}, 815 {DiffDelete, "cd"}, 816 {DiffInsert, "34"}, 817 }, 818 []Diff{ 819 {DiffDelete, "xcd"}, 820 {DiffInsert, "12x34"}, 821 }, 822 }, 823 { 824 "Backpass elimination", 825 []Diff{ 826 {DiffDelete, "ab"}, 827 {DiffInsert, "12"}, 828 {DiffEqual, "xy"}, 829 {DiffInsert, "34"}, 830 {DiffEqual, "z"}, 831 {DiffDelete, "cd"}, 832 {DiffInsert, "56"}, 833 }, 834 []Diff{ 835 {DiffDelete, "abxyzcd"}, 836 {DiffInsert, "12xy34z56"}, 837 }, 838 }, 839 } { 840 actual := dmp.diffCleanupEfficiency(tc.Diffs) 841 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name)) 842 } 843 844 dmp.EditCost = 5 845 846 for i, tc := range []TestCase{ 847 { 848 "High cost elimination", 849 []Diff{ 850 {DiffDelete, "ab"}, 851 {DiffInsert, "12"}, 852 {DiffEqual, "wxyz"}, 853 {DiffDelete, "cd"}, 854 {DiffInsert, "34"}, 855 }, 856 []Diff{ 857 {DiffDelete, "abwxyzcd"}, 858 {DiffInsert, "12wxyz34"}, 859 }, 860 }, 861 } { 862 actual := dmp.diffCleanupEfficiency(tc.Diffs) 863 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name)) 864 } 865 } 866 867 func Test_PrettyHTML(t *testing.T) { 868 type TestCase struct { 869 Diffs []Diff 870 871 Expected string 872 } 873 874 for i, tc := range []TestCase{ 875 { 876 Diffs: []Diff{ 877 {DiffEqual, "a\n"}, 878 {DiffDelete, "<B>b</B>"}, 879 {DiffInsert, "c&d"}, 880 }, 881 882 Expected: "<span>a¶<br></span><del style=\"background:#ffe6e6;\"><B>b</B></del><ins style=\"background:#e6ffe6;\">c&d</ins>", 883 }, 884 } { 885 actual := PrettyHTML(tc.Diffs) 886 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc)) 887 } 888 } 889 890 func Test_PrettyText(t *testing.T) { 891 type TestCase struct { 892 Diffs []Diff 893 894 Expected string 895 } 896 897 for i, tc := range []TestCase{ 898 { 899 Diffs: []Diff{ 900 {DiffEqual, "a\n"}, 901 {DiffDelete, "<B>b</B>"}, 902 {DiffInsert, "c&d"}, 903 }, 904 905 Expected: "a\n\x1b[31m<B>b</B>\x1b[0m\x1b[32mc&d\x1b[0m", 906 }, 907 } { 908 actual := PrettyText(tc.Diffs) 909 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc)) 910 } 911 } 912 913 func Test_Text1_2(t *testing.T) { 914 type TestCase struct { 915 Diffs []Diff 916 917 ExpectedText1 string 918 ExpectedText2 string 919 } 920 921 for i, tc := range []TestCase{ 922 { 923 Diffs: []Diff{ 924 {DiffEqual, "jump"}, 925 {DiffDelete, "s"}, 926 {DiffInsert, "ed"}, 927 {DiffEqual, " over "}, 928 {DiffDelete, "the"}, 929 {DiffInsert, "a"}, 930 {DiffEqual, " lazy"}, 931 }, 932 933 ExpectedText1: "jumps over the lazy", 934 ExpectedText2: "jumped over a lazy", 935 }, 936 } { 937 actualText1 := Text1(tc.Diffs) 938 assert.ItsEqual(t, tc.ExpectedText1, actualText1, fmt.Sprintf("Test case #%d, %#v", i, tc)) 939 940 actualText2 := Text2(tc.Diffs) 941 assert.ItsEqual(t, tc.ExpectedText2, actualText2, fmt.Sprintf("Test case #%d, %#v", i, tc)) 942 } 943 } 944 945 func TestDiffDelta(t *testing.T) { 946 type TestCase struct { 947 Name string 948 949 Text string 950 Delta string 951 952 ErrorMessagePrefix string 953 } 954 955 for i, tc := range []TestCase{ 956 {"Delta shorter than text", "jumps over the lazyx", "=4\t-1\t+ed\t=6\t-3\t+a\t=5\t+old dog", "delta length (19) is different from source text length (20)"}, 957 {"Delta longer than text", "umps over the lazy", "=4\t-1\t+ed\t=6\t-3\t+a\t=5\t+old dog", "delta length (19) is different from source text length (18)"}, 958 {"Invalid URL escaping", "", "+%c3%xy", "invalid URL escape \"%xy\""}, 959 {"Invalid UTF-8 sequence", "", "+%c3xy", "invalid UTF-8 token: \"\\xc3xy\""}, 960 {"Invalid diff operation", "", "a", "Invalid diff operation in DiffFromDelta: a"}, 961 {"Invalid diff syntax", "", "-", "strconv.ParseInt: parsing \"\": invalid syntax"}, 962 {"Negative number in delta", "", "--1", "Negative number in DiffFromDelta: -1"}, 963 {"Empty case", "", "", ""}, 964 } { 965 diffs, err := FromDelta(tc.Text, tc.Delta) 966 msg := fmt.Sprintf("Test case #%d, %s", i, tc.Name) 967 if tc.ErrorMessagePrefix == "" { 968 assert.ItsNil(t, err, msg) 969 assert.ItsNil(t, diffs, msg) 970 } else { 971 e := err.Error() 972 if strings.HasPrefix(e, tc.ErrorMessagePrefix) { 973 e = tc.ErrorMessagePrefix 974 } 975 assert.ItsNil(t, diffs, msg) 976 assert.ItsEqual(t, tc.ErrorMessagePrefix, e, msg) 977 } 978 } 979 980 // Convert a diff into delta string. 981 diffs := []Diff{ 982 {DiffEqual, "jump"}, 983 {DiffDelete, "s"}, 984 {DiffInsert, "ed"}, 985 {DiffEqual, " over "}, 986 {DiffDelete, "the"}, 987 {DiffInsert, "a"}, 988 {DiffEqual, " lazy"}, 989 {DiffInsert, "old dog"}, 990 } 991 text1 := Text1(diffs) 992 assert.ItsEqual(t, "jumps over the lazy", text1) 993 994 delta := ToDelta(diffs) 995 assert.ItsEqual(t, "=4\t-1\t+ed\t=6\t-3\t+a\t=5\t+old dog", delta) 996 997 // Convert delta string into a diff. 998 deltaDiffs, err := FromDelta(text1, delta) 999 assert.ItsNil(t, err) 1000 assert.ItsEqual(t, diffs, deltaDiffs) 1001 1002 // Test deltas with special characters. 1003 diffs = []Diff{ 1004 {DiffEqual, "\u0680 \x00 \t %"}, 1005 {DiffDelete, "\u0681 \x01 \n ^"}, 1006 {DiffInsert, "\u0682 \x02 \\ |"}, 1007 } 1008 text1 = Text1(diffs) 1009 assert.ItsEqual(t, "\u0680 \x00 \t %\u0681 \x01 \n ^", text1) 1010 1011 // Lowercase, due to UrlEncode uses lower. 1012 delta = ToDelta(diffs) 1013 assert.ItsEqual(t, "=7\t-7\t+%DA%82 %02 %5C %7C", delta) 1014 1015 deltaDiffs, err = FromDelta(text1, delta) 1016 assert.ItsEqual(t, diffs, deltaDiffs) 1017 assert.ItsNil(t, err) 1018 1019 // Verify pool of unchanged characters. 1020 diffs = []Diff{ 1021 {DiffInsert, "A-Z a-z 0-9 - _ . ! ~ * ' ( ) ; / ? : @ & = + $ , # "}, 1022 } 1023 1024 delta = ToDelta(diffs) 1025 assert.ItsEqual(t, "+A-Z a-z 0-9 - _ . ! ~ * ' ( ) ; / ? : @ & = + $ , # ", delta, "Unchanged characters.") 1026 1027 // Convert delta string into a diff. 1028 deltaDiffs, err = FromDelta("", delta) 1029 assert.ItsEqual(t, diffs, deltaDiffs) 1030 assert.ItsNil(t, err) 1031 } 1032 1033 func TestDiffXIndex(t *testing.T) { 1034 type TestCase struct { 1035 Name string 1036 1037 Diffs []Diff 1038 Location int 1039 1040 Expected int 1041 } 1042 1043 dmp := New() 1044 1045 for i, tc := range []TestCase{ 1046 {"Translation on equality", []Diff{{DiffDelete, "a"}, {DiffInsert, "1234"}, {DiffEqual, "xyz"}}, 2, 5}, 1047 {"Translation on deletion", []Diff{{DiffEqual, "a"}, {DiffDelete, "1234"}, {DiffEqual, "xyz"}}, 3, 1}, 1048 } { 1049 actual := dmp.diffXIndex(tc.Diffs, tc.Location) 1050 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name)) 1051 } 1052 } 1053 1054 func TestDiffLevenshtein(t *testing.T) { 1055 type TestCase struct { 1056 Name string 1057 1058 Diffs []Diff 1059 1060 Expected int 1061 } 1062 1063 for i, tc := range []TestCase{ 1064 {"Levenshtein with trailing equality", []Diff{{DiffDelete, "абв"}, {DiffInsert, "1234"}, {DiffEqual, "эюя"}}, 4}, 1065 {"Levenshtein with leading equality", []Diff{{DiffEqual, "эюя"}, {DiffDelete, "абв"}, {DiffInsert, "1234"}}, 4}, 1066 {"Levenshtein with middle equality", []Diff{{DiffDelete, "абв"}, {DiffEqual, "эюя"}, {DiffInsert, "1234"}}, 7}, 1067 } { 1068 actual := Levenshtein(tc.Diffs) 1069 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name)) 1070 } 1071 } 1072 1073 func TestDiffBisect(t *testing.T) { 1074 type TestCase struct { 1075 Name string 1076 1077 Time time.Time 1078 1079 Expected []Diff 1080 } 1081 1082 dmp := New() 1083 1084 for i, tc := range []TestCase{ 1085 { 1086 Name: "normal", 1087 Time: time.Date(9999, time.December, 31, 23, 59, 59, 59, time.UTC), 1088 1089 Expected: []Diff{ 1090 {DiffDelete, "c"}, 1091 {DiffInsert, "m"}, 1092 {DiffEqual, "a"}, 1093 {DiffDelete, "t"}, 1094 {DiffInsert, "p"}, 1095 }, 1096 }, 1097 { 1098 Name: "Negative deadlines count as having infinite time", 1099 Time: time.Date(0001, time.January, 01, 00, 00, 00, 00, time.UTC), 1100 1101 Expected: []Diff{ 1102 {DiffDelete, "c"}, 1103 {DiffInsert, "m"}, 1104 {DiffEqual, "a"}, 1105 {DiffDelete, "t"}, 1106 {DiffInsert, "p"}, 1107 }, 1108 }, 1109 { 1110 Name: "Timeout", 1111 Time: time.Now().Add(time.Nanosecond), 1112 1113 Expected: []Diff{ 1114 {DiffDelete, "cat"}, 1115 {DiffInsert, "map"}, 1116 }, 1117 }, 1118 } { 1119 actual := dmp.diffBisect("cat", "map", tc.Time) 1120 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name)) 1121 } 1122 1123 // Test for invalid UTF-8 sequences 1124 assert.ItsEqual(t, []Diff{ 1125 {DiffEqual, "��"}, 1126 }, dmp.diffBisect("\xe0\xe5", "\xe0\xe5", time.Now().Add(time.Minute))) 1127 } 1128 1129 func TestDiff(t *testing.T) { 1130 type TestCase struct { 1131 Text1 string 1132 Text2 string 1133 1134 Expected []Diff 1135 } 1136 1137 dmp := New() 1138 1139 // Perform a trivial diff. 1140 for i, tc := range []TestCase{ 1141 { 1142 "", 1143 "", 1144 nil, 1145 }, 1146 { 1147 "abc", 1148 "abc", 1149 []Diff{{DiffEqual, "abc"}}, 1150 }, 1151 { 1152 "abc", 1153 "ab123c", 1154 []Diff{{DiffEqual, "ab"}, {DiffInsert, "123"}, {DiffEqual, "c"}}, 1155 }, 1156 { 1157 "a123bc", 1158 "abc", 1159 []Diff{{DiffEqual, "a"}, {DiffDelete, "123"}, {DiffEqual, "bc"}}, 1160 }, 1161 { 1162 "abc", 1163 "a123b456c", 1164 []Diff{{DiffEqual, "a"}, {DiffInsert, "123"}, {DiffEqual, "b"}, {DiffInsert, "456"}, {DiffEqual, "c"}}, 1165 }, 1166 { 1167 "a123b456c", 1168 "abc", 1169 []Diff{{DiffEqual, "a"}, {DiffDelete, "123"}, {DiffEqual, "b"}, {DiffDelete, "456"}, {DiffEqual, "c"}}, 1170 }, 1171 } { 1172 actual := dmp.Diff(tc.Text1, tc.Text2, false) 1173 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc)) 1174 } 1175 1176 // Perform a real diff and switch off the timeout. 1177 dmp.Timeout = 0 1178 1179 for i, tc := range []TestCase{ 1180 { 1181 "a", 1182 "b", 1183 []Diff{{DiffDelete, "a"}, {DiffInsert, "b"}}, 1184 }, 1185 { 1186 "Apples are a fruit.", 1187 "Bananas are also fruit.", 1188 []Diff{ 1189 {DiffDelete, "Apple"}, 1190 {DiffInsert, "Banana"}, 1191 {DiffEqual, "s are a"}, 1192 {DiffInsert, "lso"}, 1193 {DiffEqual, " fruit."}, 1194 }, 1195 }, 1196 { 1197 "ax\t", 1198 "\u0680x\u0000", 1199 []Diff{ 1200 {DiffDelete, "a"}, 1201 {DiffInsert, "\u0680"}, 1202 {DiffEqual, "x"}, 1203 {DiffDelete, "\t"}, 1204 {DiffInsert, "\u0000"}, 1205 }, 1206 }, 1207 { 1208 "1ayb2", 1209 "abxab", 1210 []Diff{ 1211 {DiffDelete, "1"}, 1212 {DiffEqual, "a"}, 1213 {DiffDelete, "y"}, 1214 {DiffEqual, "b"}, 1215 {DiffDelete, "2"}, 1216 {DiffInsert, "xab"}, 1217 }, 1218 }, 1219 { 1220 "abcy", 1221 "xaxcxabc", 1222 []Diff{ 1223 {DiffInsert, "xaxcx"}, 1224 {DiffEqual, "abc"}, {DiffDelete, "y"}, 1225 }, 1226 }, 1227 { 1228 "ABCDa=bcd=efghijklmnopqrsEFGHIJKLMNOefg", 1229 "a-bcd-efghijklmnopqrs", 1230 []Diff{ 1231 {DiffDelete, "ABCD"}, 1232 {DiffEqual, "a"}, 1233 {DiffDelete, "="}, 1234 {DiffInsert, "-"}, 1235 {DiffEqual, "bcd"}, 1236 {DiffDelete, "="}, 1237 {DiffInsert, "-"}, 1238 {DiffEqual, "efghijklmnopqrs"}, 1239 {DiffDelete, "EFGHIJKLMNOefg"}, 1240 }, 1241 }, 1242 { 1243 "a [[Pennsylvania]] and [[New", 1244 " and [[Pennsylvania]]", 1245 []Diff{ 1246 {DiffInsert, " "}, 1247 {DiffEqual, "a"}, 1248 {DiffInsert, "nd"}, 1249 {DiffEqual, " [[Pennsylvania]]"}, 1250 {DiffDelete, " and [[New"}, 1251 }, 1252 }, 1253 } { 1254 actual := dmp.Diff(tc.Text1, tc.Text2, false) 1255 assert.ItsEqual(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc)) 1256 } 1257 1258 // Test for invalid UTF-8 sequences 1259 assert.ItsEqual(t, []Diff{ 1260 {DiffDelete, "��"}, 1261 }, dmp.Diff("\xe0\xe5", "", false)) 1262 } 1263 1264 func TestDiffMainWithTimeout(t *testing.T) { 1265 dmp := New() 1266 dmp.Timeout = 200 * time.Millisecond 1267 1268 a := "`Twas brillig, and the slithy toves\nDid gyre and gimble in the wabe:\nAll mimsy were the borogoves,\nAnd the mome raths outgrabe.\n" 1269 b := "I am the very model of a modern major general,\nI've information vegetable, animal, and mineral,\nI know the kings of England, and I quote the fights historical,\nFrom Marathon to Waterloo, in order categorical.\n" 1270 // Increase the text lengths by 1024 times to ensure a timeout. 1271 for x := 0; x < 13; x++ { 1272 a = a + a 1273 b = b + b 1274 } 1275 1276 startTime := time.Now() 1277 dmp.Diff(a, b, true) 1278 endTime := time.Now() 1279 1280 delta := endTime.Sub(startTime) 1281 1282 // Test that we took at least the timeout period. 1283 assert.ItsEqual(t, true, delta >= dmp.Timeout, fmt.Sprintf("%v !>= %v", delta, dmp.Timeout)) 1284 1285 // Test that we didn't take forever (be very forgiving). Theoretically this test could fail very occasionally if the OS task swaps or locks up for a second at the wrong moment. 1286 assert.ItsEqual(t, true, delta < (dmp.Timeout*100), fmt.Sprintf("%v !< %v", delta, dmp.Timeout*100)) 1287 } 1288 1289 func TestDiffMainWithCheckLines(t *testing.T) { 1290 type TestCase struct { 1291 Text1 string 1292 Text2 string 1293 } 1294 1295 dmp := New() 1296 dmp.Timeout = 0 1297 1298 // Test cases must be at least 100 chars long to pass the cutoff. 1299 for i, tc := range []TestCase{ 1300 { 1301 "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n", 1302 "abcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\n", 1303 }, 1304 { 1305 "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", 1306 "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij", 1307 }, 1308 { 1309 "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n", 1310 "abcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n", 1311 }, 1312 } { 1313 resultWithoutCheckLines := dmp.Diff(tc.Text1, tc.Text2, false) 1314 resultWithCheckLines := dmp.Diff(tc.Text1, tc.Text2, true) 1315 1316 // TODO this fails for the third test case, why? 1317 if i != 2 { 1318 assert.ItsEqual(t, resultWithoutCheckLines, resultWithCheckLines, fmt.Sprintf("Test case #%d, %#v", i, tc)) 1319 } 1320 assert.ItsEqual(t, diffRebuildTexts(resultWithoutCheckLines), diffRebuildTexts(resultWithCheckLines), fmt.Sprintf("Test case #%d, %#v", i, tc)) 1321 } 1322 } 1323 1324 func pretty(diffs []Diff) string { 1325 var w bytes.Buffer 1326 1327 for i, diff := range diffs { 1328 _, _ = w.WriteString(fmt.Sprintf("%v. ", i)) 1329 1330 switch diff.Type { 1331 case DiffInsert: 1332 _, _ = w.WriteString("DiffIns") 1333 case DiffDelete: 1334 _, _ = w.WriteString("DiffDel") 1335 case DiffEqual: 1336 _, _ = w.WriteString("DiffEql") 1337 default: 1338 _, _ = w.WriteString("Unknown") 1339 } 1340 1341 _, _ = w.WriteString(fmt.Sprintf(": %v\n", diff.Text)) 1342 } 1343 1344 return w.String() 1345 } 1346 1347 func diffRebuildTexts(diffs []Diff) []string { 1348 texts := []string{"", ""} 1349 1350 for _, d := range diffs { 1351 if d.Type != DiffInsert { 1352 texts[0] += d.Text 1353 } 1354 if d.Type != DiffDelete { 1355 texts[1] += d.Text 1356 } 1357 } 1358 1359 return texts 1360 }