/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

  • Committer: Aaron Bentley
  • Date: 2007-12-27 02:28:14 UTC
  • mto: This revision was merged to the branch mainline in revision 3145.
  • Revision ID: aaron.bentley@utoronto.ca-20071227022814-e1ance6116xmbmqk
Fix typo

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 (
 
24
    DiffFromTool,
 
25
    DiffPath,
 
26
    DiffSymlink,
 
27
    DiffTree,
 
28
    DiffText,
 
29
    external_diff,
 
30
    internal_diff,
 
31
    show_diff_trees,
 
32
    )
 
33
from bzrlib.errors import BinaryFile, NoDiff
 
34
import bzrlib.osutils as osutils
 
35
import bzrlib.patiencediff
 
36
import bzrlib._patiencediff_py
 
37
from bzrlib.tests import (Feature, TestCase, TestCaseWithTransport,
 
38
                          TestCaseInTempDir, TestSkipped)
 
39
 
 
40
 
 
41
class _CompiledPatienceDiffFeature(Feature):
 
42
 
 
43
    def _probe(self):
 
44
        try:
 
45
            import bzrlib._patiencediff_c
 
46
        except ImportError:
 
47
            return False
 
48
        return True
 
49
 
 
50
    def feature_name(self):
 
51
        return 'bzrlib._patiencediff_c'
 
52
 
 
53
CompiledPatienceDiffFeature = _CompiledPatienceDiffFeature()
 
54
 
 
55
 
 
56
class _UnicodeFilename(Feature):
 
57
    """Does the filesystem support Unicode filenames?"""
 
58
 
 
59
    def _probe(self):
 
60
        try:
 
61
            os.stat(u'\u03b1')
 
62
        except UnicodeEncodeError:
 
63
            return False
 
64
        except (IOError, OSError):
 
65
            # The filesystem allows the Unicode filename but the file doesn't
 
66
            # exist.
 
67
            return True
 
68
        else:
 
69
            # The filesystem allows the Unicode filename and the file exists,
 
70
            # for some reason.
 
71
            return True
 
72
 
 
73
UnicodeFilename = _UnicodeFilename()
 
74
 
 
75
 
 
76
class TestUnicodeFilename(TestCase):
 
77
 
 
78
    def test_probe_passes(self):
 
79
        """UnicodeFilename._probe passes."""
 
80
        # We can't test much more than that because the behaviour depends
 
81
        # on the platform.
 
82
        UnicodeFilename._probe()
 
83
        
 
84
 
 
85
def udiff_lines(old, new, allow_binary=False):
 
86
    output = StringIO()
 
87
    internal_diff('old', old, 'new', new, output, allow_binary)
 
88
    output.seek(0, 0)
 
89
    return output.readlines()
 
90
 
 
91
 
 
92
def external_udiff_lines(old, new, use_stringio=False):
 
93
    if use_stringio:
 
94
        # StringIO has no fileno, so it tests a different codepath
 
95
        output = StringIO()
 
96
    else:
 
97
        output = TemporaryFile()
 
98
    try:
 
99
        external_diff('old', old, 'new', new, output, diff_opts=['-u'])
 
100
    except NoDiff:
 
101
        raise TestSkipped('external "diff" not present to test')
 
102
    output.seek(0, 0)
 
103
    lines = output.readlines()
 
104
    output.close()
 
105
    return lines
 
106
 
 
107
 
 
108
class TestDiff(TestCase):
 
109
 
 
110
    def test_add_nl(self):
 
111
        """diff generates a valid diff for patches that add a newline"""
 
112
        lines = udiff_lines(['boo'], ['boo\n'])
 
113
        self.check_patch(lines)
 
114
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
 
115
            ## "expected no-nl, got %r" % lines[4]
 
116
 
 
117
    def test_add_nl_2(self):
 
118
        """diff generates a valid diff for patches that change last line and
 
119
        add a newline.
 
120
        """
 
121
        lines = udiff_lines(['boo'], ['goo\n'])
 
122
        self.check_patch(lines)
 
123
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
 
124
            ## "expected no-nl, got %r" % lines[4]
 
125
 
 
126
    def test_remove_nl(self):
 
127
        """diff generates a valid diff for patches that change last line and
 
128
        add a newline.
 
129
        """
 
130
        lines = udiff_lines(['boo\n'], ['boo'])
 
131
        self.check_patch(lines)
 
132
        self.assertEquals(lines[5], '\\ No newline at end of file\n')
 
133
            ## "expected no-nl, got %r" % lines[5]
 
134
 
 
135
    def check_patch(self, lines):
 
136
        self.assert_(len(lines) > 1)
 
137
            ## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
 
138
        self.assert_(lines[0].startswith ('---'))
 
139
            ## 'No orig line for patch:\n%s' % "".join(lines)
 
140
        self.assert_(lines[1].startswith ('+++'))
 
141
            ## 'No mod line for patch:\n%s' % "".join(lines)
 
142
        self.assert_(len(lines) > 2)
 
143
            ## "No hunks for patch:\n%s" % "".join(lines)
 
144
        self.assert_(lines[2].startswith('@@'))
 
145
            ## "No hunk header for patch:\n%s" % "".join(lines)
 
146
        self.assert_('@@' in lines[2][2:])
 
147
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
 
148
 
 
149
    def test_binary_lines(self):
 
150
        self.assertRaises(BinaryFile, udiff_lines, [1023 * 'a' + '\x00'], [])
 
151
        self.assertRaises(BinaryFile, udiff_lines, [], [1023 * 'a' + '\x00'])
 
152
        udiff_lines([1023 * 'a' + '\x00'], [], allow_binary=True)
 
153
        udiff_lines([], [1023 * 'a' + '\x00'], allow_binary=True)
 
154
 
 
155
    def test_external_diff(self):
 
156
        lines = external_udiff_lines(['boo\n'], ['goo\n'])
 
157
        self.check_patch(lines)
 
158
        self.assertEqual('\n', lines[-1])
 
159
 
 
160
    def test_external_diff_no_fileno(self):
 
161
        # Make sure that we can handle not having a fileno, even
 
162
        # if the diff is large
 
163
        lines = external_udiff_lines(['boo\n']*10000,
 
164
                                     ['goo\n']*10000,
 
165
                                     use_stringio=True)
 
166
        self.check_patch(lines)
 
167
 
 
168
    def test_external_diff_binary_lang_c(self):
 
169
        old_env = {}
 
170
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
 
171
            old_env[lang] = osutils.set_or_unset_env(lang, 'C')
 
172
        try:
 
173
            lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
174
            # Older versions of diffutils say "Binary files", newer
 
175
            # versions just say "Files".
 
176
            self.assertContainsRe(lines[0],
 
177
                                  '(Binary f|F)iles old and new differ\n')
 
178
            self.assertEquals(lines[1:], ['\n'])
 
179
        finally:
 
180
            for lang, old_val in old_env.iteritems():
 
181
                osutils.set_or_unset_env(lang, old_val)
 
182
 
 
183
    def test_no_external_diff(self):
 
184
        """Check that NoDiff is raised when diff is not available"""
 
185
        # Use os.environ['PATH'] to make sure no 'diff' command is available
 
186
        orig_path = os.environ['PATH']
 
187
        try:
 
188
            os.environ['PATH'] = ''
 
189
            self.assertRaises(NoDiff, external_diff,
 
190
                              'old', ['boo\n'], 'new', ['goo\n'],
 
191
                              StringIO(), diff_opts=['-u'])
 
192
        finally:
 
193
            os.environ['PATH'] = orig_path
 
194
        
 
195
    def test_internal_diff_default(self):
 
196
        # Default internal diff encoding is utf8
 
197
        output = StringIO()
 
198
        internal_diff(u'old_\xb5', ['old_text\n'],
 
199
                    u'new_\xe5', ['new_text\n'], output)
 
200
        lines = output.getvalue().splitlines(True)
 
201
        self.check_patch(lines)
 
202
        self.assertEquals(['--- old_\xc2\xb5\n',
 
203
                           '+++ new_\xc3\xa5\n',
 
204
                           '@@ -1,1 +1,1 @@\n',
 
205
                           '-old_text\n',
 
206
                           '+new_text\n',
 
207
                           '\n',
 
208
                          ]
 
209
                          , lines)
 
210
 
 
211
    def test_internal_diff_utf8(self):
 
212
        output = StringIO()
 
213
        internal_diff(u'old_\xb5', ['old_text\n'],
 
214
                    u'new_\xe5', ['new_text\n'], output,
 
215
                    path_encoding='utf8')
 
216
        lines = output.getvalue().splitlines(True)
 
217
        self.check_patch(lines)
 
218
        self.assertEquals(['--- old_\xc2\xb5\n',
 
219
                           '+++ new_\xc3\xa5\n',
 
220
                           '@@ -1,1 +1,1 @@\n',
 
221
                           '-old_text\n',
 
222
                           '+new_text\n',
 
223
                           '\n',
 
224
                          ]
 
225
                          , lines)
 
226
 
 
227
    def test_internal_diff_iso_8859_1(self):
 
228
        output = StringIO()
 
229
        internal_diff(u'old_\xb5', ['old_text\n'],
 
230
                    u'new_\xe5', ['new_text\n'], output,
 
231
                    path_encoding='iso-8859-1')
 
232
        lines = output.getvalue().splitlines(True)
 
233
        self.check_patch(lines)
 
234
        self.assertEquals(['--- old_\xb5\n',
 
235
                           '+++ new_\xe5\n',
 
236
                           '@@ -1,1 +1,1 @@\n',
 
237
                           '-old_text\n',
 
238
                           '+new_text\n',
 
239
                           '\n',
 
240
                          ]
 
241
                          , lines)
 
242
 
 
243
    def test_internal_diff_no_content(self):
 
244
        output = StringIO()
 
245
        internal_diff(u'old', [], u'new', [], output)
 
246
        self.assertEqual('', output.getvalue())
 
247
 
 
248
    def test_internal_diff_no_changes(self):
 
249
        output = StringIO()
 
250
        internal_diff(u'old', ['text\n', 'contents\n'],
 
251
                      u'new', ['text\n', 'contents\n'],
 
252
                      output)
 
253
        self.assertEqual('', output.getvalue())
 
254
 
 
255
    def test_internal_diff_returns_bytes(self):
 
256
        import StringIO
 
257
        output = StringIO.StringIO()
 
258
        internal_diff(u'old_\xb5', ['old_text\n'],
 
259
                    u'new_\xe5', ['new_text\n'], output)
 
260
        self.failUnless(isinstance(output.getvalue(), str),
 
261
            'internal_diff should return bytestrings')
 
262
 
 
263
 
 
264
class TestDiffFiles(TestCaseInTempDir):
 
265
 
 
266
    def test_external_diff_binary(self):
 
267
        """The output when using external diff should use diff's i18n error"""
 
268
        # Make sure external_diff doesn't fail in the current LANG
 
269
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
270
 
 
271
        cmd = ['diff', '-u', '--binary', 'old', 'new']
 
272
        open('old', 'wb').write('\x00foobar\n')
 
273
        open('new', 'wb').write('foo\x00bar\n')
 
274
        pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
 
275
                                     stdin=subprocess.PIPE)
 
276
        out, err = pipe.communicate()
 
277
        # Diff returns '2' on Binary files.
 
278
        self.assertEqual(2, pipe.returncode)
 
279
        # We should output whatever diff tells us, plus a trailing newline
 
280
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
 
281
 
 
282
 
 
283
class TestShowDiffTreesHelper(TestCaseWithTransport):
 
284
    """Has a helper for running show_diff_trees"""
 
285
 
 
286
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
 
287
        output = StringIO()
 
288
        if working_tree is not None:
 
289
            extra_trees = (working_tree,)
 
290
        else:
 
291
            extra_trees = ()
 
292
        show_diff_trees(tree1, tree2, output, specific_files=specific_files,
 
293
                        extra_trees=extra_trees, old_label='old/',
 
294
                        new_label='new/')
 
295
        return output.getvalue()
 
296
 
 
297
 
 
298
class TestDiffDates(TestShowDiffTreesHelper):
 
299
 
 
300
    def setUp(self):
 
301
        super(TestDiffDates, self).setUp()
 
302
        self.wt = self.make_branch_and_tree('.')
 
303
        self.b = self.wt.branch
 
304
        self.build_tree_contents([
 
305
            ('file1', 'file1 contents at rev 1\n'),
 
306
            ('file2', 'file2 contents at rev 1\n')
 
307
            ])
 
308
        self.wt.add(['file1', 'file2'])
 
309
        self.wt.commit(
 
310
            message='Revision 1',
 
311
            timestamp=1143849600, # 2006-04-01 00:00:00 UTC
 
312
            timezone=0,
 
313
            rev_id='rev-1')
 
314
        self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
 
315
        self.wt.commit(
 
316
            message='Revision 2',
 
317
            timestamp=1143936000, # 2006-04-02 00:00:00 UTC
 
318
            timezone=28800,
 
319
            rev_id='rev-2')
 
320
        self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
 
321
        self.wt.commit(
 
322
            message='Revision 3',
 
323
            timestamp=1144022400, # 2006-04-03 00:00:00 UTC
 
324
            timezone=-3600,
 
325
            rev_id='rev-3')
 
326
        self.wt.remove(['file2'])
 
327
        self.wt.commit(
 
328
            message='Revision 4',
 
329
            timestamp=1144108800, # 2006-04-04 00:00:00 UTC
 
330
            timezone=0,
 
331
            rev_id='rev-4')
 
332
        self.build_tree_contents([
 
333
            ('file1', 'file1 contents in working tree\n')
 
334
            ])
 
335
        # set the date stamps for files in the working tree to known values
 
336
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
 
337
 
 
338
    def test_diff_rev_tree_working_tree(self):
 
339
        output = self.get_diff(self.wt.basis_tree(), self.wt)
 
340
        # note that the date for old/file1 is from rev 2 rather than from
 
341
        # the basis revision (rev 4)
 
342
        self.assertEqualDiff(output, '''\
 
343
=== modified file 'file1'
 
344
--- old/file1\t2006-04-02 00:00:00 +0000
 
345
+++ new/file1\t2006-04-05 00:00:00 +0000
 
346
@@ -1,1 +1,1 @@
 
347
-file1 contents at rev 2
 
348
+file1 contents in working tree
 
349
 
 
350
''')
 
351
 
 
352
    def test_diff_rev_tree_rev_tree(self):
 
353
        tree1 = self.b.repository.revision_tree('rev-2')
 
354
        tree2 = self.b.repository.revision_tree('rev-3')
 
355
        output = self.get_diff(tree1, tree2)
 
356
        self.assertEqualDiff(output, '''\
 
357
=== modified file 'file2'
 
358
--- old/file2\t2006-04-01 00:00:00 +0000
 
359
+++ new/file2\t2006-04-03 00:00:00 +0000
 
360
@@ -1,1 +1,1 @@
 
361
-file2 contents at rev 1
 
362
+file2 contents at rev 3
 
363
 
 
364
''')
 
365
        
 
366
    def test_diff_add_files(self):
 
367
        tree1 = self.b.repository.revision_tree(None)
 
368
        tree2 = self.b.repository.revision_tree('rev-1')
 
369
        output = self.get_diff(tree1, tree2)
 
370
        # the files have the epoch time stamp for the tree in which
 
371
        # they don't exist.
 
372
        self.assertEqualDiff(output, '''\
 
373
=== added file 'file1'
 
374
--- old/file1\t1970-01-01 00:00:00 +0000
 
375
+++ new/file1\t2006-04-01 00:00:00 +0000
 
376
@@ -0,0 +1,1 @@
 
377
+file1 contents at rev 1
 
378
 
 
379
=== added file 'file2'
 
380
--- old/file2\t1970-01-01 00:00:00 +0000
 
381
+++ new/file2\t2006-04-01 00:00:00 +0000
 
382
@@ -0,0 +1,1 @@
 
383
+file2 contents at rev 1
 
384
 
 
385
''')
 
386
 
 
387
    def test_diff_remove_files(self):
 
388
        tree1 = self.b.repository.revision_tree('rev-3')
 
389
        tree2 = self.b.repository.revision_tree('rev-4')
 
390
        output = self.get_diff(tree1, tree2)
 
391
        # the file has the epoch time stamp for the tree in which
 
392
        # it doesn't exist.
 
393
        self.assertEqualDiff(output, '''\
 
394
=== removed file 'file2'
 
395
--- old/file2\t2006-04-03 00:00:00 +0000
 
396
+++ new/file2\t1970-01-01 00:00:00 +0000
 
397
@@ -1,1 +0,0 @@
 
398
-file2 contents at rev 3
 
399
 
 
400
''')
 
401
 
 
402
    def test_show_diff_specified(self):
 
403
        """A working tree filename can be used to identify a file"""
 
404
        self.wt.rename_one('file1', 'file1b')
 
405
        old_tree = self.b.repository.revision_tree('rev-1')
 
406
        new_tree = self.b.repository.revision_tree('rev-4')
 
407
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'], 
 
408
                            working_tree=self.wt)
 
409
        self.assertContainsRe(out, 'file1\t')
 
410
 
 
411
    def test_recursive_diff(self):
 
412
        """Children of directories are matched"""
 
413
        os.mkdir('dir1')
 
414
        os.mkdir('dir2')
 
415
        self.wt.add(['dir1', 'dir2'])
 
416
        self.wt.rename_one('file1', 'dir1/file1')
 
417
        old_tree = self.b.repository.revision_tree('rev-1')
 
418
        new_tree = self.b.repository.revision_tree('rev-4')
 
419
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'], 
 
420
                            working_tree=self.wt)
 
421
        self.assertContainsRe(out, 'file1\t')
 
422
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'], 
 
423
                            working_tree=self.wt)
 
424
        self.assertNotContainsRe(out, 'file1\t')
 
425
 
 
426
 
 
427
 
 
428
class TestShowDiffTrees(TestShowDiffTreesHelper):
 
429
    """Direct tests for show_diff_trees"""
 
430
 
 
431
    def test_modified_file(self):
 
432
        """Test when a file is modified."""
 
433
        tree = self.make_branch_and_tree('tree')
 
434
        self.build_tree_contents([('tree/file', 'contents\n')])
 
435
        tree.add(['file'], ['file-id'])
 
436
        tree.commit('one', rev_id='rev-1')
 
437
 
 
438
        self.build_tree_contents([('tree/file', 'new contents\n')])
 
439
        diff = self.get_diff(tree.basis_tree(), tree)
 
440
        self.assertContainsRe(diff, "=== modified file 'file'\n")
 
441
        self.assertContainsRe(diff, '--- old/file\t')
 
442
        self.assertContainsRe(diff, '\\+\\+\\+ new/file\t')
 
443
        self.assertContainsRe(diff, '-contents\n'
 
444
                                    '\\+new contents\n')
 
445
 
 
446
    def test_modified_file_in_renamed_dir(self):
 
447
        """Test when a file is modified in a renamed directory."""
 
448
        tree = self.make_branch_and_tree('tree')
 
449
        self.build_tree(['tree/dir/'])
 
450
        self.build_tree_contents([('tree/dir/file', 'contents\n')])
 
451
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
 
452
        tree.commit('one', rev_id='rev-1')
 
453
 
 
454
        tree.rename_one('dir', 'other')
 
455
        self.build_tree_contents([('tree/other/file', 'new contents\n')])
 
456
        diff = self.get_diff(tree.basis_tree(), tree)
 
457
        self.assertContainsRe(diff, "=== renamed directory 'dir' => 'other'\n")
 
458
        self.assertContainsRe(diff, "=== modified file 'other/file'\n")
 
459
        # XXX: This is technically incorrect, because it used to be at another
 
460
        # location. What to do?
 
461
        self.assertContainsRe(diff, '--- old/dir/file\t')
 
462
        self.assertContainsRe(diff, '\\+\\+\\+ new/other/file\t')
 
463
        self.assertContainsRe(diff, '-contents\n'
 
464
                                    '\\+new contents\n')
 
465
 
 
466
    def test_renamed_directory(self):
 
467
        """Test when only a directory is only renamed."""
 
468
        tree = self.make_branch_and_tree('tree')
 
469
        self.build_tree(['tree/dir/'])
 
470
        self.build_tree_contents([('tree/dir/file', 'contents\n')])
 
471
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
 
472
        tree.commit('one', rev_id='rev-1')
 
473
 
 
474
        tree.rename_one('dir', 'newdir')
 
475
        diff = self.get_diff(tree.basis_tree(), tree)
 
476
        # Renaming a directory should be a single "you renamed this dir" even
 
477
        # when there are files inside.
 
478
        self.assertEqual("=== renamed directory 'dir' => 'newdir'\n", diff)
 
479
 
 
480
    def test_renamed_file(self):
 
481
        """Test when a file is only renamed."""
 
482
        tree = self.make_branch_and_tree('tree')
 
483
        self.build_tree_contents([('tree/file', 'contents\n')])
 
484
        tree.add(['file'], ['file-id'])
 
485
        tree.commit('one', rev_id='rev-1')
 
486
 
 
487
        tree.rename_one('file', 'newname')
 
488
        diff = self.get_diff(tree.basis_tree(), tree)
 
489
        self.assertContainsRe(diff, "=== renamed file 'file' => 'newname'\n")
 
490
        # We shouldn't have a --- or +++ line, because there is no content
 
491
        # change
 
492
        self.assertNotContainsRe(diff, '---')
 
493
 
 
494
    def test_renamed_and_modified_file(self):
 
495
        """Test when a file is only renamed."""
 
496
        tree = self.make_branch_and_tree('tree')
 
497
        self.build_tree_contents([('tree/file', 'contents\n')])
 
498
        tree.add(['file'], ['file-id'])
 
499
        tree.commit('one', rev_id='rev-1')
 
500
 
 
501
        tree.rename_one('file', 'newname')
 
502
        self.build_tree_contents([('tree/newname', 'new contents\n')])
 
503
        diff = self.get_diff(tree.basis_tree(), tree)
 
504
        self.assertContainsRe(diff, "=== renamed file 'file' => 'newname'\n")
 
505
        self.assertContainsRe(diff, '--- old/file\t')
 
506
        self.assertContainsRe(diff, '\\+\\+\\+ new/newname\t')
 
507
        self.assertContainsRe(diff, '-contents\n'
 
508
                                    '\\+new contents\n')
 
509
 
 
510
    def test_binary_unicode_filenames(self):
 
511
        """Test that contents of files are *not* encoded in UTF-8 when there
 
512
        is a binary file in the diff.
 
513
        """
 
514
        # See https://bugs.launchpad.net/bugs/110092.
 
515
        self.requireFeature(UnicodeFilename)
 
516
 
 
517
        # This bug isn't triggered with cStringIO.
 
518
        from StringIO import StringIO
 
519
        tree = self.make_branch_and_tree('tree')
 
520
        alpha, omega = u'\u03b1', u'\u03c9'
 
521
        alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
 
522
        self.build_tree_contents(
 
523
            [('tree/' + alpha, chr(0)),
 
524
             ('tree/' + omega,
 
525
              ('The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
 
526
        tree.add([alpha], ['file-id'])
 
527
        tree.add([omega], ['file-id-2'])
 
528
        diff_content = StringIO()
 
529
        show_diff_trees(tree.basis_tree(), tree, diff_content)
 
530
        diff = diff_content.getvalue()
 
531
        self.assertContainsRe(diff, r"=== added file '%s'" % alpha_utf8)
 
532
        self.assertContainsRe(
 
533
            diff, "Binary files a/%s.*and b/%s.* differ\n" % (alpha_utf8, alpha_utf8))
 
534
        self.assertContainsRe(diff, r"=== added file '%s'" % omega_utf8)
 
535
        self.assertContainsRe(diff, r"--- a/%s" % (omega_utf8,))
 
536
        self.assertContainsRe(diff, r"\+\+\+ b/%s" % (omega_utf8,))
 
537
 
 
538
    def test_unicode_filename(self):
 
539
        """Test when the filename are unicode."""
 
540
        self.requireFeature(UnicodeFilename)
 
541
 
 
542
        alpha, omega = u'\u03b1', u'\u03c9'
 
543
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
 
544
 
 
545
        tree = self.make_branch_and_tree('tree')
 
546
        self.build_tree_contents([('tree/ren_'+alpha, 'contents\n')])
 
547
        tree.add(['ren_'+alpha], ['file-id-2'])
 
548
        self.build_tree_contents([('tree/del_'+alpha, 'contents\n')])
 
549
        tree.add(['del_'+alpha], ['file-id-3'])
 
550
        self.build_tree_contents([('tree/mod_'+alpha, 'contents\n')])
 
551
        tree.add(['mod_'+alpha], ['file-id-4'])
 
552
 
 
553
        tree.commit('one', rev_id='rev-1')
 
554
 
 
555
        tree.rename_one('ren_'+alpha, 'ren_'+omega)
 
556
        tree.remove('del_'+alpha)
 
557
        self.build_tree_contents([('tree/add_'+alpha, 'contents\n')])
 
558
        tree.add(['add_'+alpha], ['file-id'])
 
559
        self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
 
560
 
 
561
        diff = self.get_diff(tree.basis_tree(), tree)
 
562
        self.assertContainsRe(diff,
 
563
                "=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
 
564
        self.assertContainsRe(diff, "=== added file 'add_%s'"%autf8)
 
565
        self.assertContainsRe(diff, "=== modified file 'mod_%s'"%autf8)
 
566
        self.assertContainsRe(diff, "=== removed file 'del_%s'"%autf8)
 
567
 
 
568
 
 
569
class DiffWasIs(DiffPath):
 
570
 
 
571
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
 
572
        self.to_file.write('was: ')
 
573
        self.to_file.write(self.old_tree.get_file(file_id).read())
 
574
        self.to_file.write('is: ')
 
575
        self.to_file.write(self.new_tree.get_file(file_id).read())
 
576
        pass
 
577
 
 
578
 
 
579
class TestDiffTree(TestCaseWithTransport):
 
580
 
 
581
    def setUp(self):
 
582
        TestCaseWithTransport.setUp(self)
 
583
        self.old_tree = self.make_branch_and_tree('old-tree')
 
584
        self.old_tree.lock_write()
 
585
        self.addCleanup(self.old_tree.unlock)
 
586
        self.new_tree = self.make_branch_and_tree('new-tree')
 
587
        self.new_tree.lock_write()
 
588
        self.addCleanup(self.new_tree.unlock)
 
589
        self.differ = DiffTree(self.old_tree, self.new_tree, StringIO())
 
590
 
 
591
    def test_diff_text(self):
 
592
        self.build_tree_contents([('old-tree/olddir/',),
 
593
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
594
        self.old_tree.add('olddir')
 
595
        self.old_tree.add('olddir/oldfile', 'file-id')
 
596
        self.build_tree_contents([('new-tree/newdir/',),
 
597
                                  ('new-tree/newdir/newfile', 'new\n')])
 
598
        self.new_tree.add('newdir')
 
599
        self.new_tree.add('newdir/newfile', 'file-id')
 
600
        differ = DiffText(self.old_tree, self.new_tree, StringIO())
 
601
        differ.diff_text('file-id', None, 'old label', 'new label')
 
602
        self.assertEqual(
 
603
            '--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
 
604
            differ.to_file.getvalue())
 
605
        differ.to_file.seek(0)
 
606
        differ.diff_text(None, 'file-id', 'old label', 'new label')
 
607
        self.assertEqual(
 
608
            '--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
 
609
            differ.to_file.getvalue())
 
610
        differ.to_file.seek(0)
 
611
        differ.diff_text('file-id', 'file-id', 'old label', 'new label')
 
612
        self.assertEqual(
 
613
            '--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
 
614
            differ.to_file.getvalue())
 
615
 
 
616
    def test_diff_deletion(self):
 
617
        self.build_tree_contents([('old-tree/file', 'contents'),
 
618
                                  ('new-tree/file', 'contents')])
 
619
        self.old_tree.add('file', 'file-id')
 
620
        self.new_tree.add('file', 'file-id')
 
621
        os.unlink('new-tree/file')
 
622
        self.differ.show_diff(None)
 
623
        self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
 
624
 
 
625
    def test_diff_creation(self):
 
626
        self.build_tree_contents([('old-tree/file', 'contents'),
 
627
                                  ('new-tree/file', 'contents')])
 
628
        self.old_tree.add('file', 'file-id')
 
629
        self.new_tree.add('file', 'file-id')
 
630
        os.unlink('old-tree/file')
 
631
        self.differ.show_diff(None)
 
632
        self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
 
633
 
 
634
    def test_diff_symlink(self):
 
635
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
636
        differ.diff_symlink('old target', None)
 
637
        self.assertEqual("=== target was 'old target'\n",
 
638
                         differ.to_file.getvalue())
 
639
 
 
640
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
641
        differ.diff_symlink(None, 'new target')
 
642
        self.assertEqual("=== target is 'new target'\n",
 
643
                         differ.to_file.getvalue())
 
644
 
 
645
        differ = DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
646
        differ.diff_symlink('old target', 'new target')
 
647
        self.assertEqual("=== target changed 'old target' => 'new target'\n",
 
648
                         differ.to_file.getvalue())
 
649
 
 
650
    def test_diff(self):
 
651
        self.build_tree_contents([('old-tree/olddir/',),
 
652
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
653
        self.old_tree.add('olddir')
 
654
        self.old_tree.add('olddir/oldfile', 'file-id')
 
655
        self.build_tree_contents([('new-tree/newdir/',),
 
656
                                  ('new-tree/newdir/newfile', 'new\n')])
 
657
        self.new_tree.add('newdir')
 
658
        self.new_tree.add('newdir/newfile', 'file-id')
 
659
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
660
        self.assertContainsRe(
 
661
            self.differ.to_file.getvalue(),
 
662
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
663
             ' \@\@\n-old\n\+new\n\n')
 
664
 
 
665
    def test_diff_kind_change(self):
 
666
        self.build_tree_contents([('old-tree/olddir/',),
 
667
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
668
        self.old_tree.add('olddir')
 
669
        self.old_tree.add('olddir/oldfile', 'file-id')
 
670
        self.build_tree(['new-tree/newdir/'])
 
671
        os.symlink('new', 'new-tree/newdir/newfile')
 
672
        self.new_tree.add('newdir')
 
673
        self.new_tree.add('newdir/newfile', 'file-id')
 
674
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
675
        self.assertContainsRe(
 
676
            self.differ.to_file.getvalue(),
 
677
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
 
678
             ' \@\@\n-old\n\n')
 
679
        self.assertContainsRe(self.differ.to_file.getvalue(),
 
680
                              "=== target is 'new'\n")
 
681
 
 
682
    def test_diff_directory(self):
 
683
        self.build_tree(['new-tree/new-dir/'])
 
684
        self.new_tree.add('new-dir', 'new-dir-id')
 
685
        self.differ.diff('new-dir-id', None, 'new-dir')
 
686
        self.assertEqual(self.differ.to_file.getvalue(), '')
 
687
 
 
688
    def create_old_new(self):
 
689
        self.build_tree_contents([('old-tree/olddir/',),
 
690
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
691
        self.old_tree.add('olddir')
 
692
        self.old_tree.add('olddir/oldfile', 'file-id')
 
693
        self.build_tree_contents([('new-tree/newdir/',),
 
694
                                  ('new-tree/newdir/newfile', 'new\n')])
 
695
        self.new_tree.add('newdir')
 
696
        self.new_tree.add('newdir/newfile', 'file-id')
 
697
 
 
698
    def test_register_diff(self):
 
699
        self.create_old_new()
 
700
        old_diff_factories = DiffTree.diff_factories
 
701
        DiffTree.diff_factories=old_diff_factories[:]
 
702
        DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
 
703
        try:
 
704
            differ = DiffTree(self.old_tree, self.new_tree, StringIO())
 
705
        finally:
 
706
            DiffTree.diff_factories = old_diff_factories
 
707
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
708
        self.assertNotContainsRe(
 
709
            differ.to_file.getvalue(),
 
710
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
711
             ' \@\@\n-old\n\+new\n\n')
 
712
        self.assertContainsRe(differ.to_file.getvalue(),
 
713
                              'was: old\nis: new\n')
 
714
 
 
715
    def test_extra_factories(self):
 
716
        self.create_old_new()
 
717
        differ = DiffTree(self.old_tree, self.new_tree, StringIO(),
 
718
                            extra_factories=[DiffWasIs.from_diff_tree])
 
719
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
720
        self.assertNotContainsRe(
 
721
            differ.to_file.getvalue(),
 
722
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
723
             ' \@\@\n-old\n\+new\n\n')
 
724
        self.assertContainsRe(differ.to_file.getvalue(),
 
725
                              'was: old\nis: new\n')
 
726
 
 
727
    def test_alphabetical_order(self):
 
728
        self.build_tree(['new-tree/a-file'])
 
729
        self.new_tree.add('a-file')
 
730
        self.build_tree(['old-tree/b-file'])
 
731
        self.old_tree.add('b-file')
 
732
        self.differ.show_diff(None)
 
733
        self.assertContainsRe(self.differ.to_file.getvalue(),
 
734
            '.*a-file(.|\n)*b-file')
 
735
 
 
736
 
 
737
class TestPatienceDiffLib(TestCase):
 
738
 
 
739
    def setUp(self):
 
740
        super(TestPatienceDiffLib, self).setUp()
 
741
        self._unique_lcs = bzrlib._patiencediff_py.unique_lcs_py
 
742
        self._recurse_matches = bzrlib._patiencediff_py.recurse_matches_py
 
743
        self._PatienceSequenceMatcher = \
 
744
            bzrlib._patiencediff_py.PatienceSequenceMatcher_py
 
745
 
 
746
    def test_unique_lcs(self):
 
747
        unique_lcs = self._unique_lcs
 
748
        self.assertEquals(unique_lcs('', ''), [])
 
749
        self.assertEquals(unique_lcs('', 'a'), [])
 
750
        self.assertEquals(unique_lcs('a', ''), [])
 
751
        self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
 
752
        self.assertEquals(unique_lcs('a', 'b'), [])
 
753
        self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
 
754
        self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
 
755
        self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
 
756
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1), 
 
757
                                                         (3,3), (4,4)])
 
758
        self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
 
759
 
 
760
    def test_recurse_matches(self):
 
761
        def test_one(a, b, matches):
 
762
            test_matches = []
 
763
            self._recurse_matches(
 
764
                a, b, 0, 0, len(a), len(b), test_matches, 10)
 
765
            self.assertEquals(test_matches, matches)
 
766
 
 
767
        test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
 
768
                 [(0, 0), (2, 2), (4, 4)])
 
769
        test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
 
770
                 [(0, 0), (2, 1), (4, 2)])
 
771
        # Even though 'bc' is not unique globally, and is surrounded by
 
772
        # non-matching lines, we should still match, because they are locally
 
773
        # unique
 
774
        test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
 
775
                                          (4, 6), (5, 7), (6, 8)])
 
776
 
 
777
        # recurse_matches doesn't match non-unique 
 
778
        # lines surrounded by bogus text.
 
779
        # The update has been done in patiencediff.SequenceMatcher instead
 
780
 
 
781
        # This is what it could be
 
782
        #test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
 
783
 
 
784
        # This is what it currently gives:
 
785
        test_one('aBccDe', 'abccde', [(0,0), (5,5)])
 
786
 
 
787
    def assertDiffBlocks(self, a, b, expected_blocks):
 
788
        """Check that the sequence matcher returns the correct blocks.
 
789
 
 
790
        :param a: A sequence to match
 
791
        :param b: Another sequence to match
 
792
        :param expected_blocks: The expected output, not including the final
 
793
            matching block (len(a), len(b), 0)
 
794
        """
 
795
        matcher = self._PatienceSequenceMatcher(None, a, b)
 
796
        blocks = matcher.get_matching_blocks()
 
797
        last = blocks.pop()
 
798
        self.assertEqual((len(a), len(b), 0), last)
 
799
        self.assertEqual(expected_blocks, blocks)
 
800
 
 
801
    def test_matching_blocks(self):
 
802
        # Some basic matching tests
 
803
        self.assertDiffBlocks('', '', [])
 
804
        self.assertDiffBlocks([], [], [])
 
805
        self.assertDiffBlocks('abc', '', [])
 
806
        self.assertDiffBlocks('', 'abc', [])
 
807
        self.assertDiffBlocks('abcd', 'abcd', [(0, 0, 4)])
 
808
        self.assertDiffBlocks('abcd', 'abce', [(0, 0, 3)])
 
809
        self.assertDiffBlocks('eabc', 'abce', [(1, 0, 3)])
 
810
        self.assertDiffBlocks('eabce', 'abce', [(1, 0, 4)])
 
811
        self.assertDiffBlocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
 
812
        self.assertDiffBlocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
 
813
        self.assertDiffBlocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
 
814
        # This may check too much, but it checks to see that
 
815
        # a copied block stays attached to the previous section,
 
816
        # not the later one.
 
817
        # difflib would tend to grab the trailing longest match
 
818
        # which would make the diff not look right
 
819
        self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
820
                              [(0, 0, 6), (6, 11, 10)])
 
821
 
 
822
        # make sure it supports passing in lists
 
823
        self.assertDiffBlocks(
 
824
                   ['hello there\n',
 
825
                    'world\n',
 
826
                    'how are you today?\n'],
 
827
                   ['hello there\n',
 
828
                    'how are you today?\n'],
 
829
                [(0, 0, 1), (2, 1, 1)])
 
830
 
 
831
        # non unique lines surrounded by non-matching lines
 
832
        # won't be found
 
833
        self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
 
834
 
 
835
        # But they only need to be locally unique
 
836
        self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
 
837
 
 
838
        # non unique blocks won't be matched
 
839
        self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
 
840
 
 
841
        # but locally unique ones will
 
842
        self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
 
843
                                              (5,4,1), (7,5,2), (10,8,1)])
 
844
 
 
845
        self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
 
846
        self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
 
847
        self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
 
848
 
 
849
    def test_matching_blocks_tuples(self):
 
850
        # Some basic matching tests
 
851
        self.assertDiffBlocks([], [], [])
 
852
        self.assertDiffBlocks([('a',), ('b',), ('c,')], [], [])
 
853
        self.assertDiffBlocks([], [('a',), ('b',), ('c,')], [])
 
854
        self.assertDiffBlocks([('a',), ('b',), ('c,')],
 
855
                              [('a',), ('b',), ('c,')],
 
856
                              [(0, 0, 3)])
 
857
        self.assertDiffBlocks([('a',), ('b',), ('c,')],
 
858
                              [('a',), ('b',), ('d,')],
 
859
                              [(0, 0, 2)])
 
860
        self.assertDiffBlocks([('d',), ('b',), ('c,')],
 
861
                              [('a',), ('b',), ('c,')],
 
862
                              [(1, 1, 2)])
 
863
        self.assertDiffBlocks([('d',), ('a',), ('b',), ('c,')],
 
864
                              [('a',), ('b',), ('c,')],
 
865
                              [(1, 0, 3)])
 
866
        self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
 
867
                              [('a', 'b'), ('c', 'X'), ('e', 'f')],
 
868
                              [(0, 0, 1), (2, 2, 1)])
 
869
        self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
 
870
                              [('a', 'b'), ('c', 'dX'), ('e', 'f')],
 
871
                              [(0, 0, 1), (2, 2, 1)])
 
872
 
 
873
    def test_opcodes(self):
 
874
        def chk_ops(a, b, expected_codes):
 
875
            s = self._PatienceSequenceMatcher(None, a, b)
 
876
            self.assertEquals(expected_codes, s.get_opcodes())
 
877
 
 
878
        chk_ops('', '', [])
 
879
        chk_ops([], [], [])
 
880
        chk_ops('abc', '', [('delete', 0,3, 0,0)])
 
881
        chk_ops('', 'abc', [('insert', 0,0, 0,3)])
 
882
        chk_ops('abcd', 'abcd', [('equal',    0,4, 0,4)])
 
883
        chk_ops('abcd', 'abce', [('equal',   0,3, 0,3),
 
884
                                 ('replace', 3,4, 3,4)
 
885
                                ])
 
886
        chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
 
887
                                 ('equal',  1,4, 0,3),
 
888
                                 ('insert', 4,4, 3,4)
 
889
                                ])
 
890
        chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
 
891
                                  ('equal',  1,5, 0,4)
 
892
                                 ])
 
893
        chk_ops('abcde', 'abXde', [('equal',   0,2, 0,2),
 
894
                                   ('replace', 2,3, 2,3),
 
895
                                   ('equal',   3,5, 3,5)
 
896
                                  ])
 
897
        chk_ops('abcde', 'abXYZde', [('equal',   0,2, 0,2),
 
898
                                     ('replace', 2,3, 2,5),
 
899
                                     ('equal',   3,5, 5,7)
 
900
                                    ])
 
901
        chk_ops('abde', 'abXYZde', [('equal',  0,2, 0,2),
 
902
                                    ('insert', 2,2, 2,5),
 
903
                                    ('equal',  2,4, 5,7)
 
904
                                   ])
 
905
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
906
                [('equal',  0,6,  0,6),
 
907
                 ('insert', 6,6,  6,11),
 
908
                 ('equal',  6,16, 11,21)
 
909
                ])
 
910
        chk_ops(
 
911
                [ 'hello there\n'
 
912
                , 'world\n'
 
913
                , 'how are you today?\n'],
 
914
                [ 'hello there\n'
 
915
                , 'how are you today?\n'],
 
916
                [('equal',  0,1, 0,1),
 
917
                 ('delete', 1,2, 1,1),
 
918
                 ('equal',  2,3, 1,2),
 
919
                ])
 
920
        chk_ops('aBccDe', 'abccde', 
 
921
                [('equal',   0,1, 0,1),
 
922
                 ('replace', 1,5, 1,5),
 
923
                 ('equal',   5,6, 5,6),
 
924
                ])
 
925
        chk_ops('aBcDec', 'abcdec', 
 
926
                [('equal',   0,1, 0,1),
 
927
                 ('replace', 1,2, 1,2),
 
928
                 ('equal',   2,3, 2,3),
 
929
                 ('replace', 3,4, 3,4),
 
930
                 ('equal',   4,6, 4,6),
 
931
                ])
 
932
        chk_ops('aBcdEcdFg', 'abcdecdfg', 
 
933
                [('equal',   0,1, 0,1),
 
934
                 ('replace', 1,8, 1,8),
 
935
                 ('equal',   8,9, 8,9)
 
936
                ])
 
937
        chk_ops('aBcdEeXcdFg', 'abcdecdfg', 
 
938
                [('equal',   0,1, 0,1),
 
939
                 ('replace', 1,2, 1,2),
 
940
                 ('equal',   2,4, 2,4),
 
941
                 ('delete', 4,5, 4,4),
 
942
                 ('equal',   5,6, 4,5),
 
943
                 ('delete', 6,7, 5,5),
 
944
                 ('equal',   7,9, 5,7),
 
945
                 ('replace', 9,10, 7,8),
 
946
                 ('equal',   10,11, 8,9)
 
947
                ])
 
948
 
 
949
    def test_grouped_opcodes(self):
 
950
        def chk_ops(a, b, expected_codes, n=3):
 
951
            s = self._PatienceSequenceMatcher(None, a, b)
 
952
            self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
 
953
 
 
954
        chk_ops('', '', [])
 
955
        chk_ops([], [], [])
 
956
        chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
 
957
        chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
 
958
        chk_ops('abcd', 'abcd', [])
 
959
        chk_ops('abcd', 'abce', [[('equal',   0,3, 0,3),
 
960
                                  ('replace', 3,4, 3,4)
 
961
                                 ]])
 
962
        chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
 
963
                                 ('equal',  1,4, 0,3),
 
964
                                 ('insert', 4,4, 3,4)
 
965
                                ]])
 
966
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
967
                [[('equal',  3,6, 3,6),
 
968
                  ('insert', 6,6, 6,11),
 
969
                  ('equal',  6,9, 11,14)
 
970
                  ]])
 
971
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
972
                [[('equal',  2,6, 2,6),
 
973
                  ('insert', 6,6, 6,11),
 
974
                  ('equal',  6,10, 11,15)
 
975
                  ]], 4)
 
976
        chk_ops('Xabcdef', 'abcdef',
 
977
                [[('delete', 0,1, 0,0),
 
978
                  ('equal',  1,4, 0,3)
 
979
                  ]])
 
980
        chk_ops('abcdef', 'abcdefX',
 
981
                [[('equal',  3,6, 3,6),
 
982
                  ('insert', 6,6, 6,7)
 
983
                  ]])
 
984
 
 
985
 
 
986
    def test_multiple_ranges(self):
 
987
        # There was an earlier bug where we used a bad set of ranges,
 
988
        # this triggers that specific bug, to make sure it doesn't regress
 
989
        self.assertDiffBlocks('abcdefghijklmnop',
 
990
                              'abcXghiYZQRSTUVWXYZijklmnop',
 
991
                              [(0, 0, 3), (6, 4, 3), (9, 20, 7)])
 
992
 
 
993
        self.assertDiffBlocks('ABCd efghIjk  L',
 
994
                              'AxyzBCn mo pqrstuvwI1 2  L',
 
995
                              [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
996
 
 
997
        # These are rot13 code snippets.
 
998
        self.assertDiffBlocks('''\
 
999
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
 
1000
    """
 
1001
    gnxrf_netf = ['svyr*']
 
1002
    gnxrf_bcgvbaf = ['ab-erphefr']
 
1003
  
 
1004
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
 
1005
        sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
 
1006
        vs vf_dhvrg():
 
1007
            ercbegre = nqq_ercbegre_ahyy
 
1008
        ryfr:
 
1009
            ercbegre = nqq_ercbegre_cevag
 
1010
        fzneg_nqq(svyr_yvfg, abg ab_erphefr, ercbegre)
 
1011
 
 
1012
 
 
1013
pynff pzq_zxqve(Pbzznaq):
 
1014
'''.splitlines(True), '''\
 
1015
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
 
1016
 
 
1017
    --qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl 
 
1018
    nqq gurz.
 
1019
    """
 
1020
    gnxrf_netf = ['svyr*']
 
1021
    gnxrf_bcgvbaf = ['ab-erphefr', 'qel-eha']
 
1022
 
 
1023
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr, qel_eha=Snyfr):
 
1024
        vzcbeg omeyvo.nqq
 
1025
 
 
1026
        vs qel_eha:
 
1027
            vs vf_dhvrg():
 
1028
                # Guvf vf cbvagyrff, ohg V'q engure abg envfr na reebe
 
1029
                npgvba = omeyvo.nqq.nqq_npgvba_ahyy
 
1030
            ryfr:
 
1031
  npgvba = omeyvo.nqq.nqq_npgvba_cevag
 
1032
        ryvs vf_dhvrg():
 
1033
            npgvba = omeyvo.nqq.nqq_npgvba_nqq
 
1034
        ryfr:
 
1035
       npgvba = omeyvo.nqq.nqq_npgvba_nqq_naq_cevag
 
1036
 
 
1037
        omeyvo.nqq.fzneg_nqq(svyr_yvfg, abg ab_erphefr, npgvba)
 
1038
 
 
1039
 
 
1040
pynff pzq_zxqve(Pbzznaq):
 
1041
'''.splitlines(True)
 
1042
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
1043
 
 
1044
    def test_patience_unified_diff(self):
 
1045
        txt_a = ['hello there\n',
 
1046
                 'world\n',
 
1047
                 'how are you today?\n']
 
1048
        txt_b = ['hello there\n',
 
1049
                 'how are you today?\n']
 
1050
        unified_diff = bzrlib.patiencediff.unified_diff
 
1051
        psm = self._PatienceSequenceMatcher
 
1052
        self.assertEquals([ '---  \n',
 
1053
                           '+++  \n',
 
1054
                           '@@ -1,3 +1,2 @@\n',
 
1055
                           ' hello there\n',
 
1056
                           '-world\n',
 
1057
                           ' how are you today?\n'
 
1058
                          ]
 
1059
                          , list(unified_diff(txt_a, txt_b,
 
1060
                                 sequencematcher=psm)))
 
1061
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
 
1062
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
 
1063
        # This is the result with LongestCommonSubstring matching
 
1064
        self.assertEquals(['---  \n',
 
1065
                           '+++  \n',
 
1066
                           '@@ -1,6 +1,11 @@\n',
 
1067
                           ' a\n',
 
1068
                           ' b\n',
 
1069
                           ' c\n',
 
1070
                           '+d\n',
 
1071
                           '+e\n',
 
1072
                           '+f\n',
 
1073
                           '+x\n',
 
1074
                           '+y\n',
 
1075
                           ' d\n',
 
1076
                           ' e\n',
 
1077
                           ' f\n']
 
1078
                          , list(unified_diff(txt_a, txt_b)))
 
1079
        # And the patience diff
 
1080
        self.assertEquals(['---  \n',
 
1081
                           '+++  \n',
 
1082
                           '@@ -4,6 +4,11 @@\n',
 
1083
                           ' d\n',
 
1084
                           ' e\n',
 
1085
                           ' f\n',
 
1086
                           '+x\n',
 
1087
                           '+y\n',
 
1088
                           '+d\n',
 
1089
                           '+e\n',
 
1090
                           '+f\n',
 
1091
                           ' g\n',
 
1092
                           ' h\n',
 
1093
                           ' i\n',
 
1094
                          ]
 
1095
                          , list(unified_diff(txt_a, txt_b,
 
1096
                                 sequencematcher=psm)))
 
1097
 
 
1098
 
 
1099
class TestPatienceDiffLib_c(TestPatienceDiffLib):
 
1100
 
 
1101
    _test_needs_features = [CompiledPatienceDiffFeature]
 
1102
 
 
1103
    def setUp(self):
 
1104
        super(TestPatienceDiffLib_c, self).setUp()
 
1105
        import bzrlib._patiencediff_c
 
1106
        self._unique_lcs = bzrlib._patiencediff_c.unique_lcs_c
 
1107
        self._recurse_matches = bzrlib._patiencediff_c.recurse_matches_c
 
1108
        self._PatienceSequenceMatcher = \
 
1109
            bzrlib._patiencediff_c.PatienceSequenceMatcher_c
 
1110
 
 
1111
    def test_unhashable(self):
 
1112
        """We should get a proper exception here."""
 
1113
        # We need to be able to hash items in the sequence, lists are
 
1114
        # unhashable, and thus cannot be diffed
 
1115
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
 
1116
                                         None, [[]], [])
 
1117
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
 
1118
                                         None, ['valid', []], [])
 
1119
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
 
1120
                                         None, ['valid'], [[]])
 
1121
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
 
1122
                                         None, ['valid'], ['valid', []])
 
1123
 
 
1124
 
 
1125
class TestPatienceDiffLibFiles(TestCaseInTempDir):
 
1126
 
 
1127
    def setUp(self):
 
1128
        super(TestPatienceDiffLibFiles, self).setUp()
 
1129
        self._PatienceSequenceMatcher = \
 
1130
            bzrlib._patiencediff_py.PatienceSequenceMatcher_py
 
1131
 
 
1132
    def test_patience_unified_diff_files(self):
 
1133
        txt_a = ['hello there\n',
 
1134
                 'world\n',
 
1135
                 'how are you today?\n']
 
1136
        txt_b = ['hello there\n',
 
1137
                 'how are you today?\n']
 
1138
        open('a1', 'wb').writelines(txt_a)
 
1139
        open('b1', 'wb').writelines(txt_b)
 
1140
 
 
1141
        unified_diff_files = bzrlib.patiencediff.unified_diff_files
 
1142
        psm = self._PatienceSequenceMatcher
 
1143
        self.assertEquals(['--- a1 \n',
 
1144
                           '+++ b1 \n',
 
1145
                           '@@ -1,3 +1,2 @@\n',
 
1146
                           ' hello there\n',
 
1147
                           '-world\n',
 
1148
                           ' how are you today?\n',
 
1149
                          ]
 
1150
                          , list(unified_diff_files('a1', 'b1',
 
1151
                                 sequencematcher=psm)))
 
1152
 
 
1153
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
 
1154
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
 
1155
        open('a2', 'wb').writelines(txt_a)
 
1156
        open('b2', 'wb').writelines(txt_b)
 
1157
 
 
1158
        # This is the result with LongestCommonSubstring matching
 
1159
        self.assertEquals(['--- a2 \n',
 
1160
                           '+++ b2 \n',
 
1161
                           '@@ -1,6 +1,11 @@\n',
 
1162
                           ' a\n',
 
1163
                           ' b\n',
 
1164
                           ' c\n',
 
1165
                           '+d\n',
 
1166
                           '+e\n',
 
1167
                           '+f\n',
 
1168
                           '+x\n',
 
1169
                           '+y\n',
 
1170
                           ' d\n',
 
1171
                           ' e\n',
 
1172
                           ' f\n']
 
1173
                          , list(unified_diff_files('a2', 'b2')))
 
1174
 
 
1175
        # And the patience diff
 
1176
        self.assertEquals(['--- a2 \n',
 
1177
                           '+++ b2 \n',
 
1178
                           '@@ -4,6 +4,11 @@\n',
 
1179
                           ' d\n',
 
1180
                           ' e\n',
 
1181
                           ' f\n',
 
1182
                           '+x\n',
 
1183
                           '+y\n',
 
1184
                           '+d\n',
 
1185
                           '+e\n',
 
1186
                           '+f\n',
 
1187
                           ' g\n',
 
1188
                           ' h\n',
 
1189
                           ' i\n',
 
1190
                          ]
 
1191
                          , list(unified_diff_files('a2', 'b2',
 
1192
                                 sequencematcher=psm)))
 
1193
 
 
1194
 
 
1195
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
 
1196
 
 
1197
    _test_needs_features = [CompiledPatienceDiffFeature]
 
1198
 
 
1199
    def setUp(self):
 
1200
        super(TestPatienceDiffLibFiles_c, self).setUp()
 
1201
        import bzrlib._patiencediff_c
 
1202
        self._PatienceSequenceMatcher = \
 
1203
            bzrlib._patiencediff_c.PatienceSequenceMatcher_c
 
1204
 
 
1205
 
 
1206
class TestUsingCompiledIfAvailable(TestCase):
 
1207
 
 
1208
    def test_PatienceSequenceMatcher(self):
 
1209
        if CompiledPatienceDiffFeature.available():
 
1210
            from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
 
1211
            self.assertIs(PatienceSequenceMatcher_c,
 
1212
                          bzrlib.patiencediff.PatienceSequenceMatcher)
 
1213
        else:
 
1214
            from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
 
1215
            self.assertIs(PatienceSequenceMatcher_py,
 
1216
                          bzrlib.patiencediff.PatienceSequenceMatcher)
 
1217
 
 
1218
    def test_unique_lcs(self):
 
1219
        if CompiledPatienceDiffFeature.available():
 
1220
            from bzrlib._patiencediff_c import unique_lcs_c
 
1221
            self.assertIs(unique_lcs_c,
 
1222
                          bzrlib.patiencediff.unique_lcs)
 
1223
        else:
 
1224
            from bzrlib._patiencediff_py import unique_lcs_py
 
1225
            self.assertIs(unique_lcs_py,
 
1226
                          bzrlib.patiencediff.unique_lcs)
 
1227
 
 
1228
    def test_recurse_matches(self):
 
1229
        if CompiledPatienceDiffFeature.available():
 
1230
            from bzrlib._patiencediff_c import recurse_matches_c
 
1231
            self.assertIs(recurse_matches_c,
 
1232
                          bzrlib.patiencediff.recurse_matches)
 
1233
        else:
 
1234
            from bzrlib._patiencediff_py import recurse_matches_py
 
1235
            self.assertIs(recurse_matches_py,
 
1236
                          bzrlib.patiencediff.recurse_matches)
 
1237
 
 
1238
 
 
1239
class TestDiffFromTool(TestCaseWithTransport):
 
1240
 
 
1241
    def test_from_string(self):
 
1242
        diff_obj = DiffFromTool.from_string('diff', None, None, None)
 
1243
        self.addCleanup(diff_obj.finish)
 
1244
        self.assertEqual(['diff', '%(old_path)s', '%(new_path)s'],
 
1245
            diff_obj.command_template)
 
1246
        diff_obj = DiffFromTool.from_string('diff -u\\ 5', None, None, None)
 
1247
        self.assertEqual(['diff', '-u 5', '%(old_path)s', '%(new_path)s'],
 
1248
                         diff_obj.command_template)
 
1249
        self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
 
1250
                         diff_obj._get_command('old-path', 'new-path'))
 
1251
 
 
1252
    def test_execute(self):
 
1253
        output = StringIO()
 
1254
        diff_obj = DiffFromTool(['python', '-c',
 
1255
                                 'print "%(old_path)s %(new_path)s"'],
 
1256
                                None, None, output)
 
1257
        self.addCleanup(diff_obj.finish)
 
1258
        diff_obj._execute('old', 'new')
 
1259
        self.assertEqual(output.getvalue(), 'old new\n')
 
1260
 
 
1261
    def test_prepare_files(self):
 
1262
        output = StringIO()
 
1263
        tree = self.make_branch_and_tree('tree')
 
1264
        self.build_tree_contents([('tree/file', 'oldcontent')])
 
1265
        tree.add('file', 'file-id')
 
1266
        tree.commit('old tree')
 
1267
        self.build_tree_contents([('tree/file', 'newcontent')])
 
1268
        old_tree = tree.basis_tree()
 
1269
        old_tree.lock_read()
 
1270
        self.addCleanup(old_tree.unlock)
 
1271
        diff_obj = DiffFromTool(['python', '-c',
 
1272
                                 'print "%(old_path)s %(new_path)s"'],
 
1273
                                old_tree, tree, output)
 
1274
        self.addCleanup(diff_obj.finish)
 
1275
        self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
 
1276
        old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
 
1277
                                                     'newname')
 
1278
        self.assertContainsRe(old_path, 'old/oldname$')
 
1279
        self.assertContainsRe(new_path, 'new/newname$')
 
1280
        self.assertFileEqual('oldcontent', old_path)
 
1281
        self.assertFileEqual('newcontent', new_path)
 
1282
        # make sure we can create files with the same parent directories
 
1283
        diff_obj._prepare_files('file-id', 'oldname2', 'newname2')