~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-08-31 06:06:38 UTC
  • mfrom: (1966.1.2 build-resolver)
  • Revision ID: pqm@pqm.ubuntu.com-20060831060638-bfc0303de36a40a8
Behave reasonably when creating a checkout in a directory with files

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