~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

Make test_bench_history and _get_bzr_source_tree tolerant of UnknownFormatError for the bzr workingtree.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Development Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import os
18
18
from cStringIO import StringIO
 
19
import errno
19
20
import subprocess
20
 
import sys
21
 
import tempfile
22
 
 
23
 
from bzrlib import (
24
 
    diff,
25
 
    errors,
26
 
    osutils,
27
 
    patiencediff,
28
 
    _patiencediff_py,
29
 
    revision as _mod_revision,
30
 
    revisionspec,
31
 
    revisiontree,
32
 
    tests,
33
 
    transform,
34
 
    )
35
 
from bzrlib.symbol_versioning import deprecated_in
36
 
from bzrlib.tests import test_win32utils
37
 
 
38
 
 
39
 
class _AttribFeature(tests.Feature):
40
 
 
41
 
    def _probe(self):
42
 
        if (sys.platform not in ('cygwin', 'win32')):
43
 
            return False
44
 
        try:
45
 
            proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
46
 
        except OSError, e:
47
 
            return False
48
 
        return (0 == proc.wait())
49
 
 
50
 
    def feature_name(self):
51
 
        return 'attrib Windows command-line tool'
52
 
 
53
 
AttribFeature = _AttribFeature()
54
 
 
55
 
 
56
 
compiled_patiencediff_feature = tests.ModuleAvailableFeature(
57
 
                                    'bzrlib._patiencediff_c')
 
21
from tempfile import TemporaryFile
 
22
 
 
23
from bzrlib.diff import internal_diff, external_diff, show_diff_trees
 
24
from bzrlib.errors import BinaryFile, NoDiff
 
25
import bzrlib.patiencediff
 
26
from bzrlib.tests import (TestCase, TestCaseWithTransport,
 
27
                          TestCaseInTempDir, TestSkipped)
58
28
 
59
29
 
60
30
def udiff_lines(old, new, allow_binary=False):
61
31
    output = StringIO()
62
 
    diff.internal_diff('old', old, 'new', new, output, allow_binary)
 
32
    internal_diff('old', old, 'new', new, output, allow_binary)
63
33
    output.seek(0, 0)
64
34
    return output.readlines()
65
35
 
69
39
        # StringIO has no fileno, so it tests a different codepath
70
40
        output = StringIO()
71
41
    else:
72
 
        output = tempfile.TemporaryFile()
 
42
        output = TemporaryFile()
73
43
    try:
74
 
        diff.external_diff('old', old, 'new', new, output, diff_opts=['-u'])
75
 
    except errors.NoDiff:
76
 
        raise tests.TestSkipped('external "diff" not present to test')
 
44
        external_diff('old', old, 'new', new, output, diff_opts=['-u'])
 
45
    except NoDiff:
 
46
        raise TestSkipped('external "diff" not present to test')
77
47
    output.seek(0, 0)
78
48
    lines = output.readlines()
79
49
    output.close()
80
50
    return lines
81
51
 
82
52
 
83
 
class TestDiff(tests.TestCase):
 
53
class TestDiff(TestCase):
84
54
 
85
55
    def test_add_nl(self):
86
56
        """diff generates a valid diff for patches that add a newline"""
122
92
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
123
93
 
124
94
    def test_binary_lines(self):
125
 
        empty = []
126
 
        uni_lines = [1023 * 'a' + '\x00']
127
 
        self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines , empty)
128
 
        self.assertRaises(errors.BinaryFile, udiff_lines, empty, uni_lines)
129
 
        udiff_lines(uni_lines , empty, allow_binary=True)
130
 
        udiff_lines(empty, uni_lines, allow_binary=True)
 
95
        self.assertRaises(BinaryFile, udiff_lines, [1023 * 'a' + '\x00'], [])
 
96
        self.assertRaises(BinaryFile, udiff_lines, [], [1023 * 'a' + '\x00'])
 
97
        udiff_lines([1023 * 'a' + '\x00'], [], allow_binary=True)
 
98
        udiff_lines([], [1023 * 'a' + '\x00'], allow_binary=True)
131
99
 
132
100
    def test_external_diff(self):
133
101
        lines = external_udiff_lines(['boo\n'], ['goo\n'])
143
111
        self.check_patch(lines)
144
112
 
145
113
    def test_external_diff_binary_lang_c(self):
146
 
        old_env = {}
147
 
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
148
 
            old_env[lang] = osutils.set_or_unset_env(lang, 'C')
 
114
        orig_lang = os.environ.get('LANG')
149
115
        try:
 
116
            os.environ['LANG'] = 'C'
150
117
            lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
151
 
            # Older versions of diffutils say "Binary files", newer
152
 
            # versions just say "Files".
153
 
            self.assertContainsRe(lines[0],
154
 
                                  '(Binary f|F)iles old and new differ\n')
155
 
            self.assertEquals(lines[1:], ['\n'])
 
118
            self.assertEqual(['Binary files old and new differ\n', '\n'], lines)
156
119
        finally:
157
 
            for lang, old_val in old_env.iteritems():
158
 
                osutils.set_or_unset_env(lang, old_val)
 
120
            if orig_lang is None:
 
121
                del os.environ['LANG']
 
122
            else:
 
123
                os.environ['LANG'] = orig_lang
159
124
 
160
125
    def test_no_external_diff(self):
161
126
        """Check that NoDiff is raised when diff is not available"""
163
128
        orig_path = os.environ['PATH']
164
129
        try:
165
130
            os.environ['PATH'] = ''
166
 
            self.assertRaises(errors.NoDiff, diff.external_diff,
 
131
            self.assertRaises(NoDiff, external_diff,
167
132
                              'old', ['boo\n'], 'new', ['goo\n'],
168
133
                              StringIO(), diff_opts=['-u'])
169
134
        finally:
170
135
            os.environ['PATH'] = orig_path
171
 
 
 
136
        
172
137
    def test_internal_diff_default(self):
173
138
        # Default internal diff encoding is utf8
174
139
        output = StringIO()
175
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
176
 
                           u'new_\xe5', ['new_text\n'], output)
 
140
        internal_diff(u'old_\xb5', ['old_text\n'],
 
141
                    u'new_\xe5', ['new_text\n'], output)
177
142
        lines = output.getvalue().splitlines(True)
178
143
        self.check_patch(lines)
179
144
        self.assertEquals(['--- old_\xc2\xb5\n',
187
152
 
188
153
    def test_internal_diff_utf8(self):
189
154
        output = StringIO()
190
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
191
 
                           u'new_\xe5', ['new_text\n'], output,
192
 
                           path_encoding='utf8')
 
155
        internal_diff(u'old_\xb5', ['old_text\n'],
 
156
                    u'new_\xe5', ['new_text\n'], output,
 
157
                    path_encoding='utf8')
193
158
        lines = output.getvalue().splitlines(True)
194
159
        self.check_patch(lines)
195
160
        self.assertEquals(['--- old_\xc2\xb5\n',
203
168
 
204
169
    def test_internal_diff_iso_8859_1(self):
205
170
        output = StringIO()
206
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
207
 
                           u'new_\xe5', ['new_text\n'], output,
208
 
                           path_encoding='iso-8859-1')
 
171
        internal_diff(u'old_\xb5', ['old_text\n'],
 
172
                    u'new_\xe5', ['new_text\n'], output,
 
173
                    path_encoding='iso-8859-1')
209
174
        lines = output.getvalue().splitlines(True)
210
175
        self.check_patch(lines)
211
176
        self.assertEquals(['--- old_\xb5\n',
217
182
                          ]
218
183
                          , lines)
219
184
 
220
 
    def test_internal_diff_no_content(self):
221
 
        output = StringIO()
222
 
        diff.internal_diff(u'old', [], u'new', [], output)
223
 
        self.assertEqual('', output.getvalue())
224
 
 
225
 
    def test_internal_diff_no_changes(self):
226
 
        output = StringIO()
227
 
        diff.internal_diff(u'old', ['text\n', 'contents\n'],
228
 
                           u'new', ['text\n', 'contents\n'],
229
 
                           output)
230
 
        self.assertEqual('', output.getvalue())
231
 
 
232
185
    def test_internal_diff_returns_bytes(self):
233
186
        import StringIO
234
187
        output = StringIO.StringIO()
235
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
236
 
                            u'new_\xe5', ['new_text\n'], output)
 
188
        internal_diff(u'old_\xb5', ['old_text\n'],
 
189
                    u'new_\xe5', ['new_text\n'], output)
237
190
        self.failUnless(isinstance(output.getvalue(), str),
238
191
            'internal_diff should return bytestrings')
239
192
 
240
193
 
241
 
class TestDiffFiles(tests.TestCaseInTempDir):
 
194
class TestDiffFiles(TestCaseInTempDir):
242
195
 
243
196
    def test_external_diff_binary(self):
244
197
        """The output when using external diff should use diff's i18n error"""
245
198
        # Make sure external_diff doesn't fail in the current LANG
246
199
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
247
200
 
248
 
        cmd = ['diff', '-u', '--binary', 'old', 'new']
 
201
        cmd = ['diff', '-u', 'old', 'new']
249
202
        open('old', 'wb').write('\x00foobar\n')
250
203
        open('new', 'wb').write('foo\x00bar\n')
251
204
        pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
257
210
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
258
211
 
259
212
 
260
 
class TestShowDiffTreesHelper(tests.TestCaseWithTransport):
261
 
    """Has a helper for running show_diff_trees"""
262
 
 
263
 
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
264
 
        output = StringIO()
265
 
        if working_tree is not None:
266
 
            extra_trees = (working_tree,)
267
 
        else:
268
 
            extra_trees = ()
269
 
        diff.show_diff_trees(tree1, tree2, output,
270
 
                             specific_files=specific_files,
271
 
                             extra_trees=extra_trees, old_label='old/',
272
 
                             new_label='new/')
273
 
        return output.getvalue()
274
 
 
275
 
 
276
 
class TestDiffDates(TestShowDiffTreesHelper):
 
213
class TestDiffDates(TestCaseWithTransport):
277
214
 
278
215
    def setUp(self):
279
216
        super(TestDiffDates, self).setUp()
313
250
        # set the date stamps for files in the working tree to known values
314
251
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
315
252
 
 
253
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
 
254
        output = StringIO()
 
255
        if working_tree is not None:
 
256
            extra_trees = (working_tree,)
 
257
        else:
 
258
            extra_trees = ()
 
259
        show_diff_trees(tree1, tree2, output, specific_files=specific_files,
 
260
                        extra_trees=extra_trees, old_label='old/', 
 
261
                        new_label='new/')
 
262
        return output.getvalue()
 
263
 
316
264
    def test_diff_rev_tree_working_tree(self):
317
265
        output = self.get_diff(self.wt.basis_tree(), self.wt)
318
266
        # note that the date for old/file1 is from rev 2 rather than from
340
288
+file2 contents at rev 3
341
289
 
342
290
''')
343
 
 
 
291
        
344
292
    def test_diff_add_files(self):
345
 
        tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
 
293
        tree1 = self.b.repository.revision_tree(None)
346
294
        tree2 = self.b.repository.revision_tree('rev-1')
347
295
        output = self.get_diff(tree1, tree2)
348
296
        # the files have the epoch time stamp for the tree in which
382
330
        self.wt.rename_one('file1', 'file1b')
383
331
        old_tree = self.b.repository.revision_tree('rev-1')
384
332
        new_tree = self.b.repository.revision_tree('rev-4')
385
 
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'],
 
333
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'], 
386
334
                            working_tree=self.wt)
387
335
        self.assertContainsRe(out, 'file1\t')
388
336
 
394
342
        self.wt.rename_one('file1', 'dir1/file1')
395
343
        old_tree = self.b.repository.revision_tree('rev-1')
396
344
        new_tree = self.b.repository.revision_tree('rev-4')
397
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'],
 
345
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'], 
398
346
                            working_tree=self.wt)
399
347
        self.assertContainsRe(out, 'file1\t')
400
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'],
 
348
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'], 
401
349
                            working_tree=self.wt)
402
350
        self.assertNotContainsRe(out, 'file1\t')
403
351
 
404
352
 
405
 
 
406
 
class TestShowDiffTrees(TestShowDiffTreesHelper):
407
 
    """Direct tests for show_diff_trees"""
408
 
 
409
 
    def test_modified_file(self):
410
 
        """Test when a file is modified."""
411
 
        tree = self.make_branch_and_tree('tree')
412
 
        self.build_tree_contents([('tree/file', 'contents\n')])
413
 
        tree.add(['file'], ['file-id'])
414
 
        tree.commit('one', rev_id='rev-1')
415
 
 
416
 
        self.build_tree_contents([('tree/file', 'new contents\n')])
417
 
        d = self.get_diff(tree.basis_tree(), tree)
418
 
        self.assertContainsRe(d, "=== modified file 'file'\n")
419
 
        self.assertContainsRe(d, '--- old/file\t')
420
 
        self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
421
 
        self.assertContainsRe(d, '-contents\n'
422
 
                                 '\\+new contents\n')
423
 
 
424
 
    def test_modified_file_in_renamed_dir(self):
425
 
        """Test when a file is modified in a renamed directory."""
426
 
        tree = self.make_branch_and_tree('tree')
427
 
        self.build_tree(['tree/dir/'])
428
 
        self.build_tree_contents([('tree/dir/file', 'contents\n')])
429
 
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
430
 
        tree.commit('one', rev_id='rev-1')
431
 
 
432
 
        tree.rename_one('dir', 'other')
433
 
        self.build_tree_contents([('tree/other/file', 'new contents\n')])
434
 
        d = self.get_diff(tree.basis_tree(), tree)
435
 
        self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
436
 
        self.assertContainsRe(d, "=== modified file 'other/file'\n")
437
 
        # XXX: This is technically incorrect, because it used to be at another
438
 
        # location. What to do?
439
 
        self.assertContainsRe(d, '--- old/dir/file\t')
440
 
        self.assertContainsRe(d, '\\+\\+\\+ new/other/file\t')
441
 
        self.assertContainsRe(d, '-contents\n'
442
 
                                 '\\+new contents\n')
443
 
 
444
 
    def test_renamed_directory(self):
445
 
        """Test when only a directory is only renamed."""
446
 
        tree = self.make_branch_and_tree('tree')
447
 
        self.build_tree(['tree/dir/'])
448
 
        self.build_tree_contents([('tree/dir/file', 'contents\n')])
449
 
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
450
 
        tree.commit('one', rev_id='rev-1')
451
 
 
452
 
        tree.rename_one('dir', 'newdir')
453
 
        d = self.get_diff(tree.basis_tree(), tree)
454
 
        # Renaming a directory should be a single "you renamed this dir" even
455
 
        # when there are files inside.
456
 
        self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
457
 
 
458
 
    def test_renamed_file(self):
459
 
        """Test when a file is only renamed."""
460
 
        tree = self.make_branch_and_tree('tree')
461
 
        self.build_tree_contents([('tree/file', 'contents\n')])
462
 
        tree.add(['file'], ['file-id'])
463
 
        tree.commit('one', rev_id='rev-1')
464
 
 
465
 
        tree.rename_one('file', 'newname')
466
 
        d = self.get_diff(tree.basis_tree(), tree)
467
 
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
468
 
        # We shouldn't have a --- or +++ line, because there is no content
469
 
        # change
470
 
        self.assertNotContainsRe(d, '---')
471
 
 
472
 
    def test_renamed_and_modified_file(self):
473
 
        """Test when a file is only renamed."""
474
 
        tree = self.make_branch_and_tree('tree')
475
 
        self.build_tree_contents([('tree/file', 'contents\n')])
476
 
        tree.add(['file'], ['file-id'])
477
 
        tree.commit('one', rev_id='rev-1')
478
 
 
479
 
        tree.rename_one('file', 'newname')
480
 
        self.build_tree_contents([('tree/newname', 'new contents\n')])
481
 
        d = self.get_diff(tree.basis_tree(), tree)
482
 
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
483
 
        self.assertContainsRe(d, '--- old/file\t')
484
 
        self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
485
 
        self.assertContainsRe(d, '-contents\n'
486
 
                                 '\\+new contents\n')
487
 
 
488
 
 
489
 
    def test_internal_diff_exec_property(self):
490
 
        tree = self.make_branch_and_tree('tree')
491
 
 
492
 
        tt = transform.TreeTransform(tree)
493
 
        tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
494
 
        tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
495
 
        tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
496
 
        tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
497
 
        tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
498
 
        tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
499
 
        tt.apply()
500
 
        tree.commit('one', rev_id='rev-1')
501
 
 
502
 
        tt = transform.TreeTransform(tree)
503
 
        tt.set_executability(False, tt.trans_id_file_id('a-id'))
504
 
        tt.set_executability(True, tt.trans_id_file_id('b-id'))
505
 
        tt.set_executability(False, tt.trans_id_file_id('c-id'))
506
 
        tt.set_executability(True, tt.trans_id_file_id('d-id'))
507
 
        tt.apply()
508
 
        tree.rename_one('c', 'new-c')
509
 
        tree.rename_one('d', 'new-d')
510
 
 
511
 
        d = self.get_diff(tree.basis_tree(), tree)
512
 
 
513
 
        self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
514
 
                                  ".*\+x to -x.*\)")
515
 
        self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
516
 
                                  ".*-x to \+x.*\)")
517
 
        self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
518
 
                                  ".*\+x to -x.*\)")
519
 
        self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
520
 
                                  ".*-x to \+x.*\)")
521
 
        self.assertNotContainsRe(d, r"file 'e'")
522
 
        self.assertNotContainsRe(d, r"file 'f'")
523
 
 
524
 
 
525
 
    def test_binary_unicode_filenames(self):
526
 
        """Test that contents of files are *not* encoded in UTF-8 when there
527
 
        is a binary file in the diff.
528
 
        """
529
 
        # See https://bugs.launchpad.net/bugs/110092.
530
 
        self.requireFeature(tests.UnicodeFilenameFeature)
531
 
 
532
 
        # This bug isn't triggered with cStringIO.
533
 
        from StringIO import StringIO
534
 
        tree = self.make_branch_and_tree('tree')
535
 
        alpha, omega = u'\u03b1', u'\u03c9'
536
 
        alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
537
 
        self.build_tree_contents(
538
 
            [('tree/' + alpha, chr(0)),
539
 
             ('tree/' + omega,
540
 
              ('The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
541
 
        tree.add([alpha], ['file-id'])
542
 
        tree.add([omega], ['file-id-2'])
543
 
        diff_content = StringIO()
544
 
        diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
545
 
        d = diff_content.getvalue()
546
 
        self.assertContainsRe(d, r"=== added file '%s'" % alpha_utf8)
547
 
        self.assertContainsRe(d, "Binary files a/%s.*and b/%s.* differ\n"
548
 
                              % (alpha_utf8, alpha_utf8))
549
 
        self.assertContainsRe(d, r"=== added file '%s'" % omega_utf8)
550
 
        self.assertContainsRe(d, r"--- a/%s" % (omega_utf8,))
551
 
        self.assertContainsRe(d, r"\+\+\+ b/%s" % (omega_utf8,))
552
 
 
553
 
    def test_unicode_filename(self):
554
 
        """Test when the filename are unicode."""
555
 
        self.requireFeature(tests.UnicodeFilenameFeature)
556
 
 
557
 
        alpha, omega = u'\u03b1', u'\u03c9'
558
 
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
559
 
 
560
 
        tree = self.make_branch_and_tree('tree')
561
 
        self.build_tree_contents([('tree/ren_'+alpha, 'contents\n')])
562
 
        tree.add(['ren_'+alpha], ['file-id-2'])
563
 
        self.build_tree_contents([('tree/del_'+alpha, 'contents\n')])
564
 
        tree.add(['del_'+alpha], ['file-id-3'])
565
 
        self.build_tree_contents([('tree/mod_'+alpha, 'contents\n')])
566
 
        tree.add(['mod_'+alpha], ['file-id-4'])
567
 
 
568
 
        tree.commit('one', rev_id='rev-1')
569
 
 
570
 
        tree.rename_one('ren_'+alpha, 'ren_'+omega)
571
 
        tree.remove('del_'+alpha)
572
 
        self.build_tree_contents([('tree/add_'+alpha, 'contents\n')])
573
 
        tree.add(['add_'+alpha], ['file-id'])
574
 
        self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
575
 
 
576
 
        d = self.get_diff(tree.basis_tree(), tree)
577
 
        self.assertContainsRe(d,
578
 
                "=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
579
 
        self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
580
 
        self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
581
 
        self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
582
 
 
583
 
 
584
 
class DiffWasIs(diff.DiffPath):
585
 
 
586
 
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
587
 
        self.to_file.write('was: ')
588
 
        self.to_file.write(self.old_tree.get_file(file_id).read())
589
 
        self.to_file.write('is: ')
590
 
        self.to_file.write(self.new_tree.get_file(file_id).read())
591
 
        pass
592
 
 
593
 
 
594
 
class TestDiffTree(tests.TestCaseWithTransport):
595
 
 
596
 
    def setUp(self):
597
 
        super(TestDiffTree, self).setUp()
598
 
        self.old_tree = self.make_branch_and_tree('old-tree')
599
 
        self.old_tree.lock_write()
600
 
        self.addCleanup(self.old_tree.unlock)
601
 
        self.new_tree = self.make_branch_and_tree('new-tree')
602
 
        self.new_tree.lock_write()
603
 
        self.addCleanup(self.new_tree.unlock)
604
 
        self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
605
 
 
606
 
    def test_diff_text(self):
607
 
        self.build_tree_contents([('old-tree/olddir/',),
608
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
609
 
        self.old_tree.add('olddir')
610
 
        self.old_tree.add('olddir/oldfile', 'file-id')
611
 
        self.build_tree_contents([('new-tree/newdir/',),
612
 
                                  ('new-tree/newdir/newfile', 'new\n')])
613
 
        self.new_tree.add('newdir')
614
 
        self.new_tree.add('newdir/newfile', 'file-id')
615
 
        differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
616
 
        differ.diff_text('file-id', None, 'old label', 'new label')
617
 
        self.assertEqual(
618
 
            '--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
619
 
            differ.to_file.getvalue())
620
 
        differ.to_file.seek(0)
621
 
        differ.diff_text(None, 'file-id', 'old label', 'new label')
622
 
        self.assertEqual(
623
 
            '--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
624
 
            differ.to_file.getvalue())
625
 
        differ.to_file.seek(0)
626
 
        differ.diff_text('file-id', 'file-id', 'old label', 'new label')
627
 
        self.assertEqual(
628
 
            '--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
629
 
            differ.to_file.getvalue())
630
 
 
631
 
    def test_diff_deletion(self):
632
 
        self.build_tree_contents([('old-tree/file', 'contents'),
633
 
                                  ('new-tree/file', 'contents')])
634
 
        self.old_tree.add('file', 'file-id')
635
 
        self.new_tree.add('file', 'file-id')
636
 
        os.unlink('new-tree/file')
637
 
        self.differ.show_diff(None)
638
 
        self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
639
 
 
640
 
    def test_diff_creation(self):
641
 
        self.build_tree_contents([('old-tree/file', 'contents'),
642
 
                                  ('new-tree/file', 'contents')])
643
 
        self.old_tree.add('file', 'file-id')
644
 
        self.new_tree.add('file', 'file-id')
645
 
        os.unlink('old-tree/file')
646
 
        self.differ.show_diff(None)
647
 
        self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
648
 
 
649
 
    def test_diff_symlink(self):
650
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
651
 
        differ.diff_symlink('old target', None)
652
 
        self.assertEqual("=== target was 'old target'\n",
653
 
                         differ.to_file.getvalue())
654
 
 
655
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
656
 
        differ.diff_symlink(None, 'new target')
657
 
        self.assertEqual("=== target is 'new target'\n",
658
 
                         differ.to_file.getvalue())
659
 
 
660
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
661
 
        differ.diff_symlink('old target', 'new target')
662
 
        self.assertEqual("=== target changed 'old target' => 'new target'\n",
663
 
                         differ.to_file.getvalue())
664
 
 
665
 
    def test_diff(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_contents([('new-tree/newdir/',),
671
 
                                  ('new-tree/newdir/newfile', 'new\n')])
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 \+1,1'
678
 
             ' \@\@\n-old\n\+new\n\n')
679
 
 
680
 
    def test_diff_kind_change(self):
681
 
        self.requireFeature(tests.SymlinkFeature)
682
 
        self.build_tree_contents([('old-tree/olddir/',),
683
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
684
 
        self.old_tree.add('olddir')
685
 
        self.old_tree.add('olddir/oldfile', 'file-id')
686
 
        self.build_tree(['new-tree/newdir/'])
687
 
        os.symlink('new', 'new-tree/newdir/newfile')
688
 
        self.new_tree.add('newdir')
689
 
        self.new_tree.add('newdir/newfile', 'file-id')
690
 
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
691
 
        self.assertContainsRe(
692
 
            self.differ.to_file.getvalue(),
693
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
694
 
             ' \@\@\n-old\n\n')
695
 
        self.assertContainsRe(self.differ.to_file.getvalue(),
696
 
                              "=== target is u'new'\n")
697
 
 
698
 
    def test_diff_directory(self):
699
 
        self.build_tree(['new-tree/new-dir/'])
700
 
        self.new_tree.add('new-dir', 'new-dir-id')
701
 
        self.differ.diff('new-dir-id', None, 'new-dir')
702
 
        self.assertEqual(self.differ.to_file.getvalue(), '')
703
 
 
704
 
    def create_old_new(self):
705
 
        self.build_tree_contents([('old-tree/olddir/',),
706
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
707
 
        self.old_tree.add('olddir')
708
 
        self.old_tree.add('olddir/oldfile', 'file-id')
709
 
        self.build_tree_contents([('new-tree/newdir/',),
710
 
                                  ('new-tree/newdir/newfile', 'new\n')])
711
 
        self.new_tree.add('newdir')
712
 
        self.new_tree.add('newdir/newfile', 'file-id')
713
 
 
714
 
    def test_register_diff(self):
715
 
        self.create_old_new()
716
 
        old_diff_factories = diff.DiffTree.diff_factories
717
 
        diff.DiffTree.diff_factories=old_diff_factories[:]
718
 
        diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
719
 
        try:
720
 
            differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
721
 
        finally:
722
 
            diff.DiffTree.diff_factories = old_diff_factories
723
 
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
724
 
        self.assertNotContainsRe(
725
 
            differ.to_file.getvalue(),
726
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
727
 
             ' \@\@\n-old\n\+new\n\n')
728
 
        self.assertContainsRe(differ.to_file.getvalue(),
729
 
                              'was: old\nis: new\n')
730
 
 
731
 
    def test_extra_factories(self):
732
 
        self.create_old_new()
733
 
        differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
734
 
                               extra_factories=[DiffWasIs.from_diff_tree])
735
 
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
736
 
        self.assertNotContainsRe(
737
 
            differ.to_file.getvalue(),
738
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
739
 
             ' \@\@\n-old\n\+new\n\n')
740
 
        self.assertContainsRe(differ.to_file.getvalue(),
741
 
                              'was: old\nis: new\n')
742
 
 
743
 
    def test_alphabetical_order(self):
744
 
        self.build_tree(['new-tree/a-file'])
745
 
        self.new_tree.add('a-file')
746
 
        self.build_tree(['old-tree/b-file'])
747
 
        self.old_tree.add('b-file')
748
 
        self.differ.show_diff(None)
749
 
        self.assertContainsRe(self.differ.to_file.getvalue(),
750
 
            '.*a-file(.|\n)*b-file')
751
 
 
752
 
 
753
 
class TestPatienceDiffLib(tests.TestCase):
754
 
 
755
 
    def setUp(self):
756
 
        super(TestPatienceDiffLib, self).setUp()
757
 
        self._unique_lcs = _patiencediff_py.unique_lcs_py
758
 
        self._recurse_matches = _patiencediff_py.recurse_matches_py
759
 
        self._PatienceSequenceMatcher = \
760
 
            _patiencediff_py.PatienceSequenceMatcher_py
761
 
 
762
 
    def test_diff_unicode_string(self):
763
 
        a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
764
 
        b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
765
 
        sm = self._PatienceSequenceMatcher(None, a, b)
766
 
        mb = sm.get_matching_blocks()
767
 
        self.assertEquals(35, len(mb))
 
353
class TestPatienceDiffLib(TestCase):
768
354
 
769
355
    def test_unique_lcs(self):
770
 
        unique_lcs = self._unique_lcs
 
356
        unique_lcs = bzrlib.patiencediff.unique_lcs
771
357
        self.assertEquals(unique_lcs('', ''), [])
772
 
        self.assertEquals(unique_lcs('', 'a'), [])
773
 
        self.assertEquals(unique_lcs('a', ''), [])
774
358
        self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
775
359
        self.assertEquals(unique_lcs('a', 'b'), [])
776
360
        self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
777
361
        self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
778
362
        self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
779
 
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
 
363
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1), 
780
364
                                                         (3,3), (4,4)])
781
365
        self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
782
366
 
783
367
    def test_recurse_matches(self):
784
368
        def test_one(a, b, matches):
785
369
            test_matches = []
786
 
            self._recurse_matches(
787
 
                a, b, 0, 0, len(a), len(b), test_matches, 10)
 
370
            bzrlib.patiencediff.recurse_matches(a, b, 0, 0, len(a), len(b),
 
371
                test_matches, 10)
788
372
            self.assertEquals(test_matches, matches)
789
373
 
790
374
        test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
791
375
                 [(0, 0), (2, 2), (4, 4)])
792
376
        test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
793
377
                 [(0, 0), (2, 1), (4, 2)])
794
 
        # Even though 'bc' is not unique globally, and is surrounded by
795
 
        # non-matching lines, we should still match, because they are locally
796
 
        # unique
797
 
        test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
798
 
                                          (4, 6), (5, 7), (6, 8)])
799
378
 
800
 
        # recurse_matches doesn't match non-unique
 
379
        # recurse_matches doesn't match non-unique 
801
380
        # lines surrounded by bogus text.
802
381
        # The update has been done in patiencediff.SequenceMatcher instead
803
382
 
807
386
        # This is what it currently gives:
808
387
        test_one('aBccDe', 'abccde', [(0,0), (5,5)])
809
388
 
810
 
    def assertDiffBlocks(self, a, b, expected_blocks):
811
 
        """Check that the sequence matcher returns the correct blocks.
812
 
 
813
 
        :param a: A sequence to match
814
 
        :param b: Another sequence to match
815
 
        :param expected_blocks: The expected output, not including the final
816
 
            matching block (len(a), len(b), 0)
817
 
        """
818
 
        matcher = self._PatienceSequenceMatcher(None, a, b)
819
 
        blocks = matcher.get_matching_blocks()
820
 
        last = blocks.pop()
821
 
        self.assertEqual((len(a), len(b), 0), last)
822
 
        self.assertEqual(expected_blocks, blocks)
823
 
 
824
389
    def test_matching_blocks(self):
 
390
        def chk_blocks(a, b, expected_blocks):
 
391
            # difflib always adds a signature of the total
 
392
            # length, with no matching entries at the end
 
393
            s = bzrlib.patiencediff.PatienceSequenceMatcher(None, a, b)
 
394
            blocks = s.get_matching_blocks()
 
395
            self.assertEquals((len(a), len(b), 0), blocks[-1])
 
396
            self.assertEquals(expected_blocks, blocks[:-1])
 
397
 
825
398
        # Some basic matching tests
826
 
        self.assertDiffBlocks('', '', [])
827
 
        self.assertDiffBlocks([], [], [])
828
 
        self.assertDiffBlocks('abc', '', [])
829
 
        self.assertDiffBlocks('', 'abc', [])
830
 
        self.assertDiffBlocks('abcd', 'abcd', [(0, 0, 4)])
831
 
        self.assertDiffBlocks('abcd', 'abce', [(0, 0, 3)])
832
 
        self.assertDiffBlocks('eabc', 'abce', [(1, 0, 3)])
833
 
        self.assertDiffBlocks('eabce', 'abce', [(1, 0, 4)])
834
 
        self.assertDiffBlocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
835
 
        self.assertDiffBlocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
836
 
        self.assertDiffBlocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
837
 
        # This may check too much, but it checks to see that
 
399
        chk_blocks('', '', [])
 
400
        chk_blocks([], [], [])
 
401
        chk_blocks('abcd', 'abcd', [(0, 0, 4)])
 
402
        chk_blocks('abcd', 'abce', [(0, 0, 3)])
 
403
        chk_blocks('eabc', 'abce', [(1, 0, 3)])
 
404
        chk_blocks('eabce', 'abce', [(1, 0, 4)])
 
405
        chk_blocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
 
406
        chk_blocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
 
407
        chk_blocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
 
408
        # This may check too much, but it checks to see that 
838
409
        # a copied block stays attached to the previous section,
839
410
        # not the later one.
840
411
        # difflib would tend to grab the trailing longest match
841
412
        # which would make the diff not look right
842
 
        self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
843
 
                              [(0, 0, 6), (6, 11, 10)])
 
413
        chk_blocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
414
                   [(0, 0, 6), (6, 11, 10)])
844
415
 
845
416
        # make sure it supports passing in lists
846
 
        self.assertDiffBlocks(
 
417
        chk_blocks(
847
418
                   ['hello there\n',
848
419
                    'world\n',
849
420
                    'how are you today?\n'],
853
424
 
854
425
        # non unique lines surrounded by non-matching lines
855
426
        # won't be found
856
 
        self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
 
427
        chk_blocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
857
428
 
858
429
        # But they only need to be locally unique
859
 
        self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
 
430
        chk_blocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
860
431
 
861
432
        # non unique blocks won't be matched
862
 
        self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
 
433
        chk_blocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
863
434
 
864
435
        # but locally unique ones will
865
 
        self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
 
436
        chk_blocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
866
437
                                              (5,4,1), (7,5,2), (10,8,1)])
867
438
 
868
 
        self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
869
 
        self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
870
 
        self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
871
 
 
872
 
    def test_matching_blocks_tuples(self):
873
 
        # Some basic matching tests
874
 
        self.assertDiffBlocks([], [], [])
875
 
        self.assertDiffBlocks([('a',), ('b',), ('c,')], [], [])
876
 
        self.assertDiffBlocks([], [('a',), ('b',), ('c,')], [])
877
 
        self.assertDiffBlocks([('a',), ('b',), ('c,')],
878
 
                              [('a',), ('b',), ('c,')],
879
 
                              [(0, 0, 3)])
880
 
        self.assertDiffBlocks([('a',), ('b',), ('c,')],
881
 
                              [('a',), ('b',), ('d,')],
882
 
                              [(0, 0, 2)])
883
 
        self.assertDiffBlocks([('d',), ('b',), ('c,')],
884
 
                              [('a',), ('b',), ('c,')],
885
 
                              [(1, 1, 2)])
886
 
        self.assertDiffBlocks([('d',), ('a',), ('b',), ('c,')],
887
 
                              [('a',), ('b',), ('c,')],
888
 
                              [(1, 0, 3)])
889
 
        self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
890
 
                              [('a', 'b'), ('c', 'X'), ('e', 'f')],
891
 
                              [(0, 0, 1), (2, 2, 1)])
892
 
        self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
893
 
                              [('a', 'b'), ('c', 'dX'), ('e', 'f')],
894
 
                              [(0, 0, 1), (2, 2, 1)])
 
439
        chk_blocks('abbabbXd', 'cabbabxd', [(7,7,1)])
 
440
        chk_blocks('abbabbbb', 'cabbabbc', [])
 
441
        chk_blocks('bbbbbbbb', 'cbbbbbbc', [])
895
442
 
896
443
    def test_opcodes(self):
897
444
        def chk_ops(a, b, expected_codes):
898
 
            s = self._PatienceSequenceMatcher(None, a, b)
 
445
            s = bzrlib.patiencediff.PatienceSequenceMatcher(None, a, b)
899
446
            self.assertEquals(expected_codes, s.get_opcodes())
900
447
 
901
448
        chk_ops('', '', [])
902
449
        chk_ops([], [], [])
903
 
        chk_ops('abc', '', [('delete', 0,3, 0,0)])
904
 
        chk_ops('', 'abc', [('insert', 0,0, 0,3)])
905
450
        chk_ops('abcd', 'abcd', [('equal',    0,4, 0,4)])
906
451
        chk_ops('abcd', 'abce', [('equal',   0,3, 0,3),
907
452
                                 ('replace', 3,4, 3,4)
940
485
                 ('delete', 1,2, 1,1),
941
486
                 ('equal',  2,3, 1,2),
942
487
                ])
943
 
        chk_ops('aBccDe', 'abccde',
 
488
        chk_ops('aBccDe', 'abccde', 
944
489
                [('equal',   0,1, 0,1),
945
490
                 ('replace', 1,5, 1,5),
946
491
                 ('equal',   5,6, 5,6),
947
492
                ])
948
 
        chk_ops('aBcDec', 'abcdec',
 
493
        chk_ops('aBcDec', 'abcdec', 
949
494
                [('equal',   0,1, 0,1),
950
495
                 ('replace', 1,2, 1,2),
951
496
                 ('equal',   2,3, 2,3),
952
497
                 ('replace', 3,4, 3,4),
953
498
                 ('equal',   4,6, 4,6),
954
499
                ])
955
 
        chk_ops('aBcdEcdFg', 'abcdecdfg',
 
500
        chk_ops('aBcdEcdFg', 'abcdecdfg', 
956
501
                [('equal',   0,1, 0,1),
957
502
                 ('replace', 1,8, 1,8),
958
503
                 ('equal',   8,9, 8,9)
959
504
                ])
960
 
        chk_ops('aBcdEeXcdFg', 'abcdecdfg',
 
505
        chk_ops('aBcdEeXcdFg', 'abcdecdfg', 
961
506
                [('equal',   0,1, 0,1),
962
507
                 ('replace', 1,2, 1,2),
963
508
                 ('equal',   2,4, 2,4),
969
514
                 ('equal',   10,11, 8,9)
970
515
                ])
971
516
 
972
 
    def test_grouped_opcodes(self):
973
 
        def chk_ops(a, b, expected_codes, n=3):
974
 
            s = self._PatienceSequenceMatcher(None, a, b)
975
 
            self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
976
 
 
977
 
        chk_ops('', '', [])
978
 
        chk_ops([], [], [])
979
 
        chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
980
 
        chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
981
 
        chk_ops('abcd', 'abcd', [])
982
 
        chk_ops('abcd', 'abce', [[('equal',   0,3, 0,3),
983
 
                                  ('replace', 3,4, 3,4)
984
 
                                 ]])
985
 
        chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
986
 
                                 ('equal',  1,4, 0,3),
987
 
                                 ('insert', 4,4, 3,4)
988
 
                                ]])
989
 
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
990
 
                [[('equal',  3,6, 3,6),
991
 
                  ('insert', 6,6, 6,11),
992
 
                  ('equal',  6,9, 11,14)
993
 
                  ]])
994
 
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
995
 
                [[('equal',  2,6, 2,6),
996
 
                  ('insert', 6,6, 6,11),
997
 
                  ('equal',  6,10, 11,15)
998
 
                  ]], 4)
999
 
        chk_ops('Xabcdef', 'abcdef',
1000
 
                [[('delete', 0,1, 0,0),
1001
 
                  ('equal',  1,4, 0,3)
1002
 
                  ]])
1003
 
        chk_ops('abcdef', 'abcdefX',
1004
 
                [[('equal',  3,6, 3,6),
1005
 
                  ('insert', 6,6, 6,7)
1006
 
                  ]])
1007
 
 
1008
 
 
1009
517
    def test_multiple_ranges(self):
1010
518
        # There was an earlier bug where we used a bad set of ranges,
1011
519
        # this triggers that specific bug, to make sure it doesn't regress
1012
 
        self.assertDiffBlocks('abcdefghijklmnop',
1013
 
                              'abcXghiYZQRSTUVWXYZijklmnop',
1014
 
                              [(0, 0, 3), (6, 4, 3), (9, 20, 7)])
1015
 
 
1016
 
        self.assertDiffBlocks('ABCd efghIjk  L',
1017
 
                              'AxyzBCn mo pqrstuvwI1 2  L',
1018
 
                              [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
520
        def chk_blocks(a, b, expected_blocks):
 
521
            # difflib always adds a signature of the total
 
522
            # length, with no matching entries at the end
 
523
            s = bzrlib.patiencediff.PatienceSequenceMatcher(None, a, b)
 
524
            blocks = s.get_matching_blocks()
 
525
            x = blocks.pop()
 
526
            self.assertEquals(x, (len(a), len(b), 0))
 
527
            self.assertEquals(expected_blocks, blocks)
 
528
 
 
529
        chk_blocks('abcdefghijklmnop'
 
530
                 , 'abcXghiYZQRSTUVWXYZijklmnop'
 
531
                 , [(0, 0, 3), (6, 4, 3), (9, 20, 7)])
 
532
 
 
533
        chk_blocks('ABCd efghIjk  L'
 
534
                 , 'AxyzBCn mo pqrstuvwI1 2  L'
 
535
                 , [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1019
536
 
1020
537
        # These are rot13 code snippets.
1021
 
        self.assertDiffBlocks('''\
 
538
        chk_blocks('''\
1022
539
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1023
540
    """
1024
541
    gnxrf_netf = ['svyr*']
1025
542
    gnxrf_bcgvbaf = ['ab-erphefr']
1026
 
 
 
543
  
1027
544
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
1028
545
        sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
1029
546
        vs vf_dhvrg():
1037
554
'''.splitlines(True), '''\
1038
555
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1039
556
 
1040
 
    --qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl
 
557
    --qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl 
1041
558
    nqq gurz.
1042
559
    """
1043
560
    gnxrf_netf = ['svyr*']
1070
587
                 'how are you today?\n']
1071
588
        txt_b = ['hello there\n',
1072
589
                 'how are you today?\n']
1073
 
        unified_diff = patiencediff.unified_diff
1074
 
        psm = self._PatienceSequenceMatcher
1075
 
        self.assertEquals(['--- \n',
1076
 
                           '+++ \n',
 
590
        unified_diff = bzrlib.patiencediff.unified_diff
 
591
        psm = bzrlib.patiencediff.PatienceSequenceMatcher
 
592
        self.assertEquals([ '---  \n',
 
593
                           '+++  \n',
1077
594
                           '@@ -1,3 +1,2 @@\n',
1078
595
                           ' hello there\n',
1079
596
                           '-world\n',
1084
601
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1085
602
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1086
603
        # This is the result with LongestCommonSubstring matching
1087
 
        self.assertEquals(['--- \n',
1088
 
                           '+++ \n',
 
604
        self.assertEquals(['---  \n',
 
605
                           '+++  \n',
1089
606
                           '@@ -1,6 +1,11 @@\n',
1090
607
                           ' a\n',
1091
608
                           ' b\n',
1100
617
                           ' f\n']
1101
618
                          , list(unified_diff(txt_a, txt_b)))
1102
619
        # And the patience diff
1103
 
        self.assertEquals(['--- \n',
1104
 
                           '+++ \n',
 
620
        self.assertEquals(['---  \n',
 
621
                           '+++  \n',
1105
622
                           '@@ -4,6 +4,11 @@\n',
1106
623
                           ' d\n',
1107
624
                           ' e\n',
1118
635
                          , list(unified_diff(txt_a, txt_b,
1119
636
                                 sequencematcher=psm)))
1120
637
 
1121
 
    def test_patience_unified_diff_with_dates(self):
1122
 
        txt_a = ['hello there\n',
1123
 
                 'world\n',
1124
 
                 'how are you today?\n']
1125
 
        txt_b = ['hello there\n',
1126
 
                 'how are you today?\n']
1127
 
        unified_diff = patiencediff.unified_diff
1128
 
        psm = self._PatienceSequenceMatcher
1129
 
        self.assertEquals(['--- a\t2008-08-08\n',
1130
 
                           '+++ b\t2008-09-09\n',
1131
 
                           '@@ -1,3 +1,2 @@\n',
1132
 
                           ' hello there\n',
1133
 
                           '-world\n',
1134
 
                           ' how are you today?\n'
1135
 
                          ]
1136
 
                          , list(unified_diff(txt_a, txt_b,
1137
 
                                 fromfile='a', tofile='b',
1138
 
                                 fromfiledate='2008-08-08',
1139
 
                                 tofiledate='2008-09-09',
1140
 
                                 sequencematcher=psm)))
1141
 
 
1142
 
 
1143
 
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1144
 
 
1145
 
    _test_needs_features = [compiled_patiencediff_feature]
1146
 
 
1147
 
    def setUp(self):
1148
 
        super(TestPatienceDiffLib_c, self).setUp()
1149
 
        from bzrlib import _patiencediff_c
1150
 
        self._unique_lcs = _patiencediff_c.unique_lcs_c
1151
 
        self._recurse_matches = _patiencediff_c.recurse_matches_c
1152
 
        self._PatienceSequenceMatcher = \
1153
 
            _patiencediff_c.PatienceSequenceMatcher_c
1154
 
 
1155
 
    def test_unhashable(self):
1156
 
        """We should get a proper exception here."""
1157
 
        # We need to be able to hash items in the sequence, lists are
1158
 
        # unhashable, and thus cannot be diffed
1159
 
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1160
 
                                         None, [[]], [])
1161
 
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1162
 
                                         None, ['valid', []], [])
1163
 
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1164
 
                                         None, ['valid'], [[]])
1165
 
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1166
 
                                         None, ['valid'], ['valid', []])
1167
 
 
1168
 
 
1169
 
class TestPatienceDiffLibFiles(tests.TestCaseInTempDir):
1170
 
 
1171
 
    def setUp(self):
1172
 
        super(TestPatienceDiffLibFiles, self).setUp()
1173
 
        self._PatienceSequenceMatcher = \
1174
 
            _patiencediff_py.PatienceSequenceMatcher_py
 
638
 
 
639
class TestPatienceDiffLibFiles(TestCaseInTempDir):
1175
640
 
1176
641
    def test_patience_unified_diff_files(self):
1177
642
        txt_a = ['hello there\n',
1182
647
        open('a1', 'wb').writelines(txt_a)
1183
648
        open('b1', 'wb').writelines(txt_b)
1184
649
 
1185
 
        unified_diff_files = patiencediff.unified_diff_files
1186
 
        psm = self._PatienceSequenceMatcher
1187
 
        self.assertEquals(['--- a1\n',
1188
 
                           '+++ b1\n',
 
650
        unified_diff_files = bzrlib.patiencediff.unified_diff_files
 
651
        psm = bzrlib.patiencediff.PatienceSequenceMatcher
 
652
        self.assertEquals(['--- a1 \n',
 
653
                           '+++ b1 \n',
1189
654
                           '@@ -1,3 +1,2 @@\n',
1190
655
                           ' hello there\n',
1191
656
                           '-world\n',
1200
665
        open('b2', 'wb').writelines(txt_b)
1201
666
 
1202
667
        # This is the result with LongestCommonSubstring matching
1203
 
        self.assertEquals(['--- a2\n',
1204
 
                           '+++ b2\n',
 
668
        self.assertEquals(['--- a2 \n',
 
669
                           '+++ b2 \n',
1205
670
                           '@@ -1,6 +1,11 @@\n',
1206
671
                           ' a\n',
1207
672
                           ' b\n',
1217
682
                          , list(unified_diff_files('a2', 'b2')))
1218
683
 
1219
684
        # And the patience diff
1220
 
        self.assertEquals(['--- a2\n',
1221
 
                           '+++ b2\n',
 
685
        self.assertEquals(['--- a2 \n',
 
686
                           '+++ b2 \n',
1222
687
                           '@@ -4,6 +4,11 @@\n',
1223
688
                           ' d\n',
1224
689
                           ' e\n',
1234
699
                          ]
1235
700
                          , list(unified_diff_files('a2', 'b2',
1236
701
                                 sequencematcher=psm)))
1237
 
 
1238
 
 
1239
 
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1240
 
 
1241
 
    _test_needs_features = [compiled_patiencediff_feature]
1242
 
 
1243
 
    def setUp(self):
1244
 
        super(TestPatienceDiffLibFiles_c, self).setUp()
1245
 
        from bzrlib import _patiencediff_c
1246
 
        self._PatienceSequenceMatcher = \
1247
 
            _patiencediff_c.PatienceSequenceMatcher_c
1248
 
 
1249
 
 
1250
 
class TestUsingCompiledIfAvailable(tests.TestCase):
1251
 
 
1252
 
    def test_PatienceSequenceMatcher(self):
1253
 
        if compiled_patiencediff_feature.available():
1254
 
            from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1255
 
            self.assertIs(PatienceSequenceMatcher_c,
1256
 
                          patiencediff.PatienceSequenceMatcher)
1257
 
        else:
1258
 
            from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1259
 
            self.assertIs(PatienceSequenceMatcher_py,
1260
 
                          patiencediff.PatienceSequenceMatcher)
1261
 
 
1262
 
    def test_unique_lcs(self):
1263
 
        if compiled_patiencediff_feature.available():
1264
 
            from bzrlib._patiencediff_c import unique_lcs_c
1265
 
            self.assertIs(unique_lcs_c,
1266
 
                          patiencediff.unique_lcs)
1267
 
        else:
1268
 
            from bzrlib._patiencediff_py import unique_lcs_py
1269
 
            self.assertIs(unique_lcs_py,
1270
 
                          patiencediff.unique_lcs)
1271
 
 
1272
 
    def test_recurse_matches(self):
1273
 
        if compiled_patiencediff_feature.available():
1274
 
            from bzrlib._patiencediff_c import recurse_matches_c
1275
 
            self.assertIs(recurse_matches_c,
1276
 
                          patiencediff.recurse_matches)
1277
 
        else:
1278
 
            from bzrlib._patiencediff_py import recurse_matches_py
1279
 
            self.assertIs(recurse_matches_py,
1280
 
                          patiencediff.recurse_matches)
1281
 
 
1282
 
 
1283
 
class TestDiffFromTool(tests.TestCaseWithTransport):
1284
 
 
1285
 
    def test_from_string(self):
1286
 
        diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
1287
 
        self.addCleanup(diff_obj.finish)
1288
 
        self.assertEqual(['diff', '@old_path', '@new_path'],
1289
 
            diff_obj.command_template)
1290
 
 
1291
 
    def test_from_string_u5(self):
1292
 
        diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
1293
 
                                                 None, None, None)
1294
 
        self.addCleanup(diff_obj.finish)
1295
 
        self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
1296
 
                         diff_obj.command_template)
1297
 
        self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
1298
 
                         diff_obj._get_command('old-path', 'new-path'))
1299
 
 
1300
 
    def test_from_string_path_with_backslashes(self):
1301
 
        self.requireFeature(test_win32utils.BackslashDirSeparatorFeature)
1302
 
        tool = 'C:\\Tools\\Diff.exe'
1303
 
        diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
1304
 
        self.addCleanup(diff_obj.finish)
1305
 
        self.assertEqual(['C:\\Tools\\Diff.exe', '@old_path', '@new_path'],
1306
 
                         diff_obj.command_template)
1307
 
        self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
1308
 
                         diff_obj._get_command('old-path', 'new-path'))
1309
 
 
1310
 
    def test_execute(self):
1311
 
        output = StringIO()
1312
 
        diff_obj = diff.DiffFromTool(['python', '-c',
1313
 
                                      'print "@old_path @new_path"'],
1314
 
                                     None, None, output)
1315
 
        self.addCleanup(diff_obj.finish)
1316
 
        diff_obj._execute('old', 'new')
1317
 
        self.assertEqual(output.getvalue().rstrip(), 'old new')
1318
 
 
1319
 
    def test_excute_missing(self):
1320
 
        diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1321
 
                                     None, None, None)
1322
 
        self.addCleanup(diff_obj.finish)
1323
 
        e = self.assertRaises(errors.ExecutableMissing, diff_obj._execute,
1324
 
                              'old', 'new')
1325
 
        self.assertEqual('a-tool-which-is-unlikely-to-exist could not be found'
1326
 
                         ' on this machine', str(e))
1327
 
 
1328
 
    def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1329
 
        self.requireFeature(AttribFeature)
1330
 
        output = StringIO()
1331
 
        tree = self.make_branch_and_tree('tree')
1332
 
        self.build_tree_contents([('tree/file', 'content')])
1333
 
        tree.add('file', 'file-id')
1334
 
        tree.commit('old tree')
1335
 
        tree.lock_read()
1336
 
        self.addCleanup(tree.unlock)
1337
 
        basis_tree = tree.basis_tree()
1338
 
        basis_tree.lock_read()
1339
 
        self.addCleanup(basis_tree.unlock)
1340
 
        diff_obj = diff.DiffFromTool(['python', '-c',
1341
 
                                      'print "@old_path @new_path"'],
1342
 
                                     basis_tree, tree, output)
1343
 
        diff_obj._prepare_files('file-id', 'file', 'file')
1344
 
        # The old content should be readonly
1345
 
        self.assertReadableByAttrib(diff_obj._root, 'old\\file',
1346
 
                                    r'R.*old\\file$')
1347
 
        # The new content should use the tree object, not a 'new' file anymore
1348
 
        self.assertEndsWith(tree.basedir, 'work/tree')
1349
 
        self.assertReadableByAttrib(tree.basedir, 'file', r'work\\tree\\file$')
1350
 
 
1351
 
    def assertReadableByAttrib(self, cwd, relpath, regex):
1352
 
        proc = subprocess.Popen(['attrib', relpath],
1353
 
                                stdout=subprocess.PIPE,
1354
 
                                cwd=cwd)
1355
 
        (result, err) = proc.communicate()
1356
 
        self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1357
 
 
1358
 
    def test_prepare_files(self):
1359
 
        output = StringIO()
1360
 
        tree = self.make_branch_and_tree('tree')
1361
 
        self.build_tree_contents([('tree/oldname', 'oldcontent')])
1362
 
        self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1363
 
        tree.add('oldname', 'file-id')
1364
 
        tree.add('oldname2', 'file2-id')
1365
 
        # Earliest allowable date on FAT32 filesystems is 1980-01-01
1366
 
        tree.commit('old tree', timestamp=315532800)
1367
 
        tree.rename_one('oldname', 'newname')
1368
 
        tree.rename_one('oldname2', 'newname2')
1369
 
        self.build_tree_contents([('tree/newname', 'newcontent')])
1370
 
        self.build_tree_contents([('tree/newname2', 'newcontent2')])
1371
 
        old_tree = tree.basis_tree()
1372
 
        old_tree.lock_read()
1373
 
        self.addCleanup(old_tree.unlock)
1374
 
        tree.lock_read()
1375
 
        self.addCleanup(tree.unlock)
1376
 
        diff_obj = diff.DiffFromTool(['python', '-c',
1377
 
                                      'print "@old_path @new_path"'],
1378
 
                                     old_tree, tree, output)
1379
 
        self.addCleanup(diff_obj.finish)
1380
 
        self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1381
 
        old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
1382
 
                                                     'newname')
1383
 
        self.assertContainsRe(old_path, 'old/oldname$')
1384
 
        self.assertEqual(315532800, os.stat(old_path).st_mtime)
1385
 
        self.assertContainsRe(new_path, 'tree/newname$')
1386
 
        self.assertFileEqual('oldcontent', old_path)
1387
 
        self.assertFileEqual('newcontent', new_path)
1388
 
        if osutils.host_os_dereferences_symlinks():
1389
 
            self.assertTrue(os.path.samefile('tree/newname', new_path))
1390
 
        # make sure we can create files with the same parent directories
1391
 
        diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
1392
 
 
1393
 
 
1394
 
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1395
 
 
1396
 
    def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1397
 
        """Call get_trees_and_branches_to_diff_locked.  Overridden by
1398
 
        TestGetTreesAndBranchesToDiff.
1399
 
        """
1400
 
        return diff.get_trees_and_branches_to_diff_locked(
1401
 
            path_list, revision_specs, old_url, new_url, self.addCleanup)
1402
 
 
1403
 
    def test_basic(self):
1404
 
        tree = self.make_branch_and_tree('tree')
1405
 
        (old_tree, new_tree,
1406
 
         old_branch, new_branch,
1407
 
         specific_files, extra_trees) = self.call_gtabtd(
1408
 
             ['tree'], None, None, None)
1409
 
 
1410
 
        self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1411
 
        self.assertEqual(_mod_revision.NULL_REVISION,
1412
 
                         old_tree.get_revision_id())
1413
 
        self.assertEqual(tree.basedir, new_tree.basedir)
1414
 
        self.assertEqual(tree.branch.base, old_branch.base)
1415
 
        self.assertEqual(tree.branch.base, new_branch.base)
1416
 
        self.assertIs(None, specific_files)
1417
 
        self.assertIs(None, extra_trees)
1418
 
 
1419
 
    def test_with_rev_specs(self):
1420
 
        tree = self.make_branch_and_tree('tree')
1421
 
        self.build_tree_contents([('tree/file', 'oldcontent')])
1422
 
        tree.add('file', 'file-id')
1423
 
        tree.commit('old tree', timestamp=0, rev_id="old-id")
1424
 
        self.build_tree_contents([('tree/file', 'newcontent')])
1425
 
        tree.commit('new tree', timestamp=0, rev_id="new-id")
1426
 
 
1427
 
        revisions = [revisionspec.RevisionSpec.from_string('1'),
1428
 
                     revisionspec.RevisionSpec.from_string('2')]
1429
 
        (old_tree, new_tree,
1430
 
         old_branch, new_branch,
1431
 
         specific_files, extra_trees) = self.call_gtabtd(
1432
 
            ['tree'], revisions, None, None)
1433
 
 
1434
 
        self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1435
 
        self.assertEqual("old-id", old_tree.get_revision_id())
1436
 
        self.assertIsInstance(new_tree, revisiontree.RevisionTree)
1437
 
        self.assertEqual("new-id", new_tree.get_revision_id())
1438
 
        self.assertEqual(tree.branch.base, old_branch.base)
1439
 
        self.assertEqual(tree.branch.base, new_branch.base)
1440
 
        self.assertIs(None, specific_files)
1441
 
        self.assertEqual(tree.basedir, extra_trees[0].basedir)
1442
 
 
1443
 
 
1444
 
class TestGetTreesAndBranchesToDiff(TestGetTreesAndBranchesToDiffLocked):
1445
 
    """Apply the tests for get_trees_and_branches_to_diff_locked to the
1446
 
    deprecated get_trees_and_branches_to_diff function.
1447
 
    """
1448
 
 
1449
 
    def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1450
 
        return self.applyDeprecated(
1451
 
            deprecated_in((2, 2, 0)), diff.get_trees_and_branches_to_diff,
1452
 
            path_list, revision_specs, old_url, new_url)
1453