~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import os
 
18
import os.path
18
19
from cStringIO import StringIO
19
20
import errno
20
21
import subprocess
 
22
import sys
21
23
from tempfile import TemporaryFile
22
24
 
23
 
from bzrlib.diff import internal_diff, external_diff, show_diff_trees
24
 
from bzrlib.errors import BinaryFile, NoDiff
 
25
from bzrlib import tests
 
26
from bzrlib.diff import (
 
27
    DiffFromTool,
 
28
    DiffPath,
 
29
    DiffSymlink,
 
30
    DiffTree,
 
31
    DiffText,
 
32
    external_diff,
 
33
    internal_diff,
 
34
    show_diff_trees,
 
35
    )
 
36
from bzrlib.errors import BinaryFile, NoDiff, ExecutableMissing
25
37
import bzrlib.osutils as osutils
 
38
import bzrlib.transform as transform
26
39
import bzrlib.patiencediff
27
40
import bzrlib._patiencediff_py
28
41
from bzrlib.tests import (Feature, TestCase, TestCaseWithTransport,
29
42
                          TestCaseInTempDir, TestSkipped)
30
43
 
31
44
 
 
45
class _AttribFeature(Feature):
 
46
 
 
47
    def _probe(self):
 
48
        if (sys.platform not in ('cygwin', 'win32')):
 
49
            return False
 
50
        try:
 
51
            proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
 
52
        except OSError, e:
 
53
            return False
 
54
        return (0 == proc.wait())
 
55
 
 
56
    def feature_name(self):
 
57
        return 'attrib Windows command-line tool'
 
58
 
 
59
AttribFeature = _AttribFeature()
 
60
 
 
61
 
32
62
class _CompiledPatienceDiffFeature(Feature):
33
63
 
34
64
    def _probe(self):
44
74
CompiledPatienceDiffFeature = _CompiledPatienceDiffFeature()
45
75
 
46
76
 
47
 
class _UnicodeFilename(Feature):
48
 
    """Does the filesystem support Unicode filenames?"""
49
 
 
50
 
    def _probe(self):
51
 
        try:
52
 
            os.stat(u'\u03b1')
53
 
        except UnicodeEncodeError:
54
 
            return False
55
 
        except (IOError, OSError):
56
 
            # The filesystem allows the Unicode filename but the file doesn't
57
 
            # exist.
58
 
            return True
59
 
        else:
60
 
            # The filesystem allows the Unicode filename and the file exists,
61
 
            # for some reason.
62
 
            return True
63
 
 
64
 
UnicodeFilename = _UnicodeFilename()
65
 
 
66
 
 
67
 
class TestUnicodeFilename(TestCase):
68
 
 
69
 
    def test_probe_passes(self):
70
 
        """UnicodeFilename._probe passes."""
71
 
        # We can't test much more than that because the behaviour depends
72
 
        # on the platform.
73
 
        UnicodeFilename._probe()
74
 
        
75
 
 
76
77
def udiff_lines(old, new, allow_binary=False):
77
78
    output = StringIO()
78
79
    internal_diff('old', old, 'new', new, output, allow_binary)
231
232
                          ]
232
233
                          , lines)
233
234
 
 
235
    def test_internal_diff_no_content(self):
 
236
        output = StringIO()
 
237
        internal_diff(u'old', [], u'new', [], output)
 
238
        self.assertEqual('', output.getvalue())
 
239
 
 
240
    def test_internal_diff_no_changes(self):
 
241
        output = StringIO()
 
242
        internal_diff(u'old', ['text\n', 'contents\n'],
 
243
                      u'new', ['text\n', 'contents\n'],
 
244
                      output)
 
245
        self.assertEqual('', output.getvalue())
 
246
 
234
247
    def test_internal_diff_returns_bytes(self):
235
248
        import StringIO
236
249
        output = StringIO.StringIO()
486
499
        self.assertContainsRe(diff, '-contents\n'
487
500
                                    '\\+new contents\n')
488
501
 
 
502
 
 
503
    def test_internal_diff_exec_property(self):
 
504
        tree = self.make_branch_and_tree('tree')
 
505
 
 
506
        tt = transform.TreeTransform(tree)
 
507
        tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
 
508
        tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
 
509
        tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
 
510
        tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
 
511
        tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
 
512
        tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
 
513
        tt.apply()
 
514
        tree.commit('one', rev_id='rev-1')
 
515
 
 
516
        tt = transform.TreeTransform(tree)
 
517
        tt.set_executability(False, tt.trans_id_file_id('a-id'))
 
518
        tt.set_executability(True, tt.trans_id_file_id('b-id'))
 
519
        tt.set_executability(False, tt.trans_id_file_id('c-id'))
 
520
        tt.set_executability(True, tt.trans_id_file_id('d-id'))
 
521
        tt.apply()
 
522
        tree.rename_one('c', 'new-c')
 
523
        tree.rename_one('d', 'new-d')
 
524
 
 
525
        diff = self.get_diff(tree.basis_tree(), tree)
 
526
 
 
527
        self.assertContainsRe(diff, r"file 'a'.*\(properties changed:.*\+x to -x.*\)")
 
528
        self.assertContainsRe(diff, r"file 'b'.*\(properties changed:.*-x to \+x.*\)")
 
529
        self.assertContainsRe(diff, r"file 'c'.*\(properties changed:.*\+x to -x.*\)")
 
530
        self.assertContainsRe(diff, r"file 'd'.*\(properties changed:.*-x to \+x.*\)")
 
531
        self.assertNotContainsRe(diff, r"file 'e'")
 
532
        self.assertNotContainsRe(diff, r"file 'f'")
 
533
 
 
534
 
489
535
    def test_binary_unicode_filenames(self):
490
536
        """Test that contents of files are *not* encoded in UTF-8 when there
491
537
        is a binary file in the diff.
492
538
        """
493
539
        # See https://bugs.launchpad.net/bugs/110092.
494
 
        self.requireFeature(UnicodeFilename)
 
540
        self.requireFeature(tests.UnicodeFilenameFeature)
495
541
 
496
542
        # This bug isn't triggered with cStringIO.
497
543
        from StringIO import StringIO
516
562
 
517
563
    def test_unicode_filename(self):
518
564
        """Test when the filename are unicode."""
519
 
        self.requireFeature(UnicodeFilename)
 
565
        self.requireFeature(tests.UnicodeFilenameFeature)
520
566
 
521
567
        alpha, omega = u'\u03b1', u'\u03c9'
522
568
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
544
590
        self.assertContainsRe(diff, "=== modified file 'mod_%s'"%autf8)
545
591
        self.assertContainsRe(diff, "=== removed file 'del_%s'"%autf8)
546
592
 
 
593
 
 
594
class DiffWasIs(DiffPath):
 
595
 
 
596
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
 
597
        self.to_file.write('was: ')
 
598
        self.to_file.write(self.old_tree.get_file(file_id).read())
 
599
        self.to_file.write('is: ')
 
600
        self.to_file.write(self.new_tree.get_file(file_id).read())
 
601
        pass
 
602
 
 
603
 
 
604
class TestDiffTree(TestCaseWithTransport):
 
605
 
 
606
    def setUp(self):
 
607
        TestCaseWithTransport.setUp(self)
 
608
        self.old_tree = self.make_branch_and_tree('old-tree')
 
609
        self.old_tree.lock_write()
 
610
        self.addCleanup(self.old_tree.unlock)
 
611
        self.new_tree = self.make_branch_and_tree('new-tree')
 
612
        self.new_tree.lock_write()
 
613
        self.addCleanup(self.new_tree.unlock)
 
614
        self.differ = DiffTree(self.old_tree, self.new_tree, StringIO())
 
615
 
 
616
    def test_diff_text(self):
 
617
        self.build_tree_contents([('old-tree/olddir/',),
 
618
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
619
        self.old_tree.add('olddir')
 
620
        self.old_tree.add('olddir/oldfile', 'file-id')
 
621
        self.build_tree_contents([('new-tree/newdir/',),
 
622
                                  ('new-tree/newdir/newfile', 'new\n')])
 
623
        self.new_tree.add('newdir')
 
624
        self.new_tree.add('newdir/newfile', 'file-id')
 
625
        differ = DiffText(self.old_tree, self.new_tree, StringIO())
 
626
        differ.diff_text('file-id', None, 'old label', 'new label')
 
627
        self.assertEqual(
 
628
            '--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
 
629
            differ.to_file.getvalue())
 
630
        differ.to_file.seek(0)
 
631
        differ.diff_text(None, 'file-id', 'old label', 'new label')
 
632
        self.assertEqual(
 
633
            '--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
 
634
            differ.to_file.getvalue())
 
635
        differ.to_file.seek(0)
 
636
        differ.diff_text('file-id', 'file-id', 'old label', 'new label')
 
637
        self.assertEqual(
 
638
            '--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
 
639
            differ.to_file.getvalue())
 
640
 
 
641
    def test_diff_deletion(self):
 
642
        self.build_tree_contents([('old-tree/file', 'contents'),
 
643
                                  ('new-tree/file', 'contents')])
 
644
        self.old_tree.add('file', 'file-id')
 
645
        self.new_tree.add('file', 'file-id')
 
646
        os.unlink('new-tree/file')
 
647
        self.differ.show_diff(None)
 
648
        self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
 
649
 
 
650
    def test_diff_creation(self):
 
651
        self.build_tree_contents([('old-tree/file', 'contents'),
 
652
                                  ('new-tree/file', 'contents')])
 
653
        self.old_tree.add('file', 'file-id')
 
654
        self.new_tree.add('file', 'file-id')
 
655
        os.unlink('old-tree/file')
 
656
        self.differ.show_diff(None)
 
657
        self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
 
658
 
 
659
    def test_diff_symlink(self):
 
660
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
661
        differ.diff_symlink('old target', None)
 
662
        self.assertEqual("=== target was 'old target'\n",
 
663
                         differ.to_file.getvalue())
 
664
 
 
665
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
666
        differ.diff_symlink(None, 'new target')
 
667
        self.assertEqual("=== target is 'new target'\n",
 
668
                         differ.to_file.getvalue())
 
669
 
 
670
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
671
        differ.diff_symlink('old target', 'new target')
 
672
        self.assertEqual("=== target changed 'old target' => 'new target'\n",
 
673
                         differ.to_file.getvalue())
 
674
 
 
675
    def test_diff(self):
 
676
        self.build_tree_contents([('old-tree/olddir/',),
 
677
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
678
        self.old_tree.add('olddir')
 
679
        self.old_tree.add('olddir/oldfile', 'file-id')
 
680
        self.build_tree_contents([('new-tree/newdir/',),
 
681
                                  ('new-tree/newdir/newfile', 'new\n')])
 
682
        self.new_tree.add('newdir')
 
683
        self.new_tree.add('newdir/newfile', 'file-id')
 
684
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
685
        self.assertContainsRe(
 
686
            self.differ.to_file.getvalue(),
 
687
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
688
             ' \@\@\n-old\n\+new\n\n')
 
689
 
 
690
    def test_diff_kind_change(self):
 
691
        self.requireFeature(tests.SymlinkFeature)
 
692
        self.build_tree_contents([('old-tree/olddir/',),
 
693
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
694
        self.old_tree.add('olddir')
 
695
        self.old_tree.add('olddir/oldfile', 'file-id')
 
696
        self.build_tree(['new-tree/newdir/'])
 
697
        os.symlink('new', 'new-tree/newdir/newfile')
 
698
        self.new_tree.add('newdir')
 
699
        self.new_tree.add('newdir/newfile', 'file-id')
 
700
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
701
        self.assertContainsRe(
 
702
            self.differ.to_file.getvalue(),
 
703
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
 
704
             ' \@\@\n-old\n\n')
 
705
        self.assertContainsRe(self.differ.to_file.getvalue(),
 
706
                              "=== target is 'new'\n")
 
707
 
 
708
    def test_diff_directory(self):
 
709
        self.build_tree(['new-tree/new-dir/'])
 
710
        self.new_tree.add('new-dir', 'new-dir-id')
 
711
        self.differ.diff('new-dir-id', None, 'new-dir')
 
712
        self.assertEqual(self.differ.to_file.getvalue(), '')
 
713
 
 
714
    def create_old_new(self):
 
715
        self.build_tree_contents([('old-tree/olddir/',),
 
716
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
717
        self.old_tree.add('olddir')
 
718
        self.old_tree.add('olddir/oldfile', 'file-id')
 
719
        self.build_tree_contents([('new-tree/newdir/',),
 
720
                                  ('new-tree/newdir/newfile', 'new\n')])
 
721
        self.new_tree.add('newdir')
 
722
        self.new_tree.add('newdir/newfile', 'file-id')
 
723
 
 
724
    def test_register_diff(self):
 
725
        self.create_old_new()
 
726
        old_diff_factories = DiffTree.diff_factories
 
727
        DiffTree.diff_factories=old_diff_factories[:]
 
728
        DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
 
729
        try:
 
730
            differ = DiffTree(self.old_tree, self.new_tree, StringIO())
 
731
        finally:
 
732
            DiffTree.diff_factories = old_diff_factories
 
733
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
734
        self.assertNotContainsRe(
 
735
            differ.to_file.getvalue(),
 
736
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
737
             ' \@\@\n-old\n\+new\n\n')
 
738
        self.assertContainsRe(differ.to_file.getvalue(),
 
739
                              'was: old\nis: new\n')
 
740
 
 
741
    def test_extra_factories(self):
 
742
        self.create_old_new()
 
743
        differ = DiffTree(self.old_tree, self.new_tree, StringIO(),
 
744
                            extra_factories=[DiffWasIs.from_diff_tree])
 
745
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
746
        self.assertNotContainsRe(
 
747
            differ.to_file.getvalue(),
 
748
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
749
             ' \@\@\n-old\n\+new\n\n')
 
750
        self.assertContainsRe(differ.to_file.getvalue(),
 
751
                              'was: old\nis: new\n')
 
752
 
 
753
    def test_alphabetical_order(self):
 
754
        self.build_tree(['new-tree/a-file'])
 
755
        self.new_tree.add('a-file')
 
756
        self.build_tree(['old-tree/b-file'])
 
757
        self.old_tree.add('b-file')
 
758
        self.differ.show_diff(None)
 
759
        self.assertContainsRe(self.differ.to_file.getvalue(),
 
760
            '.*a-file(.|\n)*b-file')
 
761
 
 
762
 
547
763
class TestPatienceDiffLib(TestCase):
548
764
 
549
765
    def setUp(self):
553
769
        self._PatienceSequenceMatcher = \
554
770
            bzrlib._patiencediff_py.PatienceSequenceMatcher_py
555
771
 
 
772
    def test_diff_unicode_string(self):
 
773
        a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
 
774
        b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
 
775
        sm = self._PatienceSequenceMatcher(None, a, b)
 
776
        mb = sm.get_matching_blocks()
 
777
        self.assertEquals(35, len(mb))
 
778
 
556
779
    def test_unique_lcs(self):
557
780
        unique_lcs = self._unique_lcs
558
781
        self.assertEquals(unique_lcs('', ''), [])
594
817
        # This is what it currently gives:
595
818
        test_one('aBccDe', 'abccde', [(0,0), (5,5)])
596
819
 
 
820
    def assertDiffBlocks(self, a, b, expected_blocks):
 
821
        """Check that the sequence matcher returns the correct blocks.
 
822
 
 
823
        :param a: A sequence to match
 
824
        :param b: Another sequence to match
 
825
        :param expected_blocks: The expected output, not including the final
 
826
            matching block (len(a), len(b), 0)
 
827
        """
 
828
        matcher = self._PatienceSequenceMatcher(None, a, b)
 
829
        blocks = matcher.get_matching_blocks()
 
830
        last = blocks.pop()
 
831
        self.assertEqual((len(a), len(b), 0), last)
 
832
        self.assertEqual(expected_blocks, blocks)
 
833
 
597
834
    def test_matching_blocks(self):
598
 
        def chk_blocks(a, b, expected_blocks):
599
 
            # difflib always adds a signature of the total
600
 
            # length, with no matching entries at the end
601
 
            s = self._PatienceSequenceMatcher(None, a, b)
602
 
            blocks = s.get_matching_blocks()
603
 
            self.assertEquals((len(a), len(b), 0), blocks[-1])
604
 
            self.assertEquals(expected_blocks, blocks[:-1])
605
 
 
606
835
        # Some basic matching tests
607
 
        chk_blocks('', '', [])
608
 
        chk_blocks([], [], [])
609
 
        chk_blocks('abc', '', [])
610
 
        chk_blocks('', 'abc', [])
611
 
        chk_blocks('abcd', 'abcd', [(0, 0, 4)])
612
 
        chk_blocks('abcd', 'abce', [(0, 0, 3)])
613
 
        chk_blocks('eabc', 'abce', [(1, 0, 3)])
614
 
        chk_blocks('eabce', 'abce', [(1, 0, 4)])
615
 
        chk_blocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
616
 
        chk_blocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
617
 
        chk_blocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
618
 
        # This may check too much, but it checks to see that 
 
836
        self.assertDiffBlocks('', '', [])
 
837
        self.assertDiffBlocks([], [], [])
 
838
        self.assertDiffBlocks('abc', '', [])
 
839
        self.assertDiffBlocks('', 'abc', [])
 
840
        self.assertDiffBlocks('abcd', 'abcd', [(0, 0, 4)])
 
841
        self.assertDiffBlocks('abcd', 'abce', [(0, 0, 3)])
 
842
        self.assertDiffBlocks('eabc', 'abce', [(1, 0, 3)])
 
843
        self.assertDiffBlocks('eabce', 'abce', [(1, 0, 4)])
 
844
        self.assertDiffBlocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
 
845
        self.assertDiffBlocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
 
846
        self.assertDiffBlocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
 
847
        # This may check too much, but it checks to see that
619
848
        # a copied block stays attached to the previous section,
620
849
        # not the later one.
621
850
        # difflib would tend to grab the trailing longest match
622
851
        # which would make the diff not look right
623
 
        chk_blocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
624
 
                   [(0, 0, 6), (6, 11, 10)])
 
852
        self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
853
                              [(0, 0, 6), (6, 11, 10)])
625
854
 
626
855
        # make sure it supports passing in lists
627
 
        chk_blocks(
 
856
        self.assertDiffBlocks(
628
857
                   ['hello there\n',
629
858
                    'world\n',
630
859
                    'how are you today?\n'],
634
863
 
635
864
        # non unique lines surrounded by non-matching lines
636
865
        # won't be found
637
 
        chk_blocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
 
866
        self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
638
867
 
639
868
        # But they only need to be locally unique
640
 
        chk_blocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
 
869
        self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
641
870
 
642
871
        # non unique blocks won't be matched
643
 
        chk_blocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
 
872
        self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
644
873
 
645
874
        # but locally unique ones will
646
 
        chk_blocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
 
875
        self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
647
876
                                              (5,4,1), (7,5,2), (10,8,1)])
648
877
 
649
 
        chk_blocks('abbabbXd', 'cabbabxd', [(7,7,1)])
650
 
        chk_blocks('abbabbbb', 'cabbabbc', [])
651
 
        chk_blocks('bbbbbbbb', 'cbbbbbbc', [])
 
878
        self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
 
879
        self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
 
880
        self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
 
881
 
 
882
    def test_matching_blocks_tuples(self):
 
883
        # Some basic matching tests
 
884
        self.assertDiffBlocks([], [], [])
 
885
        self.assertDiffBlocks([('a',), ('b',), ('c,')], [], [])
 
886
        self.assertDiffBlocks([], [('a',), ('b',), ('c,')], [])
 
887
        self.assertDiffBlocks([('a',), ('b',), ('c,')],
 
888
                              [('a',), ('b',), ('c,')],
 
889
                              [(0, 0, 3)])
 
890
        self.assertDiffBlocks([('a',), ('b',), ('c,')],
 
891
                              [('a',), ('b',), ('d,')],
 
892
                              [(0, 0, 2)])
 
893
        self.assertDiffBlocks([('d',), ('b',), ('c,')],
 
894
                              [('a',), ('b',), ('c,')],
 
895
                              [(1, 1, 2)])
 
896
        self.assertDiffBlocks([('d',), ('a',), ('b',), ('c,')],
 
897
                              [('a',), ('b',), ('c,')],
 
898
                              [(1, 0, 3)])
 
899
        self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
 
900
                              [('a', 'b'), ('c', 'X'), ('e', 'f')],
 
901
                              [(0, 0, 1), (2, 2, 1)])
 
902
        self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
 
903
                              [('a', 'b'), ('c', 'dX'), ('e', 'f')],
 
904
                              [(0, 0, 1), (2, 2, 1)])
652
905
 
653
906
    def test_opcodes(self):
654
907
        def chk_ops(a, b, expected_codes):
766
1019
    def test_multiple_ranges(self):
767
1020
        # There was an earlier bug where we used a bad set of ranges,
768
1021
        # this triggers that specific bug, to make sure it doesn't regress
769
 
        def chk_blocks(a, b, expected_blocks):
770
 
            # difflib always adds a signature of the total
771
 
            # length, with no matching entries at the end
772
 
            s = self._PatienceSequenceMatcher(None, a, b)
773
 
            blocks = s.get_matching_blocks()
774
 
            x = blocks.pop()
775
 
            self.assertEquals(x, (len(a), len(b), 0))
776
 
            self.assertEquals(expected_blocks, blocks)
777
 
 
778
 
        chk_blocks('abcdefghijklmnop'
779
 
                 , 'abcXghiYZQRSTUVWXYZijklmnop'
780
 
                 , [(0, 0, 3), (6, 4, 3), (9, 20, 7)])
781
 
 
782
 
        chk_blocks('ABCd efghIjk  L'
783
 
                 , 'AxyzBCn mo pqrstuvwI1 2  L'
784
 
                 , [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
1022
        self.assertDiffBlocks('abcdefghijklmnop',
 
1023
                              'abcXghiYZQRSTUVWXYZijklmnop',
 
1024
                              [(0, 0, 3), (6, 4, 3), (9, 20, 7)])
 
1025
 
 
1026
        self.assertDiffBlocks('ABCd efghIjk  L',
 
1027
                              'AxyzBCn mo pqrstuvwI1 2  L',
 
1028
                              [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
785
1029
 
786
1030
        # These are rot13 code snippets.
787
 
        chk_blocks('''\
 
1031
        self.assertDiffBlocks('''\
788
1032
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
789
1033
    """
790
1034
    gnxrf_netf = ['svyr*']
897
1141
        self._PatienceSequenceMatcher = \
898
1142
            bzrlib._patiencediff_c.PatienceSequenceMatcher_c
899
1143
 
 
1144
    def test_unhashable(self):
 
1145
        """We should get a proper exception here."""
 
1146
        # We need to be able to hash items in the sequence, lists are
 
1147
        # unhashable, and thus cannot be diffed
 
1148
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
 
1149
                                         None, [[]], [])
 
1150
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
 
1151
                                         None, ['valid', []], [])
 
1152
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
 
1153
                                         None, ['valid'], [[]])
 
1154
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
 
1155
                                         None, ['valid'], ['valid', []])
 
1156
 
900
1157
 
901
1158
class TestPatienceDiffLibFiles(TestCaseInTempDir):
902
1159
 
1010
1267
            from bzrlib._patiencediff_py import recurse_matches_py
1011
1268
            self.assertIs(recurse_matches_py,
1012
1269
                          bzrlib.patiencediff.recurse_matches)
 
1270
 
 
1271
 
 
1272
class TestDiffFromTool(TestCaseWithTransport):
 
1273
 
 
1274
    def test_from_string(self):
 
1275
        diff_obj = DiffFromTool.from_string('diff', None, None, None)
 
1276
        self.addCleanup(diff_obj.finish)
 
1277
        self.assertEqual(['diff', '%(old_path)s', '%(new_path)s'],
 
1278
            diff_obj.command_template)
 
1279
 
 
1280
    def test_from_string_u5(self):
 
1281
        diff_obj = DiffFromTool.from_string('diff -u\\ 5', None, None, None)
 
1282
        self.addCleanup(diff_obj.finish)
 
1283
        self.assertEqual(['diff', '-u 5', '%(old_path)s', '%(new_path)s'],
 
1284
                         diff_obj.command_template)
 
1285
        self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
 
1286
                         diff_obj._get_command('old-path', 'new-path'))
 
1287
 
 
1288
    def test_execute(self):
 
1289
        output = StringIO()
 
1290
        diff_obj = DiffFromTool(['python', '-c',
 
1291
                                 'print "%(old_path)s %(new_path)s"'],
 
1292
                                None, None, output)
 
1293
        self.addCleanup(diff_obj.finish)
 
1294
        diff_obj._execute('old', 'new')
 
1295
        self.assertEqual(output.getvalue().rstrip(), 'old new')
 
1296
 
 
1297
    def test_excute_missing(self):
 
1298
        diff_obj = DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
 
1299
                                None, None, None)
 
1300
        self.addCleanup(diff_obj.finish)
 
1301
        e = self.assertRaises(ExecutableMissing, diff_obj._execute, 'old',
 
1302
                              'new')
 
1303
        self.assertEqual('a-tool-which-is-unlikely-to-exist could not be found'
 
1304
                         ' on this machine', str(e))
 
1305
 
 
1306
    def test_prepare_files_creates_paths_readable_by_windows_tool(self):
 
1307
        self.requireFeature(AttribFeature)
 
1308
        output = StringIO()
 
1309
        tree = self.make_branch_and_tree('tree')
 
1310
        self.build_tree_contents([('tree/file', 'content')])
 
1311
        tree.add('file', 'file-id')
 
1312
        tree.commit('old tree')
 
1313
        tree.lock_read()
 
1314
        self.addCleanup(tree.unlock)
 
1315
        diff_obj = DiffFromTool(['python', '-c',
 
1316
                                 'print "%(old_path)s %(new_path)s"'],
 
1317
                                tree, tree, output)
 
1318
        diff_obj._prepare_files('file-id', 'file', 'file')
 
1319
        self.assertReadableByAttrib(diff_obj._root, 'old\\file', r'old\\file')
 
1320
        self.assertReadableByAttrib(diff_obj._root, 'new\\file', r'new\\file')
 
1321
 
 
1322
    def assertReadableByAttrib(self, cwd, relpath, regex):
 
1323
        proc = subprocess.Popen(['attrib', relpath],
 
1324
                                stdout=subprocess.PIPE,
 
1325
                                cwd=cwd)
 
1326
        proc.wait()
 
1327
        result = proc.stdout.read()
 
1328
        self.assertContainsRe(result, regex)
 
1329
 
 
1330
    def test_prepare_files(self):
 
1331
        output = StringIO()
 
1332
        tree = self.make_branch_and_tree('tree')
 
1333
        self.build_tree_contents([('tree/oldname', 'oldcontent')])
 
1334
        self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
 
1335
        tree.add('oldname', 'file-id')
 
1336
        tree.add('oldname2', 'file2-id')
 
1337
        tree.commit('old tree', timestamp=0)
 
1338
        tree.rename_one('oldname', 'newname')
 
1339
        tree.rename_one('oldname2', 'newname2')
 
1340
        self.build_tree_contents([('tree/newname', 'newcontent')])
 
1341
        self.build_tree_contents([('tree/newname2', 'newcontent2')])
 
1342
        old_tree = tree.basis_tree()
 
1343
        old_tree.lock_read()
 
1344
        self.addCleanup(old_tree.unlock)
 
1345
        tree.lock_read()
 
1346
        self.addCleanup(tree.unlock)
 
1347
        diff_obj = DiffFromTool(['python', '-c',
 
1348
                                 'print "%(old_path)s %(new_path)s"'],
 
1349
                                old_tree, tree, output)
 
1350
        self.addCleanup(diff_obj.finish)
 
1351
        self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
 
1352
        old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
 
1353
                                                     'newname')
 
1354
        self.assertContainsRe(old_path, 'old/oldname$')
 
1355
        self.assertEqual(0, os.stat(old_path).st_mtime)
 
1356
        self.assertContainsRe(new_path, 'new/newname$')
 
1357
        self.assertFileEqual('oldcontent', old_path)
 
1358
        self.assertFileEqual('newcontent', new_path)
 
1359
        if osutils.host_os_dereferences_symlinks():
 
1360
            self.assertTrue(os.path.samefile('tree/newname', new_path))
 
1361
        # make sure we can create files with the same parent directories
 
1362
        diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')