/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

More work on roundtrip push support.

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