~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/testdiff.py

- improved handling of non-ascii branch names and test
  patch from Joel Rosdahl

Show diffs side-by-side

added added

removed removed

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