~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

  • Committer: mbp at sourcefrog
  • Date: 2005-04-04 13:57:54 UTC
  • Revision ID: mbp@sourcefrog.net-20050404135754-ae2e4e5fb0094c91
- Write .bzr.log in utf8

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