/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tests/test_diff.py

  • Committer: Jelmer Vernooij
  • Date: 2017-11-21 20:14:44 UTC
  • mfrom: (6821.1.1 ignore-warnings)
  • Revision ID: jelmer@jelmer.uk-20171121201444-dvb7yjku3zwjev83
Merge lp:~jelmer/brz/ignore-warnings.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2012, 2014, 2016, 2017 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
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import os
18
 
from cStringIO import StringIO
19
18
import subprocess
20
 
import sys
21
19
import tempfile
22
20
 
23
 
from bzrlib import (
 
21
from .. import (
24
22
    diff,
25
23
    errors,
26
24
    osutils,
32
30
    tests,
33
31
    transform,
34
32
    )
35
 
from bzrlib.symbol_versioning import deprecated_in
36
 
from bzrlib.tests import test_win32utils
37
 
 
38
 
 
39
 
class _AttribFeature(tests.Feature):
40
 
 
41
 
    def _probe(self):
42
 
        if (sys.platform not in ('cygwin', 'win32')):
43
 
            return False
44
 
        try:
45
 
            proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
46
 
        except OSError, e:
47
 
            return False
48
 
        return (0 == proc.wait())
49
 
 
50
 
    def feature_name(self):
51
 
        return 'attrib Windows command-line tool'
52
 
 
53
 
AttribFeature = _AttribFeature()
54
 
 
55
 
 
56
 
compiled_patiencediff_feature = tests.ModuleAvailableFeature(
57
 
                                    'bzrlib._patiencediff_c')
 
33
from ..sixish import (
 
34
    BytesIO,
 
35
    )
 
36
from ..tests import (
 
37
    features,
 
38
    EncodingAdapter,
 
39
    )
 
40
from ..tests.blackbox.test_diff import subst_dates
 
41
from ..tests.scenarios import load_tests_apply_scenarios
 
42
 
 
43
 
 
44
load_tests = load_tests_apply_scenarios
58
45
 
59
46
 
60
47
def udiff_lines(old, new, allow_binary=False):
61
 
    output = StringIO()
 
48
    output = BytesIO()
62
49
    diff.internal_diff('old', old, 'new', new, output, allow_binary)
63
50
    output.seek(0, 0)
64
51
    return output.readlines()
66
53
 
67
54
def external_udiff_lines(old, new, use_stringio=False):
68
55
    if use_stringio:
69
 
        # StringIO has no fileno, so it tests a different codepath
70
 
        output = StringIO()
 
56
        # BytesIO has no fileno, so it tests a different codepath
 
57
        output = BytesIO()
71
58
    else:
72
59
        output = tempfile.TemporaryFile()
73
60
    try:
80
67
    return lines
81
68
 
82
69
 
 
70
class StubO(object):
 
71
    """Simple file-like object that allows writes with any type and records."""
 
72
 
 
73
    def __init__(self):
 
74
        self.write_record = []
 
75
 
 
76
    def write(self, data):
 
77
        self.write_record.append(data)
 
78
 
 
79
    def check_types(self, testcase, expected_type):
 
80
        testcase.assertFalse(
 
81
            any(not isinstance(o, expected_type) for o in self.write_record),
 
82
            "Not all writes of type %s: %r" % (
 
83
                expected_type.__name__, self.write_record))
 
84
 
 
85
 
 
86
class TestDiffOptions(tests.TestCase):
 
87
 
 
88
    def test_unified_added(self):
 
89
        """Check for default style '-u' only if no other style specified
 
90
        in 'diff-options'.
 
91
        """
 
92
        # Verify that style defaults to unified, id est '-u' appended
 
93
        # to option list, in the absence of an alternative style.
 
94
        self.assertEqual(['-a', '-u'], diff.default_style_unified(['-a']))
 
95
 
 
96
 
 
97
class TestDiffOptionsScenarios(tests.TestCase):
 
98
 
 
99
    scenarios = [(s, dict(style=s)) for s in diff.style_option_list]
 
100
    style = None # Set by load_tests_apply_scenarios from scenarios
 
101
 
 
102
    def test_unified_not_added(self):
 
103
        # Verify that for all valid style options, '-u' is not
 
104
        # appended to option list.
 
105
        ret_opts = diff.default_style_unified(diff_opts=["%s" % (self.style,)])
 
106
        self.assertEqual(["%s" % (self.style,)], ret_opts)
 
107
 
 
108
 
83
109
class TestDiff(tests.TestCase):
84
110
 
85
111
    def test_add_nl(self):
86
112
        """diff generates a valid diff for patches that add a newline"""
87
113
        lines = udiff_lines(['boo'], ['boo\n'])
88
114
        self.check_patch(lines)
89
 
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
 
115
        self.assertEqual(lines[4], '\\ No newline at end of file\n')
90
116
            ## "expected no-nl, got %r" % lines[4]
91
117
 
92
118
    def test_add_nl_2(self):
95
121
        """
96
122
        lines = udiff_lines(['boo'], ['goo\n'])
97
123
        self.check_patch(lines)
98
 
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
 
124
        self.assertEqual(lines[4], '\\ No newline at end of file\n')
99
125
            ## "expected no-nl, got %r" % lines[4]
100
126
 
101
127
    def test_remove_nl(self):
104
130
        """
105
131
        lines = udiff_lines(['boo\n'], ['boo'])
106
132
        self.check_patch(lines)
107
 
        self.assertEquals(lines[5], '\\ No newline at end of file\n')
 
133
        self.assertEqual(lines[5], '\\ No newline at end of file\n')
108
134
            ## "expected no-nl, got %r" % lines[5]
109
135
 
110
136
    def check_patch(self, lines):
111
 
        self.assert_(len(lines) > 1)
 
137
        self.assertTrue(len(lines) > 1)
112
138
            ## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
113
 
        self.assert_(lines[0].startswith ('---'))
 
139
        self.assertTrue(lines[0].startswith ('---'))
114
140
            ## 'No orig line for patch:\n%s' % "".join(lines)
115
 
        self.assert_(lines[1].startswith ('+++'))
 
141
        self.assertTrue(lines[1].startswith ('+++'))
116
142
            ## 'No mod line for patch:\n%s' % "".join(lines)
117
 
        self.assert_(len(lines) > 2)
 
143
        self.assertTrue(len(lines) > 2)
118
144
            ## "No hunks for patch:\n%s" % "".join(lines)
119
 
        self.assert_(lines[2].startswith('@@'))
 
145
        self.assertTrue(lines[2].startswith('@@'))
120
146
            ## "No hunk header for patch:\n%s" % "".join(lines)
121
 
        self.assert_('@@' in lines[2][2:])
 
147
        self.assertTrue('@@' in lines[2][2:])
122
148
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
123
149
 
124
150
    def test_binary_lines(self):
125
151
        empty = []
126
152
        uni_lines = [1023 * 'a' + '\x00']
127
 
        self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines , empty)
 
153
        self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines, empty)
128
154
        self.assertRaises(errors.BinaryFile, udiff_lines, empty, uni_lines)
129
 
        udiff_lines(uni_lines , empty, allow_binary=True)
 
155
        udiff_lines(uni_lines, empty, allow_binary=True)
130
156
        udiff_lines(empty, uni_lines, allow_binary=True)
131
157
 
132
158
    def test_external_diff(self):
143
169
        self.check_patch(lines)
144
170
 
145
171
    def test_external_diff_binary_lang_c(self):
146
 
        old_env = {}
147
172
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
148
 
            old_env[lang] = osutils.set_or_unset_env(lang, 'C')
149
 
        try:
150
 
            lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
151
 
            # Older versions of diffutils say "Binary files", newer
152
 
            # versions just say "Files".
153
 
            self.assertContainsRe(lines[0],
154
 
                                  '(Binary f|F)iles old and new differ\n')
155
 
            self.assertEquals(lines[1:], ['\n'])
156
 
        finally:
157
 
            for lang, old_val in old_env.iteritems():
158
 
                osutils.set_or_unset_env(lang, old_val)
 
173
            self.overrideEnv(lang, 'C')
 
174
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
175
        # Older versions of diffutils say "Binary files", newer
 
176
        # versions just say "Files".
 
177
        self.assertContainsRe(lines[0], '(Binary f|F)iles old and new differ\n')
 
178
        self.assertEqual(lines[1:], ['\n'])
159
179
 
160
180
    def test_no_external_diff(self):
161
181
        """Check that NoDiff is raised when diff is not available"""
162
 
        # Use os.environ['PATH'] to make sure no 'diff' command is available
163
 
        orig_path = os.environ['PATH']
164
 
        try:
165
 
            os.environ['PATH'] = ''
166
 
            self.assertRaises(errors.NoDiff, diff.external_diff,
167
 
                              'old', ['boo\n'], 'new', ['goo\n'],
168
 
                              StringIO(), diff_opts=['-u'])
169
 
        finally:
170
 
            os.environ['PATH'] = orig_path
 
182
        # Make sure no 'diff' command is available
 
183
        # XXX: Weird, using None instead of '' breaks the test -- vila 20101216
 
184
        self.overrideEnv('PATH', '')
 
185
        self.assertRaises(errors.NoDiff, diff.external_diff,
 
186
                          'old', ['boo\n'], 'new', ['goo\n'],
 
187
                          BytesIO(), diff_opts=['-u'])
171
188
 
172
189
    def test_internal_diff_default(self):
173
190
        # Default internal diff encoding is utf8
174
 
        output = StringIO()
 
191
        output = BytesIO()
175
192
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
176
193
                           u'new_\xe5', ['new_text\n'], output)
177
194
        lines = output.getvalue().splitlines(True)
178
195
        self.check_patch(lines)
179
 
        self.assertEquals(['--- old_\xc2\xb5\n',
 
196
        self.assertEqual(['--- old_\xc2\xb5\n',
180
197
                           '+++ new_\xc3\xa5\n',
181
198
                           '@@ -1,1 +1,1 @@\n',
182
199
                           '-old_text\n',
186
203
                          , lines)
187
204
 
188
205
    def test_internal_diff_utf8(self):
189
 
        output = StringIO()
 
206
        output = BytesIO()
190
207
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
191
208
                           u'new_\xe5', ['new_text\n'], output,
192
209
                           path_encoding='utf8')
193
210
        lines = output.getvalue().splitlines(True)
194
211
        self.check_patch(lines)
195
 
        self.assertEquals(['--- old_\xc2\xb5\n',
 
212
        self.assertEqual(['--- old_\xc2\xb5\n',
196
213
                           '+++ new_\xc3\xa5\n',
197
214
                           '@@ -1,1 +1,1 @@\n',
198
215
                           '-old_text\n',
202
219
                          , lines)
203
220
 
204
221
    def test_internal_diff_iso_8859_1(self):
205
 
        output = StringIO()
 
222
        output = BytesIO()
206
223
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
207
224
                           u'new_\xe5', ['new_text\n'], output,
208
225
                           path_encoding='iso-8859-1')
209
226
        lines = output.getvalue().splitlines(True)
210
227
        self.check_patch(lines)
211
 
        self.assertEquals(['--- old_\xb5\n',
 
228
        self.assertEqual(['--- old_\xb5\n',
212
229
                           '+++ new_\xe5\n',
213
230
                           '@@ -1,1 +1,1 @@\n',
214
231
                           '-old_text\n',
218
235
                          , lines)
219
236
 
220
237
    def test_internal_diff_no_content(self):
221
 
        output = StringIO()
 
238
        output = BytesIO()
222
239
        diff.internal_diff(u'old', [], u'new', [], output)
223
240
        self.assertEqual('', output.getvalue())
224
241
 
225
242
    def test_internal_diff_no_changes(self):
226
 
        output = StringIO()
 
243
        output = BytesIO()
227
244
        diff.internal_diff(u'old', ['text\n', 'contents\n'],
228
245
                           u'new', ['text\n', 'contents\n'],
229
246
                           output)
230
247
        self.assertEqual('', output.getvalue())
231
248
 
232
249
    def test_internal_diff_returns_bytes(self):
233
 
        import StringIO
234
 
        output = StringIO.StringIO()
 
250
        output = StubO()
235
251
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
236
252
                            u'new_\xe5', ['new_text\n'], output)
237
 
        self.failUnless(isinstance(output.getvalue(), str),
238
 
            'internal_diff should return bytestrings')
 
253
        output.check_types(self, bytes)
 
254
 
 
255
    def test_internal_diff_default_context(self):
 
256
        output = BytesIO()
 
257
        diff.internal_diff('old', ['same_text\n', 'same_text\n', 'same_text\n',
 
258
                           'same_text\n', 'same_text\n', 'old_text\n'],
 
259
                           'new', ['same_text\n', 'same_text\n', 'same_text\n',
 
260
                           'same_text\n', 'same_text\n', 'new_text\n'], output)
 
261
        lines = output.getvalue().splitlines(True)
 
262
        self.check_patch(lines)
 
263
        self.assertEqual(['--- old\n',
 
264
                           '+++ new\n',
 
265
                           '@@ -3,4 +3,4 @@\n',
 
266
                           ' same_text\n',
 
267
                           ' same_text\n',
 
268
                           ' same_text\n',
 
269
                           '-old_text\n',
 
270
                           '+new_text\n',
 
271
                           '\n',
 
272
                          ]
 
273
                          , lines)
 
274
 
 
275
    def test_internal_diff_no_context(self):
 
276
        output = BytesIO()
 
277
        diff.internal_diff('old', ['same_text\n', 'same_text\n', 'same_text\n',
 
278
                           'same_text\n', 'same_text\n', 'old_text\n'],
 
279
                           'new', ['same_text\n', 'same_text\n', 'same_text\n',
 
280
                           'same_text\n', 'same_text\n', 'new_text\n'], output,
 
281
                           context_lines=0)
 
282
        lines = output.getvalue().splitlines(True)
 
283
        self.check_patch(lines)
 
284
        self.assertEqual(['--- old\n',
 
285
                           '+++ new\n',
 
286
                           '@@ -6,1 +6,1 @@\n',
 
287
                           '-old_text\n',
 
288
                           '+new_text\n',
 
289
                           '\n',
 
290
                          ]
 
291
                          , lines)
 
292
 
 
293
    def test_internal_diff_more_context(self):
 
294
        output = BytesIO()
 
295
        diff.internal_diff('old', ['same_text\n', 'same_text\n', 'same_text\n',
 
296
                           'same_text\n', 'same_text\n', 'old_text\n'],
 
297
                           'new', ['same_text\n', 'same_text\n', 'same_text\n',
 
298
                           'same_text\n', 'same_text\n', 'new_text\n'], output,
 
299
                           context_lines=4)
 
300
        lines = output.getvalue().splitlines(True)
 
301
        self.check_patch(lines)
 
302
        self.assertEqual(['--- old\n',
 
303
                           '+++ new\n',
 
304
                           '@@ -2,5 +2,5 @@\n',
 
305
                           ' same_text\n',
 
306
                           ' same_text\n',
 
307
                           ' same_text\n',
 
308
                           ' same_text\n',
 
309
                           '-old_text\n',
 
310
                           '+new_text\n',
 
311
                           '\n',
 
312
                          ]
 
313
                          , lines)
 
314
 
 
315
 
 
316
 
239
317
 
240
318
 
241
319
class TestDiffFiles(tests.TestCaseInTempDir):
242
320
 
243
321
    def test_external_diff_binary(self):
244
322
        """The output when using external diff should use diff's i18n error"""
 
323
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
 
324
            self.overrideEnv(lang, 'C')
245
325
        # Make sure external_diff doesn't fail in the current LANG
246
326
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
247
327
 
248
328
        cmd = ['diff', '-u', '--binary', 'old', 'new']
249
 
        open('old', 'wb').write('\x00foobar\n')
250
 
        open('new', 'wb').write('foo\x00bar\n')
 
329
        with open('old', 'wb') as f: f.write('\x00foobar\n')
 
330
        with open('new', 'wb') as f: f.write('foo\x00bar\n')
251
331
        pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
252
332
                                     stdin=subprocess.PIPE)
253
333
        out, err = pipe.communicate()
254
 
        # Diff returns '2' on Binary files.
255
 
        self.assertEqual(2, pipe.returncode)
256
334
        # We should output whatever diff tells us, plus a trailing newline
257
335
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
258
336
 
259
337
 
260
 
class TestShowDiffTreesHelper(tests.TestCaseWithTransport):
261
 
    """Has a helper for running show_diff_trees"""
262
 
 
263
 
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
264
 
        output = StringIO()
265
 
        if working_tree is not None:
266
 
            extra_trees = (working_tree,)
267
 
        else:
268
 
            extra_trees = ()
269
 
        diff.show_diff_trees(tree1, tree2, output,
270
 
                             specific_files=specific_files,
271
 
                             extra_trees=extra_trees, old_label='old/',
272
 
                             new_label='new/')
273
 
        return output.getvalue()
274
 
 
275
 
 
276
 
class TestDiffDates(TestShowDiffTreesHelper):
 
338
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
 
339
    output = BytesIO()
 
340
    if working_tree is not None:
 
341
        extra_trees = (working_tree,)
 
342
    else:
 
343
        extra_trees = ()
 
344
    diff.show_diff_trees(tree1, tree2, output,
 
345
        specific_files=specific_files,
 
346
        extra_trees=extra_trees, old_label='old/',
 
347
        new_label='new/')
 
348
    return output.getvalue()
 
349
 
 
350
 
 
351
class TestDiffDates(tests.TestCaseWithTransport):
277
352
 
278
353
    def setUp(self):
279
354
        super(TestDiffDates, self).setUp()
314
389
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
315
390
 
316
391
    def test_diff_rev_tree_working_tree(self):
317
 
        output = self.get_diff(self.wt.basis_tree(), self.wt)
 
392
        output = get_diff_as_string(self.wt.basis_tree(), self.wt)
318
393
        # note that the date for old/file1 is from rev 2 rather than from
319
394
        # the basis revision (rev 4)
320
395
        self.assertEqualDiff(output, '''\
330
405
    def test_diff_rev_tree_rev_tree(self):
331
406
        tree1 = self.b.repository.revision_tree('rev-2')
332
407
        tree2 = self.b.repository.revision_tree('rev-3')
333
 
        output = self.get_diff(tree1, tree2)
 
408
        output = get_diff_as_string(tree1, tree2)
334
409
        self.assertEqualDiff(output, '''\
335
410
=== modified file 'file2'
336
411
--- old/file2\t2006-04-01 00:00:00 +0000
344
419
    def test_diff_add_files(self):
345
420
        tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
346
421
        tree2 = self.b.repository.revision_tree('rev-1')
347
 
        output = self.get_diff(tree1, tree2)
 
422
        output = get_diff_as_string(tree1, tree2)
348
423
        # the files have the epoch time stamp for the tree in which
349
424
        # they don't exist.
350
425
        self.assertEqualDiff(output, '''\
365
440
    def test_diff_remove_files(self):
366
441
        tree1 = self.b.repository.revision_tree('rev-3')
367
442
        tree2 = self.b.repository.revision_tree('rev-4')
368
 
        output = self.get_diff(tree1, tree2)
 
443
        output = get_diff_as_string(tree1, tree2)
369
444
        # the file has the epoch time stamp for the tree in which
370
445
        # it doesn't exist.
371
446
        self.assertEqualDiff(output, '''\
382
457
        self.wt.rename_one('file1', 'file1b')
383
458
        old_tree = self.b.repository.revision_tree('rev-1')
384
459
        new_tree = self.b.repository.revision_tree('rev-4')
385
 
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'],
 
460
        out = get_diff_as_string(old_tree, new_tree, specific_files=['file1b'],
386
461
                            working_tree=self.wt)
387
462
        self.assertContainsRe(out, 'file1\t')
388
463
 
394
469
        self.wt.rename_one('file1', 'dir1/file1')
395
470
        old_tree = self.b.repository.revision_tree('rev-1')
396
471
        new_tree = self.b.repository.revision_tree('rev-4')
397
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'],
 
472
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
398
473
                            working_tree=self.wt)
399
474
        self.assertContainsRe(out, 'file1\t')
400
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'],
 
475
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
401
476
                            working_tree=self.wt)
402
477
        self.assertNotContainsRe(out, 'file1\t')
403
478
 
404
479
 
405
 
 
406
 
class TestShowDiffTrees(TestShowDiffTreesHelper):
 
480
class TestShowDiffTrees(tests.TestCaseWithTransport):
407
481
    """Direct tests for show_diff_trees"""
408
482
 
409
483
    def test_modified_file(self):
414
488
        tree.commit('one', rev_id='rev-1')
415
489
 
416
490
        self.build_tree_contents([('tree/file', 'new contents\n')])
417
 
        d = self.get_diff(tree.basis_tree(), tree)
 
491
        d = get_diff_as_string(tree.basis_tree(), tree)
418
492
        self.assertContainsRe(d, "=== modified file 'file'\n")
419
493
        self.assertContainsRe(d, '--- old/file\t')
420
494
        self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
431
505
 
432
506
        tree.rename_one('dir', 'other')
433
507
        self.build_tree_contents([('tree/other/file', 'new contents\n')])
434
 
        d = self.get_diff(tree.basis_tree(), tree)
 
508
        d = get_diff_as_string(tree.basis_tree(), tree)
435
509
        self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
436
510
        self.assertContainsRe(d, "=== modified file 'other/file'\n")
437
511
        # XXX: This is technically incorrect, because it used to be at another
450
524
        tree.commit('one', rev_id='rev-1')
451
525
 
452
526
        tree.rename_one('dir', 'newdir')
453
 
        d = self.get_diff(tree.basis_tree(), tree)
 
527
        d = get_diff_as_string(tree.basis_tree(), tree)
454
528
        # Renaming a directory should be a single "you renamed this dir" even
455
529
        # when there are files inside.
456
530
        self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
463
537
        tree.commit('one', rev_id='rev-1')
464
538
 
465
539
        tree.rename_one('file', 'newname')
466
 
        d = self.get_diff(tree.basis_tree(), tree)
 
540
        d = get_diff_as_string(tree.basis_tree(), tree)
467
541
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
468
542
        # We shouldn't have a --- or +++ line, because there is no content
469
543
        # change
478
552
 
479
553
        tree.rename_one('file', 'newname')
480
554
        self.build_tree_contents([('tree/newname', 'new contents\n')])
481
 
        d = self.get_diff(tree.basis_tree(), tree)
 
555
        d = get_diff_as_string(tree.basis_tree(), tree)
482
556
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
483
557
        self.assertContainsRe(d, '--- old/file\t')
484
558
        self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
508
582
        tree.rename_one('c', 'new-c')
509
583
        tree.rename_one('d', 'new-d')
510
584
 
511
 
        d = self.get_diff(tree.basis_tree(), tree)
 
585
        d = get_diff_as_string(tree.basis_tree(), tree)
512
586
 
513
587
        self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
514
 
                                  ".*\+x to -x.*\)")
 
588
                                 r".*\+x to -x.*\)")
515
589
        self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
516
 
                                  ".*-x to \+x.*\)")
 
590
                                 r".*-x to \+x.*\)")
517
591
        self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
518
 
                                  ".*\+x to -x.*\)")
 
592
                                 r".*\+x to -x.*\)")
519
593
        self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
520
 
                                  ".*-x to \+x.*\)")
 
594
                                 r".*-x to \+x.*\)")
521
595
        self.assertNotContainsRe(d, r"file 'e'")
522
596
        self.assertNotContainsRe(d, r"file 'f'")
523
597
 
524
 
 
525
598
    def test_binary_unicode_filenames(self):
526
599
        """Test that contents of files are *not* encoded in UTF-8 when there
527
600
        is a binary file in the diff.
528
601
        """
529
602
        # See https://bugs.launchpad.net/bugs/110092.
530
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
603
        self.requireFeature(features.UnicodeFilenameFeature)
531
604
 
532
 
        # This bug isn't triggered with cStringIO.
533
 
        from StringIO import StringIO
534
605
        tree = self.make_branch_and_tree('tree')
535
606
        alpha, omega = u'\u03b1', u'\u03c9'
536
607
        alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
540
611
              ('The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
541
612
        tree.add([alpha], ['file-id'])
542
613
        tree.add([omega], ['file-id-2'])
543
 
        diff_content = StringIO()
 
614
        diff_content = StubO()
544
615
        diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
545
 
        d = diff_content.getvalue()
 
616
        diff_content.check_types(self, bytes)
 
617
        d = b''.join(diff_content.write_record)
546
618
        self.assertContainsRe(d, r"=== added file '%s'" % alpha_utf8)
547
619
        self.assertContainsRe(d, "Binary files a/%s.*and b/%s.* differ\n"
548
620
                              % (alpha_utf8, alpha_utf8))
552
624
 
553
625
    def test_unicode_filename(self):
554
626
        """Test when the filename are unicode."""
555
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
627
        self.requireFeature(features.UnicodeFilenameFeature)
556
628
 
557
629
        alpha, omega = u'\u03b1', u'\u03c9'
558
630
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
573
645
        tree.add(['add_'+alpha], ['file-id'])
574
646
        self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
575
647
 
576
 
        d = self.get_diff(tree.basis_tree(), tree)
 
648
        d = get_diff_as_string(tree.basis_tree(), tree)
577
649
        self.assertContainsRe(d,
578
650
                "=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
579
651
        self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
580
652
        self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
581
653
        self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
582
654
 
 
655
    def test_unicode_filename_path_encoding(self):
 
656
        """Test for bug #382699: unicode filenames on Windows should be shown
 
657
        in user encoding.
 
658
        """
 
659
        self.requireFeature(features.UnicodeFilenameFeature)
 
660
        # The word 'test' in Russian
 
661
        _russian_test = u'\u0422\u0435\u0441\u0442'
 
662
        directory = _russian_test + u'/'
 
663
        test_txt = _russian_test + u'.txt'
 
664
        u1234 = u'\u1234.txt'
 
665
 
 
666
        tree = self.make_branch_and_tree('.')
 
667
        self.build_tree_contents([
 
668
            (test_txt, 'foo\n'),
 
669
            (u1234, 'foo\n'),
 
670
            (directory, None),
 
671
            ])
 
672
        tree.add([test_txt, u1234, directory])
 
673
 
 
674
        sio = BytesIO()
 
675
        diff.show_diff_trees(tree.basis_tree(), tree, sio,
 
676
            path_encoding='cp1251')
 
677
 
 
678
        output = subst_dates(sio.getvalue())
 
679
        shouldbe = ('''\
 
680
=== added directory '%(directory)s'
 
681
=== added file '%(test_txt)s'
 
682
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
683
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
684
@@ -0,0 +1,1 @@
 
685
+foo
 
686
 
 
687
=== added file '?.txt'
 
688
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
689
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
690
@@ -0,0 +1,1 @@
 
691
+foo
 
692
 
 
693
''' % {'directory': _russian_test.encode('cp1251'),
 
694
       'test_txt': test_txt.encode('cp1251'),
 
695
      })
 
696
        self.assertEqualDiff(output, shouldbe)
 
697
 
583
698
 
584
699
class DiffWasIs(diff.DiffPath):
585
700
 
586
701
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
587
702
        self.to_file.write('was: ')
588
 
        self.to_file.write(self.old_tree.get_file(file_id).read())
 
703
        self.to_file.write(self.old_tree.get_file(old_path).read())
589
704
        self.to_file.write('is: ')
590
 
        self.to_file.write(self.new_tree.get_file(file_id).read())
591
 
        pass
 
705
        self.to_file.write(self.new_tree.get_file(new_path).read())
592
706
 
593
707
 
594
708
class TestDiffTree(tests.TestCaseWithTransport):
601
715
        self.new_tree = self.make_branch_and_tree('new-tree')
602
716
        self.new_tree.lock_write()
603
717
        self.addCleanup(self.new_tree.unlock)
604
 
        self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
 
718
        self.differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
605
719
 
606
720
    def test_diff_text(self):
607
721
        self.build_tree_contents([('old-tree/olddir/',),
612
726
                                  ('new-tree/newdir/newfile', 'new\n')])
613
727
        self.new_tree.add('newdir')
614
728
        self.new_tree.add('newdir/newfile', 'file-id')
615
 
        differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
616
 
        differ.diff_text('file-id', None, 'old label', 'new label')
 
729
        differ = diff.DiffText(self.old_tree, self.new_tree, BytesIO())
 
730
        differ.diff_text('olddir/oldfile', None, 'old label',
 
731
                         'new label', 'file-id', None)
617
732
        self.assertEqual(
618
733
            '--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
619
734
            differ.to_file.getvalue())
620
735
        differ.to_file.seek(0)
621
 
        differ.diff_text(None, 'file-id', 'old label', 'new label')
 
736
        differ.diff_text(None, 'newdir/newfile',
 
737
                         'old label', 'new label', None, 'file-id')
622
738
        self.assertEqual(
623
739
            '--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
624
740
            differ.to_file.getvalue())
625
741
        differ.to_file.seek(0)
626
 
        differ.diff_text('file-id', 'file-id', 'old label', 'new label')
 
742
        differ.diff_text('olddir/oldfile', 'newdir/newfile',
 
743
                         'old label', 'new label', 'file-id', 'file-id')
627
744
        self.assertEqual(
628
745
            '--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
629
746
            differ.to_file.getvalue())
644
761
        self.new_tree.add('file', 'file-id')
645
762
        os.unlink('old-tree/file')
646
763
        self.differ.show_diff(None)
647
 
        self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
 
764
        self.assertContainsRe(self.differ.to_file.getvalue(), r'\+contents')
648
765
 
649
766
    def test_diff_symlink(self):
650
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
767
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
651
768
        differ.diff_symlink('old target', None)
652
769
        self.assertEqual("=== target was 'old target'\n",
653
770
                         differ.to_file.getvalue())
654
771
 
655
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
772
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
656
773
        differ.diff_symlink(None, 'new target')
657
774
        self.assertEqual("=== target is 'new target'\n",
658
775
                         differ.to_file.getvalue())
659
776
 
660
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
777
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
661
778
        differ.diff_symlink('old target', 'new target')
662
779
        self.assertEqual("=== target changed 'old target' => 'new target'\n",
663
780
                         differ.to_file.getvalue())
675
792
        self.assertContainsRe(
676
793
            self.differ.to_file.getvalue(),
677
794
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
678
 
             ' \@\@\n-old\n\+new\n\n')
 
795
            r' \@\@\n-old\n\+new\n\n')
679
796
 
680
797
    def test_diff_kind_change(self):
681
 
        self.requireFeature(tests.SymlinkFeature)
 
798
        self.requireFeature(features.SymlinkFeature)
682
799
        self.build_tree_contents([('old-tree/olddir/',),
683
800
                                  ('old-tree/olddir/oldfile', 'old\n')])
684
801
        self.old_tree.add('olddir')
691
808
        self.assertContainsRe(
692
809
            self.differ.to_file.getvalue(),
693
810
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
694
 
             ' \@\@\n-old\n\n')
 
811
            r' \@\@\n-old\n\n')
695
812
        self.assertContainsRe(self.differ.to_file.getvalue(),
696
813
                              "=== target is u'new'\n")
697
814
 
717
834
        diff.DiffTree.diff_factories=old_diff_factories[:]
718
835
        diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
719
836
        try:
720
 
            differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
 
837
            differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
721
838
        finally:
722
839
            diff.DiffTree.diff_factories = old_diff_factories
723
840
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
724
841
        self.assertNotContainsRe(
725
842
            differ.to_file.getvalue(),
726
843
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
727
 
             ' \@\@\n-old\n\+new\n\n')
 
844
            r' \@\@\n-old\n\+new\n\n')
728
845
        self.assertContainsRe(differ.to_file.getvalue(),
729
846
                              'was: old\nis: new\n')
730
847
 
731
848
    def test_extra_factories(self):
732
849
        self.create_old_new()
733
 
        differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
 
850
        differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO(),
734
851
                               extra_factories=[DiffWasIs.from_diff_tree])
735
852
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
736
853
        self.assertNotContainsRe(
737
854
            differ.to_file.getvalue(),
738
855
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
739
 
             ' \@\@\n-old\n\+new\n\n')
 
856
            r' \@\@\n-old\n\+new\n\n')
740
857
        self.assertContainsRe(differ.to_file.getvalue(),
741
858
                              'was: old\nis: new\n')
742
859
 
764
881
        b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
765
882
        sm = self._PatienceSequenceMatcher(None, a, b)
766
883
        mb = sm.get_matching_blocks()
767
 
        self.assertEquals(35, len(mb))
 
884
        self.assertEqual(35, len(mb))
768
885
 
769
886
    def test_unique_lcs(self):
770
887
        unique_lcs = self._unique_lcs
771
 
        self.assertEquals(unique_lcs('', ''), [])
772
 
        self.assertEquals(unique_lcs('', 'a'), [])
773
 
        self.assertEquals(unique_lcs('a', ''), [])
774
 
        self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
775
 
        self.assertEquals(unique_lcs('a', 'b'), [])
776
 
        self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
777
 
        self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
778
 
        self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
779
 
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
780
 
                                                         (3,3), (4,4)])
781
 
        self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
 
888
        self.assertEqual(unique_lcs('', ''), [])
 
889
        self.assertEqual(unique_lcs('', 'a'), [])
 
890
        self.assertEqual(unique_lcs('a', ''), [])
 
891
        self.assertEqual(unique_lcs('a', 'a'), [(0, 0)])
 
892
        self.assertEqual(unique_lcs('a', 'b'), [])
 
893
        self.assertEqual(unique_lcs('ab', 'ab'), [(0, 0), (1, 1)])
 
894
        self.assertEqual(unique_lcs('abcde', 'cdeab'), [(2, 0), (3, 1), (4, 2)])
 
895
        self.assertEqual(unique_lcs('cdeab', 'abcde'), [(0, 2), (1, 3), (2, 4)])
 
896
        self.assertEqual(unique_lcs('abXde', 'abYde'), [(0, 0), (1, 1),
 
897
                                                         (3, 3), (4, 4)])
 
898
        self.assertEqual(unique_lcs('acbac', 'abc'), [(2, 1)])
782
899
 
783
900
    def test_recurse_matches(self):
784
901
        def test_one(a, b, matches):
785
902
            test_matches = []
786
903
            self._recurse_matches(
787
904
                a, b, 0, 0, len(a), len(b), test_matches, 10)
788
 
            self.assertEquals(test_matches, matches)
 
905
            self.assertEqual(test_matches, matches)
789
906
 
790
907
        test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
791
908
                 [(0, 0), (2, 2), (4, 4)])
794
911
        # Even though 'bc' is not unique globally, and is surrounded by
795
912
        # non-matching lines, we should still match, because they are locally
796
913
        # unique
797
 
        test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
 
914
        test_one('abcdbce', 'afbcgdbce', [(0, 0), (1, 2), (2, 3), (3, 5),
798
915
                                          (4, 6), (5, 7), (6, 8)])
799
916
 
800
917
        # recurse_matches doesn't match non-unique
805
922
        #test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
806
923
 
807
924
        # This is what it currently gives:
808
 
        test_one('aBccDe', 'abccde', [(0,0), (5,5)])
 
925
        test_one('aBccDe', 'abccde', [(0, 0), (5, 5)])
809
926
 
810
927
    def assertDiffBlocks(self, a, b, expected_blocks):
811
928
        """Check that the sequence matcher returns the correct blocks.
853
970
 
854
971
        # non unique lines surrounded by non-matching lines
855
972
        # won't be found
856
 
        self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
 
973
        self.assertDiffBlocks('aBccDe', 'abccde', [(0, 0, 1), (5, 5, 1)])
857
974
 
858
975
        # But they only need to be locally unique
859
 
        self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
 
976
        self.assertDiffBlocks('aBcDec', 'abcdec', [(0, 0, 1), (2, 2, 1), (4, 4, 2)])
860
977
 
861
978
        # non unique blocks won't be matched
862
 
        self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
 
979
        self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0, 0, 1), (8, 8, 1)])
863
980
 
864
981
        # but locally unique ones will
865
 
        self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
866
 
                                              (5,4,1), (7,5,2), (10,8,1)])
 
982
        self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0, 0, 1), (2, 2, 2),
 
983
                                              (5, 4, 1), (7, 5, 2), (10, 8, 1)])
867
984
 
868
 
        self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
 
985
        self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7, 7, 1)])
869
986
        self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
870
987
        self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
871
988
 
896
1013
    def test_opcodes(self):
897
1014
        def chk_ops(a, b, expected_codes):
898
1015
            s = self._PatienceSequenceMatcher(None, a, b)
899
 
            self.assertEquals(expected_codes, s.get_opcodes())
 
1016
            self.assertEqual(expected_codes, s.get_opcodes())
900
1017
 
901
1018
        chk_ops('', '', [])
902
1019
        chk_ops([], [], [])
903
 
        chk_ops('abc', '', [('delete', 0,3, 0,0)])
904
 
        chk_ops('', 'abc', [('insert', 0,0, 0,3)])
905
 
        chk_ops('abcd', 'abcd', [('equal',    0,4, 0,4)])
906
 
        chk_ops('abcd', 'abce', [('equal',   0,3, 0,3),
907
 
                                 ('replace', 3,4, 3,4)
908
 
                                ])
909
 
        chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
910
 
                                 ('equal',  1,4, 0,3),
911
 
                                 ('insert', 4,4, 3,4)
912
 
                                ])
913
 
        chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
914
 
                                  ('equal',  1,5, 0,4)
 
1020
        chk_ops('abc', '', [('delete', 0, 3, 0, 0)])
 
1021
        chk_ops('', 'abc', [('insert', 0, 0, 0, 3)])
 
1022
        chk_ops('abcd', 'abcd', [('equal',    0, 4, 0, 4)])
 
1023
        chk_ops('abcd', 'abce', [('equal',   0, 3, 0, 3),
 
1024
                                 ('replace', 3, 4, 3, 4)
 
1025
                                ])
 
1026
        chk_ops('eabc', 'abce', [('delete', 0, 1, 0, 0),
 
1027
                                 ('equal',  1, 4, 0, 3),
 
1028
                                 ('insert', 4, 4, 3, 4)
 
1029
                                ])
 
1030
        chk_ops('eabce', 'abce', [('delete', 0, 1, 0, 0),
 
1031
                                  ('equal',  1, 5, 0, 4)
915
1032
                                 ])
916
 
        chk_ops('abcde', 'abXde', [('equal',   0,2, 0,2),
917
 
                                   ('replace', 2,3, 2,3),
918
 
                                   ('equal',   3,5, 3,5)
 
1033
        chk_ops('abcde', 'abXde', [('equal',   0, 2, 0, 2),
 
1034
                                   ('replace', 2, 3, 2, 3),
 
1035
                                   ('equal',   3, 5, 3, 5)
919
1036
                                  ])
920
 
        chk_ops('abcde', 'abXYZde', [('equal',   0,2, 0,2),
921
 
                                     ('replace', 2,3, 2,5),
922
 
                                     ('equal',   3,5, 5,7)
 
1037
        chk_ops('abcde', 'abXYZde', [('equal',   0, 2, 0, 2),
 
1038
                                     ('replace', 2, 3, 2, 5),
 
1039
                                     ('equal',   3, 5, 5, 7)
923
1040
                                    ])
924
 
        chk_ops('abde', 'abXYZde', [('equal',  0,2, 0,2),
925
 
                                    ('insert', 2,2, 2,5),
926
 
                                    ('equal',  2,4, 5,7)
 
1041
        chk_ops('abde', 'abXYZde', [('equal',  0, 2, 0, 2),
 
1042
                                    ('insert', 2, 2, 2, 5),
 
1043
                                    ('equal',  2, 4, 5, 7)
927
1044
                                   ])
928
1045
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
929
 
                [('equal',  0,6,  0,6),
930
 
                 ('insert', 6,6,  6,11),
931
 
                 ('equal',  6,16, 11,21)
 
1046
                [('equal',  0, 6,  0, 6),
 
1047
                 ('insert', 6, 6,  6, 11),
 
1048
                 ('equal',  6, 16, 11, 21)
932
1049
                ])
933
1050
        chk_ops(
934
1051
                [ 'hello there\n'
936
1053
                , 'how are you today?\n'],
937
1054
                [ 'hello there\n'
938
1055
                , 'how are you today?\n'],
939
 
                [('equal',  0,1, 0,1),
940
 
                 ('delete', 1,2, 1,1),
941
 
                 ('equal',  2,3, 1,2),
 
1056
                [('equal',  0, 1, 0, 1),
 
1057
                 ('delete', 1, 2, 1, 1),
 
1058
                 ('equal',  2, 3, 1, 2),
942
1059
                ])
943
1060
        chk_ops('aBccDe', 'abccde',
944
 
                [('equal',   0,1, 0,1),
945
 
                 ('replace', 1,5, 1,5),
946
 
                 ('equal',   5,6, 5,6),
 
1061
                [('equal',   0, 1, 0, 1),
 
1062
                 ('replace', 1, 5, 1, 5),
 
1063
                 ('equal',   5, 6, 5, 6),
947
1064
                ])
948
1065
        chk_ops('aBcDec', 'abcdec',
949
 
                [('equal',   0,1, 0,1),
950
 
                 ('replace', 1,2, 1,2),
951
 
                 ('equal',   2,3, 2,3),
952
 
                 ('replace', 3,4, 3,4),
953
 
                 ('equal',   4,6, 4,6),
 
1066
                [('equal',   0, 1, 0, 1),
 
1067
                 ('replace', 1, 2, 1, 2),
 
1068
                 ('equal',   2, 3, 2, 3),
 
1069
                 ('replace', 3, 4, 3, 4),
 
1070
                 ('equal',   4, 6, 4, 6),
954
1071
                ])
955
1072
        chk_ops('aBcdEcdFg', 'abcdecdfg',
956
 
                [('equal',   0,1, 0,1),
957
 
                 ('replace', 1,8, 1,8),
958
 
                 ('equal',   8,9, 8,9)
 
1073
                [('equal',   0, 1, 0, 1),
 
1074
                 ('replace', 1, 8, 1, 8),
 
1075
                 ('equal',   8, 9, 8, 9)
959
1076
                ])
960
1077
        chk_ops('aBcdEeXcdFg', 'abcdecdfg',
961
 
                [('equal',   0,1, 0,1),
962
 
                 ('replace', 1,2, 1,2),
963
 
                 ('equal',   2,4, 2,4),
964
 
                 ('delete', 4,5, 4,4),
965
 
                 ('equal',   5,6, 4,5),
966
 
                 ('delete', 6,7, 5,5),
967
 
                 ('equal',   7,9, 5,7),
968
 
                 ('replace', 9,10, 7,8),
969
 
                 ('equal',   10,11, 8,9)
 
1078
                [('equal',   0, 1, 0, 1),
 
1079
                 ('replace', 1, 2, 1, 2),
 
1080
                 ('equal',   2, 4, 2, 4),
 
1081
                 ('delete', 4, 5, 4, 4),
 
1082
                 ('equal',   5, 6, 4, 5),
 
1083
                 ('delete', 6, 7, 5, 5),
 
1084
                 ('equal',   7, 9, 5, 7),
 
1085
                 ('replace', 9, 10, 7, 8),
 
1086
                 ('equal',   10, 11, 8, 9)
970
1087
                ])
971
1088
 
972
1089
    def test_grouped_opcodes(self):
973
1090
        def chk_ops(a, b, expected_codes, n=3):
974
1091
            s = self._PatienceSequenceMatcher(None, a, b)
975
 
            self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
 
1092
            self.assertEqual(expected_codes, list(s.get_grouped_opcodes(n)))
976
1093
 
977
1094
        chk_ops('', '', [])
978
1095
        chk_ops([], [], [])
979
 
        chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
980
 
        chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
 
1096
        chk_ops('abc', '', [[('delete', 0, 3, 0, 0)]])
 
1097
        chk_ops('', 'abc', [[('insert', 0, 0, 0, 3)]])
981
1098
        chk_ops('abcd', 'abcd', [])
982
 
        chk_ops('abcd', 'abce', [[('equal',   0,3, 0,3),
983
 
                                  ('replace', 3,4, 3,4)
 
1099
        chk_ops('abcd', 'abce', [[('equal',   0, 3, 0, 3),
 
1100
                                  ('replace', 3, 4, 3, 4)
984
1101
                                 ]])
985
 
        chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
986
 
                                 ('equal',  1,4, 0,3),
987
 
                                 ('insert', 4,4, 3,4)
 
1102
        chk_ops('eabc', 'abce', [[('delete', 0, 1, 0, 0),
 
1103
                                 ('equal',  1, 4, 0, 3),
 
1104
                                 ('insert', 4, 4, 3, 4)
988
1105
                                ]])
989
1106
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
990
 
                [[('equal',  3,6, 3,6),
991
 
                  ('insert', 6,6, 6,11),
992
 
                  ('equal',  6,9, 11,14)
 
1107
                [[('equal',  3, 6, 3, 6),
 
1108
                  ('insert', 6, 6, 6, 11),
 
1109
                  ('equal',  6, 9, 11, 14)
993
1110
                  ]])
994
1111
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
995
 
                [[('equal',  2,6, 2,6),
996
 
                  ('insert', 6,6, 6,11),
997
 
                  ('equal',  6,10, 11,15)
 
1112
                [[('equal',  2, 6, 2, 6),
 
1113
                  ('insert', 6, 6, 6, 11),
 
1114
                  ('equal',  6, 10, 11, 15)
998
1115
                  ]], 4)
999
1116
        chk_ops('Xabcdef', 'abcdef',
1000
 
                [[('delete', 0,1, 0,0),
1001
 
                  ('equal',  1,4, 0,3)
 
1117
                [[('delete', 0, 1, 0, 0),
 
1118
                  ('equal',  1, 4, 0, 3)
1002
1119
                  ]])
1003
1120
        chk_ops('abcdef', 'abcdefX',
1004
 
                [[('equal',  3,6, 3,6),
1005
 
                  ('insert', 6,6, 6,7)
 
1121
                [[('equal',  3, 6, 3, 6),
 
1122
                  ('insert', 6, 6, 6, 7)
1006
1123
                  ]])
1007
1124
 
1008
1125
 
1015
1132
 
1016
1133
        self.assertDiffBlocks('ABCd efghIjk  L',
1017
1134
                              'AxyzBCn mo pqrstuvwI1 2  L',
1018
 
                              [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
1135
                              [(0, 0, 1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1019
1136
 
1020
1137
        # These are rot13 code snippets.
1021
1138
        self.assertDiffBlocks('''\
1062
1179
 
1063
1180
pynff pzq_zxqve(Pbzznaq):
1064
1181
'''.splitlines(True)
1065
 
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
1182
, [(0, 0, 1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1066
1183
 
1067
1184
    def test_patience_unified_diff(self):
1068
1185
        txt_a = ['hello there\n',
1072
1189
                 'how are you today?\n']
1073
1190
        unified_diff = patiencediff.unified_diff
1074
1191
        psm = self._PatienceSequenceMatcher
1075
 
        self.assertEquals(['--- \n',
 
1192
        self.assertEqual(['--- \n',
1076
1193
                           '+++ \n',
1077
1194
                           '@@ -1,3 +1,2 @@\n',
1078
1195
                           ' hello there\n',
1081
1198
                          ]
1082
1199
                          , list(unified_diff(txt_a, txt_b,
1083
1200
                                 sequencematcher=psm)))
1084
 
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1085
 
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
 
1201
        txt_a = [x+'\n' for x in 'abcdefghijklmnop']
 
1202
        txt_b = [x+'\n' for x in 'abcdefxydefghijklmnop']
1086
1203
        # This is the result with LongestCommonSubstring matching
1087
 
        self.assertEquals(['--- \n',
 
1204
        self.assertEqual(['--- \n',
1088
1205
                           '+++ \n',
1089
1206
                           '@@ -1,6 +1,11 @@\n',
1090
1207
                           ' a\n',
1100
1217
                           ' f\n']
1101
1218
                          , list(unified_diff(txt_a, txt_b)))
1102
1219
        # And the patience diff
1103
 
        self.assertEquals(['--- \n',
 
1220
        self.assertEqual(['--- \n',
1104
1221
                           '+++ \n',
1105
1222
                           '@@ -4,6 +4,11 @@\n',
1106
1223
                           ' d\n',
1126
1243
                 'how are you today?\n']
1127
1244
        unified_diff = patiencediff.unified_diff
1128
1245
        psm = self._PatienceSequenceMatcher
1129
 
        self.assertEquals(['--- a\t2008-08-08\n',
 
1246
        self.assertEqual(['--- a\t2008-08-08\n',
1130
1247
                           '+++ b\t2008-09-09\n',
1131
1248
                           '@@ -1,3 +1,2 @@\n',
1132
1249
                           ' hello there\n',
1142
1259
 
1143
1260
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1144
1261
 
1145
 
    _test_needs_features = [compiled_patiencediff_feature]
 
1262
    _test_needs_features = [features.compiled_patiencediff_feature]
1146
1263
 
1147
1264
    def setUp(self):
1148
1265
        super(TestPatienceDiffLib_c, self).setUp()
1149
 
        from bzrlib import _patiencediff_c
 
1266
        from breezy import _patiencediff_c
1150
1267
        self._unique_lcs = _patiencediff_c.unique_lcs_c
1151
1268
        self._recurse_matches = _patiencediff_c.recurse_matches_c
1152
1269
        self._PatienceSequenceMatcher = \
1179
1296
                 'how are you today?\n']
1180
1297
        txt_b = ['hello there\n',
1181
1298
                 'how are you today?\n']
1182
 
        open('a1', 'wb').writelines(txt_a)
1183
 
        open('b1', 'wb').writelines(txt_b)
 
1299
        with open('a1', 'wb') as f: f.writelines(txt_a)
 
1300
        with open('b1', 'wb') as f: f.writelines(txt_b)
1184
1301
 
1185
1302
        unified_diff_files = patiencediff.unified_diff_files
1186
1303
        psm = self._PatienceSequenceMatcher
1187
 
        self.assertEquals(['--- a1\n',
 
1304
        self.assertEqual(['--- a1\n',
1188
1305
                           '+++ b1\n',
1189
1306
                           '@@ -1,3 +1,2 @@\n',
1190
1307
                           ' hello there\n',
1194
1311
                          , list(unified_diff_files('a1', 'b1',
1195
1312
                                 sequencematcher=psm)))
1196
1313
 
1197
 
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1198
 
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1199
 
        open('a2', 'wb').writelines(txt_a)
1200
 
        open('b2', 'wb').writelines(txt_b)
 
1314
        txt_a = [x+'\n' for x in 'abcdefghijklmnop']
 
1315
        txt_b = [x+'\n' for x in 'abcdefxydefghijklmnop']
 
1316
        with open('a2', 'wb') as f: f.writelines(txt_a)
 
1317
        with open('b2', 'wb') as f: f.writelines(txt_b)
1201
1318
 
1202
1319
        # This is the result with LongestCommonSubstring matching
1203
 
        self.assertEquals(['--- a2\n',
 
1320
        self.assertEqual(['--- a2\n',
1204
1321
                           '+++ b2\n',
1205
1322
                           '@@ -1,6 +1,11 @@\n',
1206
1323
                           ' a\n',
1217
1334
                          , list(unified_diff_files('a2', 'b2')))
1218
1335
 
1219
1336
        # And the patience diff
1220
 
        self.assertEquals(['--- a2\n',
1221
 
                           '+++ b2\n',
1222
 
                           '@@ -4,6 +4,11 @@\n',
1223
 
                           ' d\n',
1224
 
                           ' e\n',
1225
 
                           ' f\n',
1226
 
                           '+x\n',
1227
 
                           '+y\n',
1228
 
                           '+d\n',
1229
 
                           '+e\n',
1230
 
                           '+f\n',
1231
 
                           ' g\n',
1232
 
                           ' h\n',
1233
 
                           ' i\n',
1234
 
                          ]
1235
 
                          , list(unified_diff_files('a2', 'b2',
1236
 
                                 sequencematcher=psm)))
 
1337
        self.assertEqual(['--- a2\n',
 
1338
                          '+++ b2\n',
 
1339
                          '@@ -4,6 +4,11 @@\n',
 
1340
                          ' d\n',
 
1341
                          ' e\n',
 
1342
                          ' f\n',
 
1343
                          '+x\n',
 
1344
                          '+y\n',
 
1345
                          '+d\n',
 
1346
                          '+e\n',
 
1347
                          '+f\n',
 
1348
                          ' g\n',
 
1349
                          ' h\n',
 
1350
                          ' i\n'],
 
1351
                         list(unified_diff_files('a2', 'b2',
 
1352
                                                 sequencematcher=psm)))
1237
1353
 
1238
1354
 
1239
1355
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1240
1356
 
1241
 
    _test_needs_features = [compiled_patiencediff_feature]
 
1357
    _test_needs_features = [features.compiled_patiencediff_feature]
1242
1358
 
1243
1359
    def setUp(self):
1244
1360
        super(TestPatienceDiffLibFiles_c, self).setUp()
1245
 
        from bzrlib import _patiencediff_c
 
1361
        from breezy import _patiencediff_c
1246
1362
        self._PatienceSequenceMatcher = \
1247
1363
            _patiencediff_c.PatienceSequenceMatcher_c
1248
1364
 
1250
1366
class TestUsingCompiledIfAvailable(tests.TestCase):
1251
1367
 
1252
1368
    def test_PatienceSequenceMatcher(self):
1253
 
        if compiled_patiencediff_feature.available():
1254
 
            from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
 
1369
        if features.compiled_patiencediff_feature.available():
 
1370
            from breezy._patiencediff_c import PatienceSequenceMatcher_c
1255
1371
            self.assertIs(PatienceSequenceMatcher_c,
1256
1372
                          patiencediff.PatienceSequenceMatcher)
1257
1373
        else:
1258
 
            from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
 
1374
            from breezy._patiencediff_py import PatienceSequenceMatcher_py
1259
1375
            self.assertIs(PatienceSequenceMatcher_py,
1260
1376
                          patiencediff.PatienceSequenceMatcher)
1261
1377
 
1262
1378
    def test_unique_lcs(self):
1263
 
        if compiled_patiencediff_feature.available():
1264
 
            from bzrlib._patiencediff_c import unique_lcs_c
 
1379
        if features.compiled_patiencediff_feature.available():
 
1380
            from breezy._patiencediff_c import unique_lcs_c
1265
1381
            self.assertIs(unique_lcs_c,
1266
1382
                          patiencediff.unique_lcs)
1267
1383
        else:
1268
 
            from bzrlib._patiencediff_py import unique_lcs_py
 
1384
            from breezy._patiencediff_py import unique_lcs_py
1269
1385
            self.assertIs(unique_lcs_py,
1270
1386
                          patiencediff.unique_lcs)
1271
1387
 
1272
1388
    def test_recurse_matches(self):
1273
 
        if compiled_patiencediff_feature.available():
1274
 
            from bzrlib._patiencediff_c import recurse_matches_c
 
1389
        if features.compiled_patiencediff_feature.available():
 
1390
            from breezy._patiencediff_c import recurse_matches_c
1275
1391
            self.assertIs(recurse_matches_c,
1276
1392
                          patiencediff.recurse_matches)
1277
1393
        else:
1278
 
            from bzrlib._patiencediff_py import recurse_matches_py
 
1394
            from breezy._patiencediff_py import recurse_matches_py
1279
1395
            self.assertIs(recurse_matches_py,
1280
1396
                          patiencediff.recurse_matches)
1281
1397
 
1298
1414
                         diff_obj._get_command('old-path', 'new-path'))
1299
1415
 
1300
1416
    def test_from_string_path_with_backslashes(self):
1301
 
        self.requireFeature(test_win32utils.BackslashDirSeparatorFeature)
 
1417
        self.requireFeature(features.backslashdir_feature)
1302
1418
        tool = 'C:\\Tools\\Diff.exe'
1303
1419
        diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
1304
1420
        self.addCleanup(diff_obj.finish)
1308
1424
                         diff_obj._get_command('old-path', 'new-path'))
1309
1425
 
1310
1426
    def test_execute(self):
1311
 
        output = StringIO()
 
1427
        output = BytesIO()
1312
1428
        diff_obj = diff.DiffFromTool(['python', '-c',
1313
1429
                                      'print "@old_path @new_path"'],
1314
1430
                                     None, None, output)
1316
1432
        diff_obj._execute('old', 'new')
1317
1433
        self.assertEqual(output.getvalue().rstrip(), 'old new')
1318
1434
 
1319
 
    def test_excute_missing(self):
 
1435
    def test_execute_missing(self):
1320
1436
        diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1321
1437
                                     None, None, None)
1322
1438
        self.addCleanup(diff_obj.finish)
1326
1442
                         ' on this machine', str(e))
1327
1443
 
1328
1444
    def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1329
 
        self.requireFeature(AttribFeature)
1330
 
        output = StringIO()
 
1445
        self.requireFeature(features.AttribFeature)
 
1446
        output = BytesIO()
1331
1447
        tree = self.make_branch_and_tree('tree')
1332
1448
        self.build_tree_contents([('tree/file', 'content')])
1333
1449
        tree.add('file', 'file-id')
1340
1456
        diff_obj = diff.DiffFromTool(['python', '-c',
1341
1457
                                      'print "@old_path @new_path"'],
1342
1458
                                     basis_tree, tree, output)
1343
 
        diff_obj._prepare_files('file-id', 'file', 'file')
 
1459
        diff_obj._prepare_files('file', 'file', file_id='file-id')
1344
1460
        # The old content should be readonly
1345
1461
        self.assertReadableByAttrib(diff_obj._root, 'old\\file',
1346
1462
                                    r'R.*old\\file$')
1356
1472
        self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1357
1473
 
1358
1474
    def test_prepare_files(self):
1359
 
        output = StringIO()
 
1475
        output = BytesIO()
1360
1476
        tree = self.make_branch_and_tree('tree')
1361
1477
        self.build_tree_contents([('tree/oldname', 'oldcontent')])
1362
1478
        self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1377
1493
                                      'print "@old_path @new_path"'],
1378
1494
                                     old_tree, tree, output)
1379
1495
        self.addCleanup(diff_obj.finish)
1380
 
        self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1381
 
        old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
1382
 
                                                     'newname')
 
1496
        self.assertContainsRe(diff_obj._root, 'brz-diff-[^/]*')
 
1497
        old_path, new_path = diff_obj._prepare_files(
 
1498
                'oldname', 'newname', file_id='file-id')
1383
1499
        self.assertContainsRe(old_path, 'old/oldname$')
1384
1500
        self.assertEqual(315532800, os.stat(old_path).st_mtime)
1385
1501
        self.assertContainsRe(new_path, 'tree/newname$')
1388
1504
        if osutils.host_os_dereferences_symlinks():
1389
1505
            self.assertTrue(os.path.samefile('tree/newname', new_path))
1390
1506
        # make sure we can create files with the same parent directories
1391
 
        diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
 
1507
        diff_obj._prepare_files('oldname2', 'newname2', file_id='file2-id')
 
1508
 
 
1509
 
 
1510
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
 
1511
 
 
1512
    def test_encodable_filename(self):
 
1513
        # Just checks file path for external diff tool.
 
1514
        # We cannot change CPython's internal encoding used by os.exec*.
 
1515
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
 
1516
                                    None, None, None)
 
1517
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
1518
            encoding = scenario['encoding']
 
1519
            dirname = scenario['info']['directory']
 
1520
            filename = scenario['info']['filename']
 
1521
 
 
1522
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
1523
            relpath = dirname + u'/' + filename
 
1524
            fullpath = diffobj._safe_filename('safe', relpath)
 
1525
            self.assertEqual(fullpath,
 
1526
                             fullpath.encode(encoding).decode(encoding))
 
1527
            self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
 
1528
 
 
1529
    def test_unencodable_filename(self):
 
1530
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
 
1531
                                    None, None, None)
 
1532
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
1533
            encoding = scenario['encoding']
 
1534
            dirname = scenario['info']['directory']
 
1535
            filename = scenario['info']['filename']
 
1536
 
 
1537
            if encoding == 'iso-8859-1':
 
1538
                encoding = 'iso-8859-2'
 
1539
            else:
 
1540
                encoding = 'iso-8859-1'
 
1541
 
 
1542
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
1543
            relpath = dirname + u'/' + filename
 
1544
            fullpath = diffobj._safe_filename('safe', relpath)
 
1545
            self.assertEqual(fullpath,
 
1546
                             fullpath.encode(encoding).decode(encoding))
 
1547
            self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
1392
1548
 
1393
1549
 
1394
1550
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1395
1551
 
1396
1552
    def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1397
 
        """Call get_trees_and_branches_to_diff_locked.  Overridden by
1398
 
        TestGetTreesAndBranchesToDiff.
1399
 
        """
 
1553
        """Call get_trees_and_branches_to_diff_locked."""
1400
1554
        return diff.get_trees_and_branches_to_diff_locked(
1401
1555
            path_list, revision_specs, old_url, new_url, self.addCleanup)
1402
1556
 
1439
1593
        self.assertEqual(tree.branch.base, new_branch.base)
1440
1594
        self.assertIs(None, specific_files)
1441
1595
        self.assertEqual(tree.basedir, extra_trees[0].basedir)
1442
 
 
1443
 
 
1444
 
class TestGetTreesAndBranchesToDiff(TestGetTreesAndBranchesToDiffLocked):
1445
 
    """Apply the tests for get_trees_and_branches_to_diff_locked to the
1446
 
    deprecated get_trees_and_branches_to_diff function.
1447
 
    """
1448
 
 
1449
 
    def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1450
 
        return self.applyDeprecated(
1451
 
            deprecated_in((2, 2, 0)), diff.get_trees_and_branches_to_diff,
1452
 
            path_list, revision_specs, old_url, new_url)
1453