~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

  • Committer: Vincent Ladeuil
  • Date: 2010-10-26 08:08:23 UTC
  • mfrom: (5514.1.1 665100-content-type)
  • mto: This revision was merged to the branch mainline in revision 5516.
  • Revision ID: v.ladeuil+lp@free.fr-20101026080823-3wggo03b7cpn9908
Correctly set the Content-Type header when POSTing http requests

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Development Ltd
 
1
# Copyright (C) 2005-2010 Canonical 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import os
18
18
from cStringIO import StringIO
19
 
import errno
20
 
from tempfile import TemporaryFile
21
 
 
22
 
from bzrlib.diff import internal_diff, external_diff, show_diff_trees
23
 
from bzrlib.errors import BinaryFile, NoDiff
24
 
import bzrlib.patiencediff
25
 
from bzrlib.tests import (TestCase, TestCaseWithTransport,
26
 
                          TestCaseInTempDir, TestSkipped)
 
19
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')
27
59
 
28
60
 
29
61
def udiff_lines(old, new, allow_binary=False):
30
62
    output = StringIO()
31
 
    internal_diff('old', old, 'new', new, output, allow_binary)
 
63
    diff.internal_diff('old', old, 'new', new, output, allow_binary)
32
64
    output.seek(0, 0)
33
65
    return output.readlines()
34
66
 
38
70
        # StringIO has no fileno, so it tests a different codepath
39
71
        output = StringIO()
40
72
    else:
41
 
        output = TemporaryFile()
 
73
        output = tempfile.TemporaryFile()
42
74
    try:
43
 
        external_diff('old', old, 'new', new, output, diff_opts=['-u'])
44
 
    except NoDiff:
45
 
        raise TestSkipped('external "diff" not present to test')
 
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')
46
78
    output.seek(0, 0)
47
79
    lines = output.readlines()
48
80
    output.close()
49
81
    return lines
50
82
 
51
83
 
52
 
class TestDiff(TestCase):
 
84
class TestDiff(tests.TestCase):
53
85
 
54
86
    def test_add_nl(self):
55
87
        """diff generates a valid diff for patches that add a newline"""
91
123
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
92
124
 
93
125
    def test_binary_lines(self):
94
 
        self.assertRaises(BinaryFile, udiff_lines, [1023 * 'a' + '\x00'], [])
95
 
        self.assertRaises(BinaryFile, udiff_lines, [], [1023 * 'a' + '\x00'])
96
 
        udiff_lines([1023 * 'a' + '\x00'], [], allow_binary=True)
97
 
        udiff_lines([], [1023 * 'a' + '\x00'], allow_binary=True)
 
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)
98
132
 
99
133
    def test_external_diff(self):
100
134
        lines = external_udiff_lines(['boo\n'], ['goo\n'])
101
135
        self.check_patch(lines)
 
136
        self.assertEqual('\n', lines[-1])
102
137
 
103
138
    def test_external_diff_no_fileno(self):
104
139
        # Make sure that we can handle not having a fileno, even
107
142
                                     ['goo\n']*10000,
108
143
                                     use_stringio=True)
109
144
        self.check_patch(lines)
110
 
        
 
145
 
 
146
    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')
 
150
        try:
 
151
            lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
152
            # Older versions of diffutils say "Binary files", newer
 
153
            # versions just say "Files".
 
154
            self.assertContainsRe(lines[0],
 
155
                                  '(Binary f|F)iles old and new differ\n')
 
156
            self.assertEquals(lines[1:], ['\n'])
 
157
        finally:
 
158
            for lang, old_val in old_env.iteritems():
 
159
                osutils.set_or_unset_env(lang, old_val)
 
160
 
 
161
    def test_no_external_diff(self):
 
162
        """Check that NoDiff is raised when diff is not available"""
 
163
        # Use os.environ['PATH'] to make sure no 'diff' command is available
 
164
        orig_path = os.environ['PATH']
 
165
        try:
 
166
            os.environ['PATH'] = ''
 
167
            self.assertRaises(errors.NoDiff, diff.external_diff,
 
168
                              'old', ['boo\n'], 'new', ['goo\n'],
 
169
                              StringIO(), diff_opts=['-u'])
 
170
        finally:
 
171
            os.environ['PATH'] = orig_path
 
172
 
111
173
    def test_internal_diff_default(self):
112
174
        # Default internal diff encoding is utf8
113
175
        output = StringIO()
114
 
        internal_diff(u'old_\xb5', ['old_text\n'],
115
 
                    u'new_\xe5', ['new_text\n'], output)
 
176
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
177
                           u'new_\xe5', ['new_text\n'], output)
116
178
        lines = output.getvalue().splitlines(True)
117
179
        self.check_patch(lines)
118
180
        self.assertEquals(['--- old_\xc2\xb5\n',
126
188
 
127
189
    def test_internal_diff_utf8(self):
128
190
        output = StringIO()
129
 
        internal_diff(u'old_\xb5', ['old_text\n'],
130
 
                    u'new_\xe5', ['new_text\n'], output,
131
 
                    path_encoding='utf8')
 
191
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
192
                           u'new_\xe5', ['new_text\n'], output,
 
193
                           path_encoding='utf8')
132
194
        lines = output.getvalue().splitlines(True)
133
195
        self.check_patch(lines)
134
196
        self.assertEquals(['--- old_\xc2\xb5\n',
142
204
 
143
205
    def test_internal_diff_iso_8859_1(self):
144
206
        output = StringIO()
145
 
        internal_diff(u'old_\xb5', ['old_text\n'],
146
 
                    u'new_\xe5', ['new_text\n'], output,
147
 
                    path_encoding='iso-8859-1')
 
207
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
208
                           u'new_\xe5', ['new_text\n'], output,
 
209
                           path_encoding='iso-8859-1')
148
210
        lines = output.getvalue().splitlines(True)
149
211
        self.check_patch(lines)
150
212
        self.assertEquals(['--- old_\xb5\n',
156
218
                          ]
157
219
                          , lines)
158
220
 
 
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
 
159
233
    def test_internal_diff_returns_bytes(self):
160
234
        import StringIO
161
235
        output = StringIO.StringIO()
162
 
        internal_diff(u'old_\xb5', ['old_text\n'],
163
 
                    u'new_\xe5', ['new_text\n'], output)
 
236
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
 
237
                            u'new_\xe5', ['new_text\n'], output)
164
238
        self.failUnless(isinstance(output.getvalue(), str),
165
239
            'internal_diff should return bytestrings')
166
240
 
167
241
 
168
 
class TestDiffDates(TestCaseWithTransport):
 
242
class TestDiffFiles(tests.TestCaseInTempDir):
 
243
 
 
244
    def test_external_diff_binary(self):
 
245
        """The output when using external diff should use diff's i18n error"""
 
246
        # Make sure external_diff doesn't fail in the current LANG
 
247
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
248
 
 
249
        cmd = ['diff', '-u', '--binary', 'old', 'new']
 
250
        open('old', 'wb').write('\x00foobar\n')
 
251
        open('new', 'wb').write('foo\x00bar\n')
 
252
        pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
 
253
                                     stdin=subprocess.PIPE)
 
254
        out, err = pipe.communicate()
 
255
        # Diff returns '2' on Binary files.
 
256
        self.assertEqual(2, pipe.returncode)
 
257
        # We should output whatever diff tells us, plus a trailing newline
 
258
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
 
259
 
 
260
 
 
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):
169
278
 
170
279
    def setUp(self):
171
280
        super(TestDiffDates, self).setUp()
205
314
        # set the date stamps for files in the working tree to known values
206
315
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
207
316
 
208
 
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
209
 
        output = StringIO()
210
 
        if working_tree is not None:
211
 
            extra_trees = (working_tree,)
212
 
        else:
213
 
            extra_trees = ()
214
 
        show_diff_trees(tree1, tree2, output, specific_files=specific_files,
215
 
                        extra_trees=extra_trees, old_label='old/', 
216
 
                        new_label='new/')
217
 
        return output.getvalue()
218
 
 
219
317
    def test_diff_rev_tree_working_tree(self):
220
318
        output = self.get_diff(self.wt.basis_tree(), self.wt)
221
319
        # note that the date for old/file1 is from rev 2 rather than from
243
341
+file2 contents at rev 3
244
342
 
245
343
''')
246
 
        
 
344
 
247
345
    def test_diff_add_files(self):
248
 
        tree1 = self.b.repository.revision_tree(None)
 
346
        tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
249
347
        tree2 = self.b.repository.revision_tree('rev-1')
250
348
        output = self.get_diff(tree1, tree2)
251
349
        # the files have the epoch time stamp for the tree in which
285
383
        self.wt.rename_one('file1', 'file1b')
286
384
        old_tree = self.b.repository.revision_tree('rev-1')
287
385
        new_tree = self.b.repository.revision_tree('rev-4')
288
 
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'], 
 
386
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'],
289
387
                            working_tree=self.wt)
290
388
        self.assertContainsRe(out, 'file1\t')
291
389
 
297
395
        self.wt.rename_one('file1', 'dir1/file1')
298
396
        old_tree = self.b.repository.revision_tree('rev-1')
299
397
        new_tree = self.b.repository.revision_tree('rev-4')
300
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'], 
 
398
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'],
301
399
                            working_tree=self.wt)
302
400
        self.assertContainsRe(out, 'file1\t')
303
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'], 
 
401
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'],
304
402
                            working_tree=self.wt)
305
403
        self.assertNotContainsRe(out, 'file1\t')
306
404
 
307
 
class TestPatienceDiffLib(TestCase):
 
405
 
 
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))
308
811
 
309
812
    def test_unique_lcs(self):
310
 
        unique_lcs = bzrlib.patiencediff.unique_lcs
 
813
        unique_lcs = self._unique_lcs
311
814
        self.assertEquals(unique_lcs('', ''), [])
 
815
        self.assertEquals(unique_lcs('', 'a'), [])
 
816
        self.assertEquals(unique_lcs('a', ''), [])
312
817
        self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
313
818
        self.assertEquals(unique_lcs('a', 'b'), [])
314
819
        self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
315
820
        self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
316
821
        self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
317
 
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1), 
 
822
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
318
823
                                                         (3,3), (4,4)])
319
824
        self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
320
825
 
321
826
    def test_recurse_matches(self):
322
827
        def test_one(a, b, matches):
323
828
            test_matches = []
324
 
            bzrlib.patiencediff.recurse_matches(a, b, 0, 0, len(a), len(b),
325
 
                test_matches, 10)
 
829
            self._recurse_matches(
 
830
                a, b, 0, 0, len(a), len(b), test_matches, 10)
326
831
            self.assertEquals(test_matches, matches)
327
832
 
328
833
        test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
329
834
                 [(0, 0), (2, 2), (4, 4)])
330
835
        test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
331
836
                 [(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)])
332
842
 
333
 
        # recurse_matches doesn't match non-unique 
 
843
        # recurse_matches doesn't match non-unique
334
844
        # lines surrounded by bogus text.
335
845
        # The update has been done in patiencediff.SequenceMatcher instead
336
846
 
340
850
        # This is what it currently gives:
341
851
        test_one('aBccDe', 'abccde', [(0,0), (5,5)])
342
852
 
 
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
 
343
867
    def test_matching_blocks(self):
344
 
        def chk_blocks(a, b, expected_blocks):
345
 
            # difflib always adds a signature of the total
346
 
            # length, with no matching entries at the end
347
 
            s = bzrlib.patiencediff.PatienceSequenceMatcher(None, a, b)
348
 
            blocks = s.get_matching_blocks()
349
 
            self.assertEquals((len(a), len(b), 0), blocks[-1])
350
 
            self.assertEquals(expected_blocks, blocks[:-1])
351
 
 
352
868
        # Some basic matching tests
353
 
        chk_blocks('', '', [])
354
 
        chk_blocks([], [], [])
355
 
        chk_blocks('abcd', 'abcd', [(0, 0, 4)])
356
 
        chk_blocks('abcd', 'abce', [(0, 0, 3)])
357
 
        chk_blocks('eabc', 'abce', [(1, 0, 3)])
358
 
        chk_blocks('eabce', 'abce', [(1, 0, 4)])
359
 
        chk_blocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
360
 
        chk_blocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
361
 
        chk_blocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
362
 
        # This may check too much, but it checks to see that 
 
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
363
881
        # a copied block stays attached to the previous section,
364
882
        # not the later one.
365
883
        # difflib would tend to grab the trailing longest match
366
884
        # which would make the diff not look right
367
 
        chk_blocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
368
 
                   [(0, 0, 6), (6, 11, 10)])
 
885
        self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
886
                              [(0, 0, 6), (6, 11, 10)])
369
887
 
370
888
        # make sure it supports passing in lists
371
 
        chk_blocks(
 
889
        self.assertDiffBlocks(
372
890
                   ['hello there\n',
373
891
                    'world\n',
374
892
                    'how are you today?\n'],
378
896
 
379
897
        # non unique lines surrounded by non-matching lines
380
898
        # won't be found
381
 
        chk_blocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
 
899
        self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
382
900
 
383
901
        # But they only need to be locally unique
384
 
        chk_blocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
 
902
        self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
385
903
 
386
904
        # non unique blocks won't be matched
387
 
        chk_blocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
 
905
        self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
388
906
 
389
907
        # but locally unique ones will
390
 
        chk_blocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
 
908
        self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
391
909
                                              (5,4,1), (7,5,2), (10,8,1)])
392
910
 
393
 
        chk_blocks('abbabbXd', 'cabbabxd', [(7,7,1)])
394
 
        chk_blocks('abbabbbb', 'cabbabbc', [])
395
 
        chk_blocks('bbbbbbbb', 'cbbbbbbc', [])
 
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)])
396
938
 
397
939
    def test_opcodes(self):
398
940
        def chk_ops(a, b, expected_codes):
399
 
            s = bzrlib.patiencediff.PatienceSequenceMatcher(None, a, b)
 
941
            s = self._PatienceSequenceMatcher(None, a, b)
400
942
            self.assertEquals(expected_codes, s.get_opcodes())
401
943
 
402
944
        chk_ops('', '', [])
403
945
        chk_ops([], [], [])
 
946
        chk_ops('abc', '', [('delete', 0,3, 0,0)])
 
947
        chk_ops('', 'abc', [('insert', 0,0, 0,3)])
404
948
        chk_ops('abcd', 'abcd', [('equal',    0,4, 0,4)])
405
949
        chk_ops('abcd', 'abce', [('equal',   0,3, 0,3),
406
950
                                 ('replace', 3,4, 3,4)
439
983
                 ('delete', 1,2, 1,1),
440
984
                 ('equal',  2,3, 1,2),
441
985
                ])
442
 
        chk_ops('aBccDe', 'abccde', 
 
986
        chk_ops('aBccDe', 'abccde',
443
987
                [('equal',   0,1, 0,1),
444
988
                 ('replace', 1,5, 1,5),
445
989
                 ('equal',   5,6, 5,6),
446
990
                ])
447
 
        chk_ops('aBcDec', 'abcdec', 
 
991
        chk_ops('aBcDec', 'abcdec',
448
992
                [('equal',   0,1, 0,1),
449
993
                 ('replace', 1,2, 1,2),
450
994
                 ('equal',   2,3, 2,3),
451
995
                 ('replace', 3,4, 3,4),
452
996
                 ('equal',   4,6, 4,6),
453
997
                ])
454
 
        chk_ops('aBcdEcdFg', 'abcdecdfg', 
 
998
        chk_ops('aBcdEcdFg', 'abcdecdfg',
455
999
                [('equal',   0,1, 0,1),
456
1000
                 ('replace', 1,8, 1,8),
457
1001
                 ('equal',   8,9, 8,9)
458
1002
                ])
459
 
        chk_ops('aBcdEeXcdFg', 'abcdecdfg', 
 
1003
        chk_ops('aBcdEeXcdFg', 'abcdecdfg',
460
1004
                [('equal',   0,1, 0,1),
461
1005
                 ('replace', 1,2, 1,2),
462
1006
                 ('equal',   2,4, 2,4),
468
1012
                 ('equal',   10,11, 8,9)
469
1013
                ])
470
1014
 
 
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
 
471
1052
    def test_multiple_ranges(self):
472
1053
        # There was an earlier bug where we used a bad set of ranges,
473
1054
        # this triggers that specific bug, to make sure it doesn't regress
474
 
        def chk_blocks(a, b, expected_blocks):
475
 
            # difflib always adds a signature of the total
476
 
            # length, with no matching entries at the end
477
 
            s = bzrlib.patiencediff.PatienceSequenceMatcher(None, a, b)
478
 
            blocks = s.get_matching_blocks()
479
 
            x = blocks.pop()
480
 
            self.assertEquals(x, (len(a), len(b), 0))
481
 
            self.assertEquals(expected_blocks, blocks)
482
 
 
483
 
        chk_blocks('abcdefghijklmnop'
484
 
                 , 'abcXghiYZQRSTUVWXYZijklmnop'
485
 
                 , [(0, 0, 3), (6, 4, 3), (9, 20, 7)])
486
 
 
487
 
        chk_blocks('ABCd efghIjk  L'
488
 
                 , 'AxyzBCn mo pqrstuvwI1 2  L'
489
 
                 , [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
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)])
490
1062
 
491
1063
        # These are rot13 code snippets.
492
 
        chk_blocks('''\
 
1064
        self.assertDiffBlocks('''\
493
1065
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
494
1066
    """
495
1067
    gnxrf_netf = ['svyr*']
496
1068
    gnxrf_bcgvbaf = ['ab-erphefr']
497
 
  
 
1069
 
498
1070
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
499
1071
        sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
500
1072
        vs vf_dhvrg():
508
1080
'''.splitlines(True), '''\
509
1081
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
510
1082
 
511
 
    --qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl 
 
1083
    --qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl
512
1084
    nqq gurz.
513
1085
    """
514
1086
    gnxrf_netf = ['svyr*']
541
1113
                 'how are you today?\n']
542
1114
        txt_b = ['hello there\n',
543
1115
                 'how are you today?\n']
544
 
        unified_diff = bzrlib.patiencediff.unified_diff
545
 
        psm = bzrlib.patiencediff.PatienceSequenceMatcher
546
 
        self.assertEquals([ '---  \n',
547
 
                           '+++  \n',
 
1116
        unified_diff = patiencediff.unified_diff
 
1117
        psm = self._PatienceSequenceMatcher
 
1118
        self.assertEquals(['--- \n',
 
1119
                           '+++ \n',
548
1120
                           '@@ -1,3 +1,2 @@\n',
549
1121
                           ' hello there\n',
550
1122
                           '-world\n',
555
1127
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
556
1128
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
557
1129
        # This is the result with LongestCommonSubstring matching
558
 
        self.assertEquals(['---  \n',
559
 
                           '+++  \n',
 
1130
        self.assertEquals(['--- \n',
 
1131
                           '+++ \n',
560
1132
                           '@@ -1,6 +1,11 @@\n',
561
1133
                           ' a\n',
562
1134
                           ' b\n',
571
1143
                           ' f\n']
572
1144
                          , list(unified_diff(txt_a, txt_b)))
573
1145
        # And the patience diff
574
 
        self.assertEquals(['---  \n',
575
 
                           '+++  \n',
 
1146
        self.assertEquals(['--- \n',
 
1147
                           '+++ \n',
576
1148
                           '@@ -4,6 +4,11 @@\n',
577
1149
                           ' d\n',
578
1150
                           ' e\n',
589
1161
                          , list(unified_diff(txt_a, txt_b,
590
1162
                                 sequencematcher=psm)))
591
1163
 
592
 
 
593
 
class TestPatienceDiffLibFiles(TestCaseInTempDir):
 
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
594
1218
 
595
1219
    def test_patience_unified_diff_files(self):
596
1220
        txt_a = ['hello there\n',
601
1225
        open('a1', 'wb').writelines(txt_a)
602
1226
        open('b1', 'wb').writelines(txt_b)
603
1227
 
604
 
        unified_diff_files = bzrlib.patiencediff.unified_diff_files
605
 
        psm = bzrlib.patiencediff.PatienceSequenceMatcher
606
 
        self.assertEquals(['--- a1 \n',
607
 
                           '+++ b1 \n',
 
1228
        unified_diff_files = patiencediff.unified_diff_files
 
1229
        psm = self._PatienceSequenceMatcher
 
1230
        self.assertEquals(['--- a1\n',
 
1231
                           '+++ b1\n',
608
1232
                           '@@ -1,3 +1,2 @@\n',
609
1233
                           ' hello there\n',
610
1234
                           '-world\n',
619
1243
        open('b2', 'wb').writelines(txt_b)
620
1244
 
621
1245
        # This is the result with LongestCommonSubstring matching
622
 
        self.assertEquals(['--- a2 \n',
623
 
                           '+++ b2 \n',
 
1246
        self.assertEquals(['--- a2\n',
 
1247
                           '+++ b2\n',
624
1248
                           '@@ -1,6 +1,11 @@\n',
625
1249
                           ' a\n',
626
1250
                           ' b\n',
636
1260
                          , list(unified_diff_files('a2', 'b2')))
637
1261
 
638
1262
        # And the patience diff
639
 
        self.assertEquals(['--- a2 \n',
640
 
                           '+++ b2 \n',
 
1263
        self.assertEquals(['--- a2\n',
 
1264
                           '+++ b2\n',
641
1265
                           '@@ -4,6 +4,11 @@\n',
642
1266
                           ' d\n',
643
1267
                           ' e\n',
653
1277
                          ]
654
1278
                          , list(unified_diff_files('a2', 'b2',
655
1279
                                 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