~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-03-21 22:29:49 UTC
  • Revision ID: mbp@sourcefrog.net-20050321222949-232c2093a6eadd80
fixup doctest for new module structure

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