/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-12 20:44:54 UTC
  • mto: This revision was merged to the branch mainline in revision 6819.
  • Revision ID: jelmer@jelmer.uk-20171112204454-m3lxl0l09zduidxw
Fix some test failures.

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())
 
729
        differ = diff.DiffText(self.old_tree, self.new_tree, BytesIO())
616
730
        differ.diff_text('file-id', None, 'old label', 'new label')
617
731
        self.assertEqual(
618
732
            '--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
644
758
        self.new_tree.add('file', 'file-id')
645
759
        os.unlink('old-tree/file')
646
760
        self.differ.show_diff(None)
647
 
        self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
 
761
        self.assertContainsRe(self.differ.to_file.getvalue(), r'\+contents')
648
762
 
649
763
    def test_diff_symlink(self):
650
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
764
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
651
765
        differ.diff_symlink('old target', None)
652
766
        self.assertEqual("=== target was 'old target'\n",
653
767
                         differ.to_file.getvalue())
654
768
 
655
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
769
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
656
770
        differ.diff_symlink(None, 'new target')
657
771
        self.assertEqual("=== target is 'new target'\n",
658
772
                         differ.to_file.getvalue())
659
773
 
660
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
774
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
661
775
        differ.diff_symlink('old target', 'new target')
662
776
        self.assertEqual("=== target changed 'old target' => 'new target'\n",
663
777
                         differ.to_file.getvalue())
675
789
        self.assertContainsRe(
676
790
            self.differ.to_file.getvalue(),
677
791
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
678
 
             ' \@\@\n-old\n\+new\n\n')
 
792
            r' \@\@\n-old\n\+new\n\n')
679
793
 
680
794
    def test_diff_kind_change(self):
681
 
        self.requireFeature(tests.SymlinkFeature)
 
795
        self.requireFeature(features.SymlinkFeature)
682
796
        self.build_tree_contents([('old-tree/olddir/',),
683
797
                                  ('old-tree/olddir/oldfile', 'old\n')])
684
798
        self.old_tree.add('olddir')
691
805
        self.assertContainsRe(
692
806
            self.differ.to_file.getvalue(),
693
807
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
694
 
             ' \@\@\n-old\n\n')
 
808
            r' \@\@\n-old\n\n')
695
809
        self.assertContainsRe(self.differ.to_file.getvalue(),
696
810
                              "=== target is u'new'\n")
697
811
 
717
831
        diff.DiffTree.diff_factories=old_diff_factories[:]
718
832
        diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
719
833
        try:
720
 
            differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
 
834
            differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
721
835
        finally:
722
836
            diff.DiffTree.diff_factories = old_diff_factories
723
837
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
724
838
        self.assertNotContainsRe(
725
839
            differ.to_file.getvalue(),
726
840
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
727
 
             ' \@\@\n-old\n\+new\n\n')
 
841
            r' \@\@\n-old\n\+new\n\n')
728
842
        self.assertContainsRe(differ.to_file.getvalue(),
729
843
                              'was: old\nis: new\n')
730
844
 
731
845
    def test_extra_factories(self):
732
846
        self.create_old_new()
733
 
        differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
 
847
        differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO(),
734
848
                               extra_factories=[DiffWasIs.from_diff_tree])
735
849
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
736
850
        self.assertNotContainsRe(
737
851
            differ.to_file.getvalue(),
738
852
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
739
 
             ' \@\@\n-old\n\+new\n\n')
 
853
            r' \@\@\n-old\n\+new\n\n')
740
854
        self.assertContainsRe(differ.to_file.getvalue(),
741
855
                              'was: old\nis: new\n')
742
856
 
764
878
        b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
765
879
        sm = self._PatienceSequenceMatcher(None, a, b)
766
880
        mb = sm.get_matching_blocks()
767
 
        self.assertEquals(35, len(mb))
 
881
        self.assertEqual(35, len(mb))
768
882
 
769
883
    def test_unique_lcs(self):
770
884
        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)])
 
885
        self.assertEqual(unique_lcs('', ''), [])
 
886
        self.assertEqual(unique_lcs('', 'a'), [])
 
887
        self.assertEqual(unique_lcs('a', ''), [])
 
888
        self.assertEqual(unique_lcs('a', 'a'), [(0, 0)])
 
889
        self.assertEqual(unique_lcs('a', 'b'), [])
 
890
        self.assertEqual(unique_lcs('ab', 'ab'), [(0, 0), (1, 1)])
 
891
        self.assertEqual(unique_lcs('abcde', 'cdeab'), [(2, 0), (3, 1), (4, 2)])
 
892
        self.assertEqual(unique_lcs('cdeab', 'abcde'), [(0, 2), (1, 3), (2, 4)])
 
893
        self.assertEqual(unique_lcs('abXde', 'abYde'), [(0, 0), (1, 1),
 
894
                                                         (3, 3), (4, 4)])
 
895
        self.assertEqual(unique_lcs('acbac', 'abc'), [(2, 1)])
782
896
 
783
897
    def test_recurse_matches(self):
784
898
        def test_one(a, b, matches):
785
899
            test_matches = []
786
900
            self._recurse_matches(
787
901
                a, b, 0, 0, len(a), len(b), test_matches, 10)
788
 
            self.assertEquals(test_matches, matches)
 
902
            self.assertEqual(test_matches, matches)
789
903
 
790
904
        test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
791
905
                 [(0, 0), (2, 2), (4, 4)])
794
908
        # Even though 'bc' is not unique globally, and is surrounded by
795
909
        # non-matching lines, we should still match, because they are locally
796
910
        # unique
797
 
        test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
 
911
        test_one('abcdbce', 'afbcgdbce', [(0, 0), (1, 2), (2, 3), (3, 5),
798
912
                                          (4, 6), (5, 7), (6, 8)])
799
913
 
800
914
        # recurse_matches doesn't match non-unique
805
919
        #test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
806
920
 
807
921
        # This is what it currently gives:
808
 
        test_one('aBccDe', 'abccde', [(0,0), (5,5)])
 
922
        test_one('aBccDe', 'abccde', [(0, 0), (5, 5)])
809
923
 
810
924
    def assertDiffBlocks(self, a, b, expected_blocks):
811
925
        """Check that the sequence matcher returns the correct blocks.
853
967
 
854
968
        # non unique lines surrounded by non-matching lines
855
969
        # won't be found
856
 
        self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
 
970
        self.assertDiffBlocks('aBccDe', 'abccde', [(0, 0, 1), (5, 5, 1)])
857
971
 
858
972
        # But they only need to be locally unique
859
 
        self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
 
973
        self.assertDiffBlocks('aBcDec', 'abcdec', [(0, 0, 1), (2, 2, 1), (4, 4, 2)])
860
974
 
861
975
        # non unique blocks won't be matched
862
 
        self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
 
976
        self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0, 0, 1), (8, 8, 1)])
863
977
 
864
978
        # 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)])
 
979
        self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0, 0, 1), (2, 2, 2),
 
980
                                              (5, 4, 1), (7, 5, 2), (10, 8, 1)])
867
981
 
868
 
        self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
 
982
        self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7, 7, 1)])
869
983
        self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
870
984
        self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
871
985
 
896
1010
    def test_opcodes(self):
897
1011
        def chk_ops(a, b, expected_codes):
898
1012
            s = self._PatienceSequenceMatcher(None, a, b)
899
 
            self.assertEquals(expected_codes, s.get_opcodes())
 
1013
            self.assertEqual(expected_codes, s.get_opcodes())
900
1014
 
901
1015
        chk_ops('', '', [])
902
1016
        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)
 
1017
        chk_ops('abc', '', [('delete', 0, 3, 0, 0)])
 
1018
        chk_ops('', 'abc', [('insert', 0, 0, 0, 3)])
 
1019
        chk_ops('abcd', 'abcd', [('equal',    0, 4, 0, 4)])
 
1020
        chk_ops('abcd', 'abce', [('equal',   0, 3, 0, 3),
 
1021
                                 ('replace', 3, 4, 3, 4)
 
1022
                                ])
 
1023
        chk_ops('eabc', 'abce', [('delete', 0, 1, 0, 0),
 
1024
                                 ('equal',  1, 4, 0, 3),
 
1025
                                 ('insert', 4, 4, 3, 4)
 
1026
                                ])
 
1027
        chk_ops('eabce', 'abce', [('delete', 0, 1, 0, 0),
 
1028
                                  ('equal',  1, 5, 0, 4)
915
1029
                                 ])
916
 
        chk_ops('abcde', 'abXde', [('equal',   0,2, 0,2),
917
 
                                   ('replace', 2,3, 2,3),
918
 
                                   ('equal',   3,5, 3,5)
 
1030
        chk_ops('abcde', 'abXde', [('equal',   0, 2, 0, 2),
 
1031
                                   ('replace', 2, 3, 2, 3),
 
1032
                                   ('equal',   3, 5, 3, 5)
919
1033
                                  ])
920
 
        chk_ops('abcde', 'abXYZde', [('equal',   0,2, 0,2),
921
 
                                     ('replace', 2,3, 2,5),
922
 
                                     ('equal',   3,5, 5,7)
 
1034
        chk_ops('abcde', 'abXYZde', [('equal',   0, 2, 0, 2),
 
1035
                                     ('replace', 2, 3, 2, 5),
 
1036
                                     ('equal',   3, 5, 5, 7)
923
1037
                                    ])
924
 
        chk_ops('abde', 'abXYZde', [('equal',  0,2, 0,2),
925
 
                                    ('insert', 2,2, 2,5),
926
 
                                    ('equal',  2,4, 5,7)
 
1038
        chk_ops('abde', 'abXYZde', [('equal',  0, 2, 0, 2),
 
1039
                                    ('insert', 2, 2, 2, 5),
 
1040
                                    ('equal',  2, 4, 5, 7)
927
1041
                                   ])
928
1042
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
929
 
                [('equal',  0,6,  0,6),
930
 
                 ('insert', 6,6,  6,11),
931
 
                 ('equal',  6,16, 11,21)
 
1043
                [('equal',  0, 6,  0, 6),
 
1044
                 ('insert', 6, 6,  6, 11),
 
1045
                 ('equal',  6, 16, 11, 21)
932
1046
                ])
933
1047
        chk_ops(
934
1048
                [ 'hello there\n'
936
1050
                , 'how are you today?\n'],
937
1051
                [ 'hello there\n'
938
1052
                , 'how are you today?\n'],
939
 
                [('equal',  0,1, 0,1),
940
 
                 ('delete', 1,2, 1,1),
941
 
                 ('equal',  2,3, 1,2),
 
1053
                [('equal',  0, 1, 0, 1),
 
1054
                 ('delete', 1, 2, 1, 1),
 
1055
                 ('equal',  2, 3, 1, 2),
942
1056
                ])
943
1057
        chk_ops('aBccDe', 'abccde',
944
 
                [('equal',   0,1, 0,1),
945
 
                 ('replace', 1,5, 1,5),
946
 
                 ('equal',   5,6, 5,6),
 
1058
                [('equal',   0, 1, 0, 1),
 
1059
                 ('replace', 1, 5, 1, 5),
 
1060
                 ('equal',   5, 6, 5, 6),
947
1061
                ])
948
1062
        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),
 
1063
                [('equal',   0, 1, 0, 1),
 
1064
                 ('replace', 1, 2, 1, 2),
 
1065
                 ('equal',   2, 3, 2, 3),
 
1066
                 ('replace', 3, 4, 3, 4),
 
1067
                 ('equal',   4, 6, 4, 6),
954
1068
                ])
955
1069
        chk_ops('aBcdEcdFg', 'abcdecdfg',
956
 
                [('equal',   0,1, 0,1),
957
 
                 ('replace', 1,8, 1,8),
958
 
                 ('equal',   8,9, 8,9)
 
1070
                [('equal',   0, 1, 0, 1),
 
1071
                 ('replace', 1, 8, 1, 8),
 
1072
                 ('equal',   8, 9, 8, 9)
959
1073
                ])
960
1074
        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)
 
1075
                [('equal',   0, 1, 0, 1),
 
1076
                 ('replace', 1, 2, 1, 2),
 
1077
                 ('equal',   2, 4, 2, 4),
 
1078
                 ('delete', 4, 5, 4, 4),
 
1079
                 ('equal',   5, 6, 4, 5),
 
1080
                 ('delete', 6, 7, 5, 5),
 
1081
                 ('equal',   7, 9, 5, 7),
 
1082
                 ('replace', 9, 10, 7, 8),
 
1083
                 ('equal',   10, 11, 8, 9)
970
1084
                ])
971
1085
 
972
1086
    def test_grouped_opcodes(self):
973
1087
        def chk_ops(a, b, expected_codes, n=3):
974
1088
            s = self._PatienceSequenceMatcher(None, a, b)
975
 
            self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
 
1089
            self.assertEqual(expected_codes, list(s.get_grouped_opcodes(n)))
976
1090
 
977
1091
        chk_ops('', '', [])
978
1092
        chk_ops([], [], [])
979
 
        chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
980
 
        chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
 
1093
        chk_ops('abc', '', [[('delete', 0, 3, 0, 0)]])
 
1094
        chk_ops('', 'abc', [[('insert', 0, 0, 0, 3)]])
981
1095
        chk_ops('abcd', 'abcd', [])
982
 
        chk_ops('abcd', 'abce', [[('equal',   0,3, 0,3),
983
 
                                  ('replace', 3,4, 3,4)
 
1096
        chk_ops('abcd', 'abce', [[('equal',   0, 3, 0, 3),
 
1097
                                  ('replace', 3, 4, 3, 4)
984
1098
                                 ]])
985
 
        chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
986
 
                                 ('equal',  1,4, 0,3),
987
 
                                 ('insert', 4,4, 3,4)
 
1099
        chk_ops('eabc', 'abce', [[('delete', 0, 1, 0, 0),
 
1100
                                 ('equal',  1, 4, 0, 3),
 
1101
                                 ('insert', 4, 4, 3, 4)
988
1102
                                ]])
989
1103
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
990
 
                [[('equal',  3,6, 3,6),
991
 
                  ('insert', 6,6, 6,11),
992
 
                  ('equal',  6,9, 11,14)
 
1104
                [[('equal',  3, 6, 3, 6),
 
1105
                  ('insert', 6, 6, 6, 11),
 
1106
                  ('equal',  6, 9, 11, 14)
993
1107
                  ]])
994
1108
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
995
 
                [[('equal',  2,6, 2,6),
996
 
                  ('insert', 6,6, 6,11),
997
 
                  ('equal',  6,10, 11,15)
 
1109
                [[('equal',  2, 6, 2, 6),
 
1110
                  ('insert', 6, 6, 6, 11),
 
1111
                  ('equal',  6, 10, 11, 15)
998
1112
                  ]], 4)
999
1113
        chk_ops('Xabcdef', 'abcdef',
1000
 
                [[('delete', 0,1, 0,0),
1001
 
                  ('equal',  1,4, 0,3)
 
1114
                [[('delete', 0, 1, 0, 0),
 
1115
                  ('equal',  1, 4, 0, 3)
1002
1116
                  ]])
1003
1117
        chk_ops('abcdef', 'abcdefX',
1004
 
                [[('equal',  3,6, 3,6),
1005
 
                  ('insert', 6,6, 6,7)
 
1118
                [[('equal',  3, 6, 3, 6),
 
1119
                  ('insert', 6, 6, 6, 7)
1006
1120
                  ]])
1007
1121
 
1008
1122
 
1015
1129
 
1016
1130
        self.assertDiffBlocks('ABCd efghIjk  L',
1017
1131
                              'AxyzBCn mo pqrstuvwI1 2  L',
1018
 
                              [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
1132
                              [(0, 0, 1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1019
1133
 
1020
1134
        # These are rot13 code snippets.
1021
1135
        self.assertDiffBlocks('''\
1062
1176
 
1063
1177
pynff pzq_zxqve(Pbzznaq):
1064
1178
'''.splitlines(True)
1065
 
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
1179
, [(0, 0, 1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1066
1180
 
1067
1181
    def test_patience_unified_diff(self):
1068
1182
        txt_a = ['hello there\n',
1072
1186
                 'how are you today?\n']
1073
1187
        unified_diff = patiencediff.unified_diff
1074
1188
        psm = self._PatienceSequenceMatcher
1075
 
        self.assertEquals(['--- \n',
 
1189
        self.assertEqual(['--- \n',
1076
1190
                           '+++ \n',
1077
1191
                           '@@ -1,3 +1,2 @@\n',
1078
1192
                           ' hello there\n',
1081
1195
                          ]
1082
1196
                          , list(unified_diff(txt_a, txt_b,
1083
1197
                                 sequencematcher=psm)))
1084
 
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1085
 
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
 
1198
        txt_a = [x+'\n' for x in 'abcdefghijklmnop']
 
1199
        txt_b = [x+'\n' for x in 'abcdefxydefghijklmnop']
1086
1200
        # This is the result with LongestCommonSubstring matching
1087
 
        self.assertEquals(['--- \n',
 
1201
        self.assertEqual(['--- \n',
1088
1202
                           '+++ \n',
1089
1203
                           '@@ -1,6 +1,11 @@\n',
1090
1204
                           ' a\n',
1100
1214
                           ' f\n']
1101
1215
                          , list(unified_diff(txt_a, txt_b)))
1102
1216
        # And the patience diff
1103
 
        self.assertEquals(['--- \n',
 
1217
        self.assertEqual(['--- \n',
1104
1218
                           '+++ \n',
1105
1219
                           '@@ -4,6 +4,11 @@\n',
1106
1220
                           ' d\n',
1126
1240
                 'how are you today?\n']
1127
1241
        unified_diff = patiencediff.unified_diff
1128
1242
        psm = self._PatienceSequenceMatcher
1129
 
        self.assertEquals(['--- a\t2008-08-08\n',
 
1243
        self.assertEqual(['--- a\t2008-08-08\n',
1130
1244
                           '+++ b\t2008-09-09\n',
1131
1245
                           '@@ -1,3 +1,2 @@\n',
1132
1246
                           ' hello there\n',
1142
1256
 
1143
1257
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1144
1258
 
1145
 
    _test_needs_features = [compiled_patiencediff_feature]
 
1259
    _test_needs_features = [features.compiled_patiencediff_feature]
1146
1260
 
1147
1261
    def setUp(self):
1148
1262
        super(TestPatienceDiffLib_c, self).setUp()
1149
 
        from bzrlib import _patiencediff_c
 
1263
        from breezy import _patiencediff_c
1150
1264
        self._unique_lcs = _patiencediff_c.unique_lcs_c
1151
1265
        self._recurse_matches = _patiencediff_c.recurse_matches_c
1152
1266
        self._PatienceSequenceMatcher = \
1179
1293
                 'how are you today?\n']
1180
1294
        txt_b = ['hello there\n',
1181
1295
                 'how are you today?\n']
1182
 
        open('a1', 'wb').writelines(txt_a)
1183
 
        open('b1', 'wb').writelines(txt_b)
 
1296
        with open('a1', 'wb') as f: f.writelines(txt_a)
 
1297
        with open('b1', 'wb') as f: f.writelines(txt_b)
1184
1298
 
1185
1299
        unified_diff_files = patiencediff.unified_diff_files
1186
1300
        psm = self._PatienceSequenceMatcher
1187
 
        self.assertEquals(['--- a1\n',
 
1301
        self.assertEqual(['--- a1\n',
1188
1302
                           '+++ b1\n',
1189
1303
                           '@@ -1,3 +1,2 @@\n',
1190
1304
                           ' hello there\n',
1194
1308
                          , list(unified_diff_files('a1', 'b1',
1195
1309
                                 sequencematcher=psm)))
1196
1310
 
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)
 
1311
        txt_a = [x+'\n' for x in 'abcdefghijklmnop']
 
1312
        txt_b = [x+'\n' for x in 'abcdefxydefghijklmnop']
 
1313
        with open('a2', 'wb') as f: f.writelines(txt_a)
 
1314
        with open('b2', 'wb') as f: f.writelines(txt_b)
1201
1315
 
1202
1316
        # This is the result with LongestCommonSubstring matching
1203
 
        self.assertEquals(['--- a2\n',
 
1317
        self.assertEqual(['--- a2\n',
1204
1318
                           '+++ b2\n',
1205
1319
                           '@@ -1,6 +1,11 @@\n',
1206
1320
                           ' a\n',
1217
1331
                          , list(unified_diff_files('a2', 'b2')))
1218
1332
 
1219
1333
        # 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)))
 
1334
        self.assertEqual(['--- a2\n',
 
1335
                          '+++ b2\n',
 
1336
                          '@@ -4,6 +4,11 @@\n',
 
1337
                          ' d\n',
 
1338
                          ' e\n',
 
1339
                          ' f\n',
 
1340
                          '+x\n',
 
1341
                          '+y\n',
 
1342
                          '+d\n',
 
1343
                          '+e\n',
 
1344
                          '+f\n',
 
1345
                          ' g\n',
 
1346
                          ' h\n',
 
1347
                          ' i\n'],
 
1348
                         list(unified_diff_files('a2', 'b2',
 
1349
                                                 sequencematcher=psm)))
1237
1350
 
1238
1351
 
1239
1352
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1240
1353
 
1241
 
    _test_needs_features = [compiled_patiencediff_feature]
 
1354
    _test_needs_features = [features.compiled_patiencediff_feature]
1242
1355
 
1243
1356
    def setUp(self):
1244
1357
        super(TestPatienceDiffLibFiles_c, self).setUp()
1245
 
        from bzrlib import _patiencediff_c
 
1358
        from breezy import _patiencediff_c
1246
1359
        self._PatienceSequenceMatcher = \
1247
1360
            _patiencediff_c.PatienceSequenceMatcher_c
1248
1361
 
1250
1363
class TestUsingCompiledIfAvailable(tests.TestCase):
1251
1364
 
1252
1365
    def test_PatienceSequenceMatcher(self):
1253
 
        if compiled_patiencediff_feature.available():
1254
 
            from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
 
1366
        if features.compiled_patiencediff_feature.available():
 
1367
            from breezy._patiencediff_c import PatienceSequenceMatcher_c
1255
1368
            self.assertIs(PatienceSequenceMatcher_c,
1256
1369
                          patiencediff.PatienceSequenceMatcher)
1257
1370
        else:
1258
 
            from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
 
1371
            from breezy._patiencediff_py import PatienceSequenceMatcher_py
1259
1372
            self.assertIs(PatienceSequenceMatcher_py,
1260
1373
                          patiencediff.PatienceSequenceMatcher)
1261
1374
 
1262
1375
    def test_unique_lcs(self):
1263
 
        if compiled_patiencediff_feature.available():
1264
 
            from bzrlib._patiencediff_c import unique_lcs_c
 
1376
        if features.compiled_patiencediff_feature.available():
 
1377
            from breezy._patiencediff_c import unique_lcs_c
1265
1378
            self.assertIs(unique_lcs_c,
1266
1379
                          patiencediff.unique_lcs)
1267
1380
        else:
1268
 
            from bzrlib._patiencediff_py import unique_lcs_py
 
1381
            from breezy._patiencediff_py import unique_lcs_py
1269
1382
            self.assertIs(unique_lcs_py,
1270
1383
                          patiencediff.unique_lcs)
1271
1384
 
1272
1385
    def test_recurse_matches(self):
1273
 
        if compiled_patiencediff_feature.available():
1274
 
            from bzrlib._patiencediff_c import recurse_matches_c
 
1386
        if features.compiled_patiencediff_feature.available():
 
1387
            from breezy._patiencediff_c import recurse_matches_c
1275
1388
            self.assertIs(recurse_matches_c,
1276
1389
                          patiencediff.recurse_matches)
1277
1390
        else:
1278
 
            from bzrlib._patiencediff_py import recurse_matches_py
 
1391
            from breezy._patiencediff_py import recurse_matches_py
1279
1392
            self.assertIs(recurse_matches_py,
1280
1393
                          patiencediff.recurse_matches)
1281
1394
 
1298
1411
                         diff_obj._get_command('old-path', 'new-path'))
1299
1412
 
1300
1413
    def test_from_string_path_with_backslashes(self):
1301
 
        self.requireFeature(test_win32utils.BackslashDirSeparatorFeature)
 
1414
        self.requireFeature(features.backslashdir_feature)
1302
1415
        tool = 'C:\\Tools\\Diff.exe'
1303
1416
        diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
1304
1417
        self.addCleanup(diff_obj.finish)
1308
1421
                         diff_obj._get_command('old-path', 'new-path'))
1309
1422
 
1310
1423
    def test_execute(self):
1311
 
        output = StringIO()
 
1424
        output = BytesIO()
1312
1425
        diff_obj = diff.DiffFromTool(['python', '-c',
1313
1426
                                      'print "@old_path @new_path"'],
1314
1427
                                     None, None, output)
1316
1429
        diff_obj._execute('old', 'new')
1317
1430
        self.assertEqual(output.getvalue().rstrip(), 'old new')
1318
1431
 
1319
 
    def test_excute_missing(self):
 
1432
    def test_execute_missing(self):
1320
1433
        diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1321
1434
                                     None, None, None)
1322
1435
        self.addCleanup(diff_obj.finish)
1326
1439
                         ' on this machine', str(e))
1327
1440
 
1328
1441
    def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1329
 
        self.requireFeature(AttribFeature)
1330
 
        output = StringIO()
 
1442
        self.requireFeature(features.AttribFeature)
 
1443
        output = BytesIO()
1331
1444
        tree = self.make_branch_and_tree('tree')
1332
1445
        self.build_tree_contents([('tree/file', 'content')])
1333
1446
        tree.add('file', 'file-id')
1356
1469
        self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1357
1470
 
1358
1471
    def test_prepare_files(self):
1359
 
        output = StringIO()
 
1472
        output = BytesIO()
1360
1473
        tree = self.make_branch_and_tree('tree')
1361
1474
        self.build_tree_contents([('tree/oldname', 'oldcontent')])
1362
1475
        self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1377
1490
                                      'print "@old_path @new_path"'],
1378
1491
                                     old_tree, tree, output)
1379
1492
        self.addCleanup(diff_obj.finish)
1380
 
        self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
 
1493
        self.assertContainsRe(diff_obj._root, 'brz-diff-[^/]*')
1381
1494
        old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
1382
1495
                                                     'newname')
1383
1496
        self.assertContainsRe(old_path, 'old/oldname$')
1391
1504
        diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
1392
1505
 
1393
1506
 
 
1507
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
 
1508
 
 
1509
    def test_encodable_filename(self):
 
1510
        # Just checks file path for external diff tool.
 
1511
        # We cannot change CPython's internal encoding used by os.exec*.
 
1512
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
 
1513
                                    None, None, None)
 
1514
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
1515
            encoding = scenario['encoding']
 
1516
            dirname = scenario['info']['directory']
 
1517
            filename = scenario['info']['filename']
 
1518
 
 
1519
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
1520
            relpath = dirname + u'/' + filename
 
1521
            fullpath = diffobj._safe_filename('safe', relpath)
 
1522
            self.assertEqual(fullpath,
 
1523
                             fullpath.encode(encoding).decode(encoding))
 
1524
            self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
 
1525
 
 
1526
    def test_unencodable_filename(self):
 
1527
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
 
1528
                                    None, None, None)
 
1529
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
1530
            encoding = scenario['encoding']
 
1531
            dirname = scenario['info']['directory']
 
1532
            filename = scenario['info']['filename']
 
1533
 
 
1534
            if encoding == 'iso-8859-1':
 
1535
                encoding = 'iso-8859-2'
 
1536
            else:
 
1537
                encoding = 'iso-8859-1'
 
1538
 
 
1539
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
1540
            relpath = dirname + u'/' + filename
 
1541
            fullpath = diffobj._safe_filename('safe', relpath)
 
1542
            self.assertEqual(fullpath,
 
1543
                             fullpath.encode(encoding).decode(encoding))
 
1544
            self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
 
1545
 
 
1546
 
1394
1547
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1395
1548
 
1396
1549
    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
 
        """
 
1550
        """Call get_trees_and_branches_to_diff_locked."""
1400
1551
        return diff.get_trees_and_branches_to_diff_locked(
1401
1552
            path_list, revision_specs, old_url, new_url, self.addCleanup)
1402
1553
 
1439
1590
        self.assertEqual(tree.branch.base, new_branch.base)
1440
1591
        self.assertIs(None, specific_files)
1441
1592
        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