/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: Canonical.com Patch Queue Manager
  • Date: 2008-06-09 21:16:46 UTC
  • mfrom: (3453.3.4 mpdiffs_235687)
  • Revision ID: pqm@pqm.ubuntu.com-20080609211646-amc2rr2zi50omr8m
(Daniel Fischer) VersionedFile.make_mpdiffs() needs to raise the
        right exception, (bug #235687)

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