~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/testdiff.py

merge merge tweaks from aaron, which includes latest .dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Development Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
 
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
import os
 
1
from bzrlib.selftest import TestCase
 
2
from bzrlib.diff import internal_diff
18
3
from cStringIO import StringIO
19
 
import errno
20
 
from tempfile import TemporaryFile
21
 
 
22
 
from bzrlib.diff import internal_diff, external_diff, show_diff_trees
23
 
from bzrlib.errors import BinaryFile, NoDiff
24
 
import bzrlib.patiencediff
25
 
from bzrlib.tests import (TestCase, TestCaseWithTransport,
26
 
                          TestCaseInTempDir, TestSkipped)
27
 
 
28
 
 
29
 
def udiff_lines(old, new, allow_binary=False):
 
4
def udiff_lines(old, new):
30
5
    output = StringIO()
31
 
    internal_diff('old', old, 'new', new, output, allow_binary)
 
6
    internal_diff('old', old, 'new', new, output)
32
7
    output.seek(0, 0)
33
8
    return output.readlines()
34
9
 
35
 
 
36
 
def external_udiff_lines(old, new, use_stringio=False):
37
 
    if use_stringio:
38
 
        # StringIO has no fileno, so it tests a different codepath
39
 
        output = StringIO()
40
 
    else:
41
 
        output = TemporaryFile()
42
 
    try:
43
 
        external_diff('old', old, 'new', new, output, diff_opts=['-u'])
44
 
    except NoDiff:
45
 
        raise TestSkipped('external "diff" not present to test')
46
 
    output.seek(0, 0)
47
 
    lines = output.readlines()
48
 
    output.close()
49
 
    return lines
50
 
 
 
10
def check_patch(lines):
 
11
    assert len(lines) > 1, \
 
12
        "Not enough lines for a file header for patch:\n%s" % "".join(lines)
 
13
    assert lines[0].startswith ('---'), \
 
14
        'No orig line for patch:\n%s' % "".join(lines)
 
15
    assert lines[1].startswith ('+++'), \
 
16
        'No mod line for patch:\n%s' % "".join(lines)
 
17
    assert len(lines) > 2, \
 
18
        "No hunks for patch:\n%s" % "".join(lines)
 
19
    assert lines[2].startswith('@@'),\
 
20
        "No hunk header for patch:\n%s" % "".join(lines)
 
21
    assert '@@' in lines[2][2:], \
 
22
        "Unterminated hunk header for patch:\n%s" % "".join(lines)
51
23
 
52
24
class TestDiff(TestCase):
53
 
 
54
25
    def test_add_nl(self):
55
26
        """diff generates a valid diff for patches that add a newline"""
56
27
        lines = udiff_lines(['boo'], ['boo\n'])
57
 
        self.check_patch(lines)
58
 
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
59
 
            ## "expected no-nl, got %r" % lines[4]
 
28
        check_patch(lines)
 
29
        assert lines[4] == '\\ No newline at end of file\n', \
 
30
            "expected no-nl, got %r" % lines[4]
60
31
 
61
32
    def test_add_nl_2(self):
62
33
        """diff generates a valid diff for patches that change last line and
63
34
        add a newline.
64
35
        """
65
36
        lines = udiff_lines(['boo'], ['goo\n'])
66
 
        self.check_patch(lines)
67
 
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
68
 
            ## "expected no-nl, got %r" % lines[4]
 
37
        check_patch(lines)
 
38
        assert lines[4] == '\\ No newline at end of file\n', \
 
39
            "expected no-nl, got %r" % lines[4]
69
40
 
70
41
    def test_remove_nl(self):
71
42
        """diff generates a valid diff for patches that change last line and
72
43
        add a newline.
73
44
        """
74
45
        lines = udiff_lines(['boo\n'], ['boo'])
75
 
        self.check_patch(lines)
76
 
        self.assertEquals(lines[5], '\\ No newline at end of file\n')
77
 
            ## "expected no-nl, got %r" % lines[5]
78
 
 
79
 
    def check_patch(self, lines):
80
 
        self.assert_(len(lines) > 1)
81
 
            ## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
82
 
        self.assert_(lines[0].startswith ('---'))
83
 
            ## 'No orig line for patch:\n%s' % "".join(lines)
84
 
        self.assert_(lines[1].startswith ('+++'))
85
 
            ## 'No mod line for patch:\n%s' % "".join(lines)
86
 
        self.assert_(len(lines) > 2)
87
 
            ## "No hunks for patch:\n%s" % "".join(lines)
88
 
        self.assert_(lines[2].startswith('@@'))
89
 
            ## "No hunk header for patch:\n%s" % "".join(lines)
90
 
        self.assert_('@@' in lines[2][2:])
91
 
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
92
 
 
93
 
    def test_binary_lines(self):
94
 
        self.assertRaises(BinaryFile, udiff_lines, [1023 * 'a' + '\x00'], [])
95
 
        self.assertRaises(BinaryFile, udiff_lines, [], [1023 * 'a' + '\x00'])
96
 
        udiff_lines([1023 * 'a' + '\x00'], [], allow_binary=True)
97
 
        udiff_lines([], [1023 * 'a' + '\x00'], allow_binary=True)
98
 
 
99
 
    def test_external_diff(self):
100
 
        lines = external_udiff_lines(['boo\n'], ['goo\n'])
101
 
        self.check_patch(lines)
102
 
 
103
 
    def test_external_diff_no_fileno(self):
104
 
        # Make sure that we can handle not having a fileno, even
105
 
        # if the diff is large
106
 
        lines = external_udiff_lines(['boo\n']*10000,
107
 
                                     ['goo\n']*10000,
108
 
                                     use_stringio=True)
109
 
        self.check_patch(lines)
110
 
        
111
 
    def test_internal_diff_default(self):
112
 
        # Default internal diff encoding is utf8
113
 
        output = StringIO()
114
 
        internal_diff(u'old_\xb5', ['old_text\n'],
115
 
                    u'new_\xe5', ['new_text\n'], output)
116
 
        lines = output.getvalue().splitlines(True)
117
 
        self.check_patch(lines)
118
 
        self.assertEquals(['--- old_\xc2\xb5\n',
119
 
                           '+++ new_\xc3\xa5\n',
120
 
                           '@@ -1,1 +1,1 @@\n',
121
 
                           '-old_text\n',
122
 
                           '+new_text\n',
123
 
                           '\n',
124
 
                          ]
125
 
                          , lines)
126
 
 
127
 
    def test_internal_diff_utf8(self):
128
 
        output = StringIO()
129
 
        internal_diff(u'old_\xb5', ['old_text\n'],
130
 
                    u'new_\xe5', ['new_text\n'], output,
131
 
                    path_encoding='utf8')
132
 
        lines = output.getvalue().splitlines(True)
133
 
        self.check_patch(lines)
134
 
        self.assertEquals(['--- old_\xc2\xb5\n',
135
 
                           '+++ new_\xc3\xa5\n',
136
 
                           '@@ -1,1 +1,1 @@\n',
137
 
                           '-old_text\n',
138
 
                           '+new_text\n',
139
 
                           '\n',
140
 
                          ]
141
 
                          , lines)
142
 
 
143
 
    def test_internal_diff_iso_8859_1(self):
144
 
        output = StringIO()
145
 
        internal_diff(u'old_\xb5', ['old_text\n'],
146
 
                    u'new_\xe5', ['new_text\n'], output,
147
 
                    path_encoding='iso-8859-1')
148
 
        lines = output.getvalue().splitlines(True)
149
 
        self.check_patch(lines)
150
 
        self.assertEquals(['--- old_\xb5\n',
151
 
                           '+++ new_\xe5\n',
152
 
                           '@@ -1,1 +1,1 @@\n',
153
 
                           '-old_text\n',
154
 
                           '+new_text\n',
155
 
                           '\n',
156
 
                          ]
157
 
                          , lines)
158
 
 
159
 
    def test_internal_diff_returns_bytes(self):
160
 
        import StringIO
161
 
        output = StringIO.StringIO()
162
 
        internal_diff(u'old_\xb5', ['old_text\n'],
163
 
                    u'new_\xe5', ['new_text\n'], output)
164
 
        self.failUnless(isinstance(output.getvalue(), str),
165
 
            'internal_diff should return bytestrings')
166
 
 
167
 
 
168
 
class TestDiffDates(TestCaseWithTransport):
169
 
 
170
 
    def setUp(self):
171
 
        super(TestDiffDates, self).setUp()
172
 
        self.wt = self.make_branch_and_tree('.')
173
 
        self.b = self.wt.branch
174
 
        self.build_tree_contents([
175
 
            ('file1', 'file1 contents at rev 1\n'),
176
 
            ('file2', 'file2 contents at rev 1\n')
177
 
            ])
178
 
        self.wt.add(['file1', 'file2'])
179
 
        self.wt.commit(
180
 
            message='Revision 1',
181
 
            timestamp=1143849600, # 2006-04-01 00:00:00 UTC
182
 
            timezone=0,
183
 
            rev_id='rev-1')
184
 
        self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
185
 
        self.wt.commit(
186
 
            message='Revision 2',
187
 
            timestamp=1143936000, # 2006-04-02 00:00:00 UTC
188
 
            timezone=28800,
189
 
            rev_id='rev-2')
190
 
        self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
191
 
        self.wt.commit(
192
 
            message='Revision 3',
193
 
            timestamp=1144022400, # 2006-04-03 00:00:00 UTC
194
 
            timezone=-3600,
195
 
            rev_id='rev-3')
196
 
        self.wt.remove(['file2'])
197
 
        self.wt.commit(
198
 
            message='Revision 4',
199
 
            timestamp=1144108800, # 2006-04-04 00:00:00 UTC
200
 
            timezone=0,
201
 
            rev_id='rev-4')
202
 
        self.build_tree_contents([
203
 
            ('file1', 'file1 contents in working tree\n')
204
 
            ])
205
 
        # set the date stamps for files in the working tree to known values
206
 
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
207
 
 
208
 
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
209
 
        output = StringIO()
210
 
        if working_tree is not None:
211
 
            extra_trees = (working_tree,)
212
 
        else:
213
 
            extra_trees = ()
214
 
        show_diff_trees(tree1, tree2, output, specific_files=specific_files,
215
 
                        extra_trees=extra_trees, old_label='old/', 
216
 
                        new_label='new/')
217
 
        return output.getvalue()
218
 
 
219
 
    def test_diff_rev_tree_working_tree(self):
220
 
        output = self.get_diff(self.wt.basis_tree(), self.wt)
221
 
        # note that the date for old/file1 is from rev 2 rather than from
222
 
        # the basis revision (rev 4)
223
 
        self.assertEqualDiff(output, '''\
224
 
=== modified file 'file1'
225
 
--- old/file1\t2006-04-02 00:00:00 +0000
226
 
+++ new/file1\t2006-04-05 00:00:00 +0000
227
 
@@ -1,1 +1,1 @@
228
 
-file1 contents at rev 2
229
 
+file1 contents in working tree
230
 
 
231
 
''')
232
 
 
233
 
    def test_diff_rev_tree_rev_tree(self):
234
 
        tree1 = self.b.repository.revision_tree('rev-2')
235
 
        tree2 = self.b.repository.revision_tree('rev-3')
236
 
        output = self.get_diff(tree1, tree2)
237
 
        self.assertEqualDiff(output, '''\
238
 
=== modified file 'file2'
239
 
--- old/file2\t2006-04-01 00:00:00 +0000
240
 
+++ new/file2\t2006-04-03 00:00:00 +0000
241
 
@@ -1,1 +1,1 @@
242
 
-file2 contents at rev 1
243
 
+file2 contents at rev 3
244
 
 
245
 
''')
246
 
        
247
 
    def test_diff_add_files(self):
248
 
        tree1 = self.b.repository.revision_tree(None)
249
 
        tree2 = self.b.repository.revision_tree('rev-1')
250
 
        output = self.get_diff(tree1, tree2)
251
 
        # the files have the epoch time stamp for the tree in which
252
 
        # they don't exist.
253
 
        self.assertEqualDiff(output, '''\
254
 
=== added file 'file1'
255
 
--- old/file1\t1970-01-01 00:00:00 +0000
256
 
+++ new/file1\t2006-04-01 00:00:00 +0000
257
 
@@ -0,0 +1,1 @@
258
 
+file1 contents at rev 1
259
 
 
260
 
=== added file 'file2'
261
 
--- old/file2\t1970-01-01 00:00:00 +0000
262
 
+++ new/file2\t2006-04-01 00:00:00 +0000
263
 
@@ -0,0 +1,1 @@
264
 
+file2 contents at rev 1
265
 
 
266
 
''')
267
 
 
268
 
    def test_diff_remove_files(self):
269
 
        tree1 = self.b.repository.revision_tree('rev-3')
270
 
        tree2 = self.b.repository.revision_tree('rev-4')
271
 
        output = self.get_diff(tree1, tree2)
272
 
        # the file has the epoch time stamp for the tree in which
273
 
        # it doesn't exist.
274
 
        self.assertEqualDiff(output, '''\
275
 
=== removed file 'file2'
276
 
--- old/file2\t2006-04-03 00:00:00 +0000
277
 
+++ new/file2\t1970-01-01 00:00:00 +0000
278
 
@@ -1,1 +0,0 @@
279
 
-file2 contents at rev 3
280
 
 
281
 
''')
282
 
 
283
 
    def test_show_diff_specified(self):
284
 
        """A working tree filename can be used to identify a file"""
285
 
        self.wt.rename_one('file1', 'file1b')
286
 
        old_tree = self.b.repository.revision_tree('rev-1')
287
 
        new_tree = self.b.repository.revision_tree('rev-4')
288
 
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'], 
289
 
                            working_tree=self.wt)
290
 
        self.assertContainsRe(out, 'file1\t')
291
 
 
292
 
    def test_recursive_diff(self):
293
 
        """Children of directories are matched"""
294
 
        os.mkdir('dir1')
295
 
        os.mkdir('dir2')
296
 
        self.wt.add(['dir1', 'dir2'])
297
 
        self.wt.rename_one('file1', 'dir1/file1')
298
 
        old_tree = self.b.repository.revision_tree('rev-1')
299
 
        new_tree = self.b.repository.revision_tree('rev-4')
300
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'], 
301
 
                            working_tree=self.wt)
302
 
        self.assertContainsRe(out, 'file1\t')
303
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'], 
304
 
                            working_tree=self.wt)
305
 
        self.assertNotContainsRe(out, 'file1\t')
306
 
 
307
 
class TestPatienceDiffLib(TestCase):
308
 
 
309
 
    def test_unique_lcs(self):
310
 
        unique_lcs = bzrlib.patiencediff.unique_lcs
311
 
        self.assertEquals(unique_lcs('', ''), [])
312
 
        self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
313
 
        self.assertEquals(unique_lcs('a', 'b'), [])
314
 
        self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
315
 
        self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
316
 
        self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
317
 
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1), 
318
 
                                                         (3,3), (4,4)])
319
 
        self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
320
 
 
321
 
    def test_recurse_matches(self):
322
 
        def test_one(a, b, matches):
323
 
            test_matches = []
324
 
            bzrlib.patiencediff.recurse_matches(a, b, 0, 0, len(a), len(b),
325
 
                test_matches, 10)
326
 
            self.assertEquals(test_matches, matches)
327
 
 
328
 
        test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
329
 
                 [(0, 0), (2, 2), (4, 4)])
330
 
        test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
331
 
                 [(0, 0), (2, 1), (4, 2)])
332
 
 
333
 
        # recurse_matches doesn't match non-unique 
334
 
        # lines surrounded by bogus text.
335
 
        # The update has been done in patiencediff.SequenceMatcher instead
336
 
 
337
 
        # This is what it could be
338
 
        #test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
339
 
 
340
 
        # This is what it currently gives:
341
 
        test_one('aBccDe', 'abccde', [(0,0), (5,5)])
342
 
 
343
 
    def test_matching_blocks(self):
344
 
        def chk_blocks(a, b, expected_blocks):
345
 
            # difflib always adds a signature of the total
346
 
            # length, with no matching entries at the end
347
 
            s = bzrlib.patiencediff.PatienceSequenceMatcher(None, a, b)
348
 
            blocks = s.get_matching_blocks()
349
 
            self.assertEquals((len(a), len(b), 0), blocks[-1])
350
 
            self.assertEquals(expected_blocks, blocks[:-1])
351
 
 
352
 
        # Some basic matching tests
353
 
        chk_blocks('', '', [])
354
 
        chk_blocks([], [], [])
355
 
        chk_blocks('abcd', 'abcd', [(0, 0, 4)])
356
 
        chk_blocks('abcd', 'abce', [(0, 0, 3)])
357
 
        chk_blocks('eabc', 'abce', [(1, 0, 3)])
358
 
        chk_blocks('eabce', 'abce', [(1, 0, 4)])
359
 
        chk_blocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
360
 
        chk_blocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
361
 
        chk_blocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
362
 
        # This may check too much, but it checks to see that 
363
 
        # a copied block stays attached to the previous section,
364
 
        # not the later one.
365
 
        # difflib would tend to grab the trailing longest match
366
 
        # which would make the diff not look right
367
 
        chk_blocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
368
 
                   [(0, 0, 6), (6, 11, 10)])
369
 
 
370
 
        # make sure it supports passing in lists
371
 
        chk_blocks(
372
 
                   ['hello there\n',
373
 
                    'world\n',
374
 
                    'how are you today?\n'],
375
 
                   ['hello there\n',
376
 
                    'how are you today?\n'],
377
 
                [(0, 0, 1), (2, 1, 1)])
378
 
 
379
 
        # non unique lines surrounded by non-matching lines
380
 
        # won't be found
381
 
        chk_blocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
382
 
 
383
 
        # But they only need to be locally unique
384
 
        chk_blocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
385
 
 
386
 
        # non unique blocks won't be matched
387
 
        chk_blocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
388
 
 
389
 
        # but locally unique ones will
390
 
        chk_blocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
391
 
                                              (5,4,1), (7,5,2), (10,8,1)])
392
 
 
393
 
        chk_blocks('abbabbXd', 'cabbabxd', [(7,7,1)])
394
 
        chk_blocks('abbabbbb', 'cabbabbc', [])
395
 
        chk_blocks('bbbbbbbb', 'cbbbbbbc', [])
396
 
 
397
 
    def test_opcodes(self):
398
 
        def chk_ops(a, b, expected_codes):
399
 
            s = bzrlib.patiencediff.PatienceSequenceMatcher(None, a, b)
400
 
            self.assertEquals(expected_codes, s.get_opcodes())
401
 
 
402
 
        chk_ops('', '', [])
403
 
        chk_ops([], [], [])
404
 
        chk_ops('abcd', 'abcd', [('equal',    0,4, 0,4)])
405
 
        chk_ops('abcd', 'abce', [('equal',   0,3, 0,3),
406
 
                                 ('replace', 3,4, 3,4)
407
 
                                ])
408
 
        chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
409
 
                                 ('equal',  1,4, 0,3),
410
 
                                 ('insert', 4,4, 3,4)
411
 
                                ])
412
 
        chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
413
 
                                  ('equal',  1,5, 0,4)
414
 
                                 ])
415
 
        chk_ops('abcde', 'abXde', [('equal',   0,2, 0,2),
416
 
                                   ('replace', 2,3, 2,3),
417
 
                                   ('equal',   3,5, 3,5)
418
 
                                  ])
419
 
        chk_ops('abcde', 'abXYZde', [('equal',   0,2, 0,2),
420
 
                                     ('replace', 2,3, 2,5),
421
 
                                     ('equal',   3,5, 5,7)
422
 
                                    ])
423
 
        chk_ops('abde', 'abXYZde', [('equal',  0,2, 0,2),
424
 
                                    ('insert', 2,2, 2,5),
425
 
                                    ('equal',  2,4, 5,7)
426
 
                                   ])
427
 
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
428
 
                [('equal',  0,6,  0,6),
429
 
                 ('insert', 6,6,  6,11),
430
 
                 ('equal',  6,16, 11,21)
431
 
                ])
432
 
        chk_ops(
433
 
                [ 'hello there\n'
434
 
                , 'world\n'
435
 
                , 'how are you today?\n'],
436
 
                [ 'hello there\n'
437
 
                , 'how are you today?\n'],
438
 
                [('equal',  0,1, 0,1),
439
 
                 ('delete', 1,2, 1,1),
440
 
                 ('equal',  2,3, 1,2),
441
 
                ])
442
 
        chk_ops('aBccDe', 'abccde', 
443
 
                [('equal',   0,1, 0,1),
444
 
                 ('replace', 1,5, 1,5),
445
 
                 ('equal',   5,6, 5,6),
446
 
                ])
447
 
        chk_ops('aBcDec', 'abcdec', 
448
 
                [('equal',   0,1, 0,1),
449
 
                 ('replace', 1,2, 1,2),
450
 
                 ('equal',   2,3, 2,3),
451
 
                 ('replace', 3,4, 3,4),
452
 
                 ('equal',   4,6, 4,6),
453
 
                ])
454
 
        chk_ops('aBcdEcdFg', 'abcdecdfg', 
455
 
                [('equal',   0,1, 0,1),
456
 
                 ('replace', 1,8, 1,8),
457
 
                 ('equal',   8,9, 8,9)
458
 
                ])
459
 
        chk_ops('aBcdEeXcdFg', 'abcdecdfg', 
460
 
                [('equal',   0,1, 0,1),
461
 
                 ('replace', 1,2, 1,2),
462
 
                 ('equal',   2,4, 2,4),
463
 
                 ('delete', 4,5, 4,4),
464
 
                 ('equal',   5,6, 4,5),
465
 
                 ('delete', 6,7, 5,5),
466
 
                 ('equal',   7,9, 5,7),
467
 
                 ('replace', 9,10, 7,8),
468
 
                 ('equal',   10,11, 8,9)
469
 
                ])
470
 
 
471
 
    def test_multiple_ranges(self):
472
 
        # There was an earlier bug where we used a bad set of ranges,
473
 
        # this triggers that specific bug, to make sure it doesn't regress
474
 
        def chk_blocks(a, b, expected_blocks):
475
 
            # difflib always adds a signature of the total
476
 
            # length, with no matching entries at the end
477
 
            s = bzrlib.patiencediff.PatienceSequenceMatcher(None, a, b)
478
 
            blocks = s.get_matching_blocks()
479
 
            x = blocks.pop()
480
 
            self.assertEquals(x, (len(a), len(b), 0))
481
 
            self.assertEquals(expected_blocks, blocks)
482
 
 
483
 
        chk_blocks('abcdefghijklmnop'
484
 
                 , 'abcXghiYZQRSTUVWXYZijklmnop'
485
 
                 , [(0, 0, 3), (6, 4, 3), (9, 20, 7)])
486
 
 
487
 
        chk_blocks('ABCd efghIjk  L'
488
 
                 , 'AxyzBCn mo pqrstuvwI1 2  L'
489
 
                 , [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
490
 
 
491
 
        # These are rot13 code snippets.
492
 
        chk_blocks('''\
493
 
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
494
 
    """
495
 
    gnxrf_netf = ['svyr*']
496
 
    gnxrf_bcgvbaf = ['ab-erphefr']
497
 
  
498
 
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
499
 
        sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
500
 
        vs vf_dhvrg():
501
 
            ercbegre = nqq_ercbegre_ahyy
502
 
        ryfr:
503
 
            ercbegre = nqq_ercbegre_cevag
504
 
        fzneg_nqq(svyr_yvfg, abg ab_erphefr, ercbegre)
505
 
 
506
 
 
507
 
pynff pzq_zxqve(Pbzznaq):
508
 
'''.splitlines(True), '''\
509
 
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
510
 
 
511
 
    --qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl 
512
 
    nqq gurz.
513
 
    """
514
 
    gnxrf_netf = ['svyr*']
515
 
    gnxrf_bcgvbaf = ['ab-erphefr', 'qel-eha']
516
 
 
517
 
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr, qel_eha=Snyfr):
518
 
        vzcbeg omeyvo.nqq
519
 
 
520
 
        vs qel_eha:
521
 
            vs vf_dhvrg():
522
 
                # Guvf vf cbvagyrff, ohg V'q engure abg envfr na reebe
523
 
                npgvba = omeyvo.nqq.nqq_npgvba_ahyy
524
 
            ryfr:
525
 
  npgvba = omeyvo.nqq.nqq_npgvba_cevag
526
 
        ryvs vf_dhvrg():
527
 
            npgvba = omeyvo.nqq.nqq_npgvba_nqq
528
 
        ryfr:
529
 
       npgvba = omeyvo.nqq.nqq_npgvba_nqq_naq_cevag
530
 
 
531
 
        omeyvo.nqq.fzneg_nqq(svyr_yvfg, abg ab_erphefr, npgvba)
532
 
 
533
 
 
534
 
pynff pzq_zxqve(Pbzznaq):
535
 
'''.splitlines(True)
536
 
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
537
 
 
538
 
    def test_patience_unified_diff(self):
539
 
        txt_a = ['hello there\n',
540
 
                 'world\n',
541
 
                 'how are you today?\n']
542
 
        txt_b = ['hello there\n',
543
 
                 'how are you today?\n']
544
 
        unified_diff = bzrlib.patiencediff.unified_diff
545
 
        psm = bzrlib.patiencediff.PatienceSequenceMatcher
546
 
        self.assertEquals([ '---  \n',
547
 
                           '+++  \n',
548
 
                           '@@ -1,3 +1,2 @@\n',
549
 
                           ' hello there\n',
550
 
                           '-world\n',
551
 
                           ' how are you today?\n'
552
 
                          ]
553
 
                          , list(unified_diff(txt_a, txt_b,
554
 
                                 sequencematcher=psm)))
555
 
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
556
 
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
557
 
        # This is the result with LongestCommonSubstring matching
558
 
        self.assertEquals(['---  \n',
559
 
                           '+++  \n',
560
 
                           '@@ -1,6 +1,11 @@\n',
561
 
                           ' a\n',
562
 
                           ' b\n',
563
 
                           ' c\n',
564
 
                           '+d\n',
565
 
                           '+e\n',
566
 
                           '+f\n',
567
 
                           '+x\n',
568
 
                           '+y\n',
569
 
                           ' d\n',
570
 
                           ' e\n',
571
 
                           ' f\n']
572
 
                          , list(unified_diff(txt_a, txt_b)))
573
 
        # And the patience diff
574
 
        self.assertEquals(['---  \n',
575
 
                           '+++  \n',
576
 
                           '@@ -4,6 +4,11 @@\n',
577
 
                           ' d\n',
578
 
                           ' e\n',
579
 
                           ' f\n',
580
 
                           '+x\n',
581
 
                           '+y\n',
582
 
                           '+d\n',
583
 
                           '+e\n',
584
 
                           '+f\n',
585
 
                           ' g\n',
586
 
                           ' h\n',
587
 
                           ' i\n',
588
 
                          ]
589
 
                          , list(unified_diff(txt_a, txt_b,
590
 
                                 sequencematcher=psm)))
591
 
 
592
 
 
593
 
class TestPatienceDiffLibFiles(TestCaseInTempDir):
594
 
 
595
 
    def test_patience_unified_diff_files(self):
596
 
        txt_a = ['hello there\n',
597
 
                 'world\n',
598
 
                 'how are you today?\n']
599
 
        txt_b = ['hello there\n',
600
 
                 'how are you today?\n']
601
 
        open('a1', 'wb').writelines(txt_a)
602
 
        open('b1', 'wb').writelines(txt_b)
603
 
 
604
 
        unified_diff_files = bzrlib.patiencediff.unified_diff_files
605
 
        psm = bzrlib.patiencediff.PatienceSequenceMatcher
606
 
        self.assertEquals(['--- a1 \n',
607
 
                           '+++ b1 \n',
608
 
                           '@@ -1,3 +1,2 @@\n',
609
 
                           ' hello there\n',
610
 
                           '-world\n',
611
 
                           ' how are you today?\n',
612
 
                          ]
613
 
                          , list(unified_diff_files('a1', 'b1',
614
 
                                 sequencematcher=psm)))
615
 
 
616
 
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
617
 
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
618
 
        open('a2', 'wb').writelines(txt_a)
619
 
        open('b2', 'wb').writelines(txt_b)
620
 
 
621
 
        # This is the result with LongestCommonSubstring matching
622
 
        self.assertEquals(['--- a2 \n',
623
 
                           '+++ b2 \n',
624
 
                           '@@ -1,6 +1,11 @@\n',
625
 
                           ' a\n',
626
 
                           ' b\n',
627
 
                           ' c\n',
628
 
                           '+d\n',
629
 
                           '+e\n',
630
 
                           '+f\n',
631
 
                           '+x\n',
632
 
                           '+y\n',
633
 
                           ' d\n',
634
 
                           ' e\n',
635
 
                           ' f\n']
636
 
                          , list(unified_diff_files('a2', 'b2')))
637
 
 
638
 
        # And the patience diff
639
 
        self.assertEquals(['--- a2 \n',
640
 
                           '+++ b2 \n',
641
 
                           '@@ -4,6 +4,11 @@\n',
642
 
                           ' d\n',
643
 
                           ' e\n',
644
 
                           ' f\n',
645
 
                           '+x\n',
646
 
                           '+y\n',
647
 
                           '+d\n',
648
 
                           '+e\n',
649
 
                           '+f\n',
650
 
                           ' g\n',
651
 
                           ' h\n',
652
 
                           ' i\n',
653
 
                          ]
654
 
                          , list(unified_diff_files('a2', 'b2',
655
 
                                 sequencematcher=psm)))
 
46
        check_patch(lines)
 
47
        assert lines[5] == '\\ No newline at end of file\n', \
 
48
            "expected no-nl, got %r" % lines[5]