/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: 2018-07-06 23:36:14 UTC
  • mto: (7027.3.2 python3-n)
  • mto: This revision was merged to the branch mainline in revision 7030.
  • Revision ID: jelmer@jelmer.uk-20180706233614-t4m8krag15t4z4lp
Update python3.passing.

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()
206
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
207
 
                           u'new_\xe5', ['new_text\n'], output,
 
222
        output = BytesIO()
 
223
        diff.internal_diff(u'old_\xb5', [b'old_text\n'],
 
224
                           u'new_\xe5', [b'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',
212
 
                           '+++ new_\xe5\n',
213
 
                           '@@ -1,1 +1,1 @@\n',
214
 
                           '-old_text\n',
215
 
                           '+new_text\n',
216
 
                           '\n',
 
228
        self.assertEqual([b'--- old_\xb5\n',
 
229
                          b'+++ new_\xe5\n',
 
230
                          b'@@ -1,1 +1,1 @@\n',
 
231
                          b'-old_text\n',
 
232
                          b'+new_text\n',
 
233
                          b'\n',
217
234
                          ]
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
 
        self.assertEqual('', output.getvalue())
 
240
        self.assertEqual(b'', output.getvalue())
224
241
 
225
242
    def test_internal_diff_no_changes(self):
226
 
        output = StringIO()
227
 
        diff.internal_diff(u'old', ['text\n', 'contents\n'],
228
 
                           u'new', ['text\n', 'contents\n'],
 
243
        output = BytesIO()
 
244
        diff.internal_diff(u'old', [b'text\n', b'contents\n'],
 
245
                           u'new', [b'text\n', b'contents\n'],
229
246
                           output)
230
 
        self.assertEqual('', output.getvalue())
 
247
        self.assertEqual(b'', output.getvalue())
231
248
 
232
249
    def test_internal_diff_returns_bytes(self):
233
 
        import StringIO
234
 
        output = StringIO.StringIO()
235
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
236
 
                            u'new_\xe5', ['new_text\n'], output)
237
 
        self.failUnless(isinstance(output.getvalue(), str),
238
 
            'internal_diff should return bytestrings')
 
250
        output = StubO()
 
251
        diff.internal_diff(u'old_\xb5', [b'old_text\n'],
 
252
                            u'new_\xe5', [b'new_text\n'], output)
 
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
 
239
315
 
240
316
 
241
317
class TestDiffFiles(tests.TestCaseInTempDir):
242
318
 
243
319
    def test_external_diff_binary(self):
244
320
        """The output when using external diff should use diff's i18n error"""
 
321
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
 
322
            self.overrideEnv(lang, 'C')
245
323
        # Make sure external_diff doesn't fail in the current LANG
246
324
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
247
325
 
248
326
        cmd = ['diff', '-u', '--binary', 'old', 'new']
249
 
        open('old', 'wb').write('\x00foobar\n')
250
 
        open('new', 'wb').write('foo\x00bar\n')
 
327
        with open('old', 'wb') as f: f.write(b'\x00foobar\n')
 
328
        with open('new', 'wb') as f: f.write(b'foo\x00bar\n')
251
329
        pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
252
330
                                     stdin=subprocess.PIPE)
253
331
        out, err = pipe.communicate()
254
 
        # Diff returns '2' on Binary files.
255
 
        self.assertEqual(2, pipe.returncode)
256
332
        # We should output whatever diff tells us, plus a trailing newline
257
333
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
258
334
 
259
335
 
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):
 
336
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
 
337
    output = BytesIO()
 
338
    if working_tree is not None:
 
339
        extra_trees = (working_tree,)
 
340
    else:
 
341
        extra_trees = ()
 
342
    diff.show_diff_trees(tree1, tree2, output,
 
343
        specific_files=specific_files,
 
344
        extra_trees=extra_trees, old_label='old/',
 
345
        new_label='new/')
 
346
    return output.getvalue()
 
347
 
 
348
 
 
349
class TestDiffDates(tests.TestCaseWithTransport):
277
350
 
278
351
    def setUp(self):
279
352
        super(TestDiffDates, self).setUp()
280
353
        self.wt = self.make_branch_and_tree('.')
281
354
        self.b = self.wt.branch
282
355
        self.build_tree_contents([
283
 
            ('file1', 'file1 contents at rev 1\n'),
284
 
            ('file2', 'file2 contents at rev 1\n')
 
356
            ('file1', b'file1 contents at rev 1\n'),
 
357
            ('file2', b'file2 contents at rev 1\n')
285
358
            ])
286
359
        self.wt.add(['file1', 'file2'])
287
360
        self.wt.commit(
288
361
            message='Revision 1',
289
362
            timestamp=1143849600, # 2006-04-01 00:00:00 UTC
290
363
            timezone=0,
291
 
            rev_id='rev-1')
292
 
        self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
 
364
            rev_id=b'rev-1')
 
365
        self.build_tree_contents([('file1', b'file1 contents at rev 2\n')])
293
366
        self.wt.commit(
294
367
            message='Revision 2',
295
368
            timestamp=1143936000, # 2006-04-02 00:00:00 UTC
296
369
            timezone=28800,
297
 
            rev_id='rev-2')
298
 
        self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
 
370
            rev_id=b'rev-2')
 
371
        self.build_tree_contents([('file2', b'file2 contents at rev 3\n')])
299
372
        self.wt.commit(
300
373
            message='Revision 3',
301
374
            timestamp=1144022400, # 2006-04-03 00:00:00 UTC
302
375
            timezone=-3600,
303
 
            rev_id='rev-3')
 
376
            rev_id=b'rev-3')
304
377
        self.wt.remove(['file2'])
305
378
        self.wt.commit(
306
379
            message='Revision 4',
307
380
            timestamp=1144108800, # 2006-04-04 00:00:00 UTC
308
381
            timezone=0,
309
 
            rev_id='rev-4')
 
382
            rev_id=b'rev-4')
310
383
        self.build_tree_contents([
311
 
            ('file1', 'file1 contents in working tree\n')
 
384
            ('file1', b'file1 contents in working tree\n')
312
385
            ])
313
386
        # set the date stamps for files in the working tree to known values
314
387
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
315
388
 
316
389
    def test_diff_rev_tree_working_tree(self):
317
 
        output = self.get_diff(self.wt.basis_tree(), self.wt)
 
390
        output = get_diff_as_string(self.wt.basis_tree(), self.wt)
318
391
        # note that the date for old/file1 is from rev 2 rather than from
319
392
        # the basis revision (rev 4)
320
393
        self.assertEqualDiff(output, '''\
328
401
''')
329
402
 
330
403
    def test_diff_rev_tree_rev_tree(self):
331
 
        tree1 = self.b.repository.revision_tree('rev-2')
332
 
        tree2 = self.b.repository.revision_tree('rev-3')
333
 
        output = self.get_diff(tree1, tree2)
 
404
        tree1 = self.b.repository.revision_tree(b'rev-2')
 
405
        tree2 = self.b.repository.revision_tree(b'rev-3')
 
406
        output = get_diff_as_string(tree1, tree2)
334
407
        self.assertEqualDiff(output, '''\
335
408
=== modified file 'file2'
336
409
--- old/file2\t2006-04-01 00:00:00 +0000
343
416
 
344
417
    def test_diff_add_files(self):
345
418
        tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
346
 
        tree2 = self.b.repository.revision_tree('rev-1')
347
 
        output = self.get_diff(tree1, tree2)
 
419
        tree2 = self.b.repository.revision_tree(b'rev-1')
 
420
        output = get_diff_as_string(tree1, tree2)
348
421
        # the files have the epoch time stamp for the tree in which
349
422
        # they don't exist.
350
423
        self.assertEqualDiff(output, '''\
363
436
''')
364
437
 
365
438
    def test_diff_remove_files(self):
366
 
        tree1 = self.b.repository.revision_tree('rev-3')
367
 
        tree2 = self.b.repository.revision_tree('rev-4')
368
 
        output = self.get_diff(tree1, tree2)
 
439
        tree1 = self.b.repository.revision_tree(b'rev-3')
 
440
        tree2 = self.b.repository.revision_tree(b'rev-4')
 
441
        output = get_diff_as_string(tree1, tree2)
369
442
        # the file has the epoch time stamp for the tree in which
370
443
        # it doesn't exist.
371
444
        self.assertEqualDiff(output, '''\
380
453
    def test_show_diff_specified(self):
381
454
        """A working tree filename can be used to identify a file"""
382
455
        self.wt.rename_one('file1', 'file1b')
383
 
        old_tree = self.b.repository.revision_tree('rev-1')
384
 
        new_tree = self.b.repository.revision_tree('rev-4')
385
 
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'],
 
456
        old_tree = self.b.repository.revision_tree(b'rev-1')
 
457
        new_tree = self.b.repository.revision_tree(b'rev-4')
 
458
        out = get_diff_as_string(old_tree, new_tree, specific_files=['file1b'],
386
459
                            working_tree=self.wt)
387
460
        self.assertContainsRe(out, 'file1\t')
388
461
 
392
465
        os.mkdir('dir2')
393
466
        self.wt.add(['dir1', 'dir2'])
394
467
        self.wt.rename_one('file1', 'dir1/file1')
395
 
        old_tree = self.b.repository.revision_tree('rev-1')
396
 
        new_tree = self.b.repository.revision_tree('rev-4')
397
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'],
398
 
                            working_tree=self.wt)
399
 
        self.assertContainsRe(out, 'file1\t')
400
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'],
401
 
                            working_tree=self.wt)
402
 
        self.assertNotContainsRe(out, 'file1\t')
403
 
 
404
 
 
405
 
 
406
 
class TestShowDiffTrees(TestShowDiffTreesHelper):
 
468
        old_tree = self.b.repository.revision_tree(b'rev-1')
 
469
        new_tree = self.b.repository.revision_tree(b'rev-4')
 
470
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
 
471
                            working_tree=self.wt)
 
472
        self.assertContainsRe(out, b'file1\t')
 
473
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
 
474
                            working_tree=self.wt)
 
475
        self.assertNotContainsRe(out, b'file1\t')
 
476
 
 
477
 
 
478
class TestShowDiffTrees(tests.TestCaseWithTransport):
407
479
    """Direct tests for show_diff_trees"""
408
480
 
409
481
    def test_modified_file(self):
410
482
        """Test when a file is modified."""
411
483
        tree = self.make_branch_and_tree('tree')
412
 
        self.build_tree_contents([('tree/file', 'contents\n')])
413
 
        tree.add(['file'], ['file-id'])
414
 
        tree.commit('one', rev_id='rev-1')
 
484
        self.build_tree_contents([('tree/file', b'contents\n')])
 
485
        tree.add(['file'], [b'file-id'])
 
486
        tree.commit('one', rev_id=b'rev-1')
415
487
 
416
 
        self.build_tree_contents([('tree/file', 'new contents\n')])
417
 
        d = self.get_diff(tree.basis_tree(), tree)
 
488
        self.build_tree_contents([('tree/file', b'new contents\n')])
 
489
        d = get_diff_as_string(tree.basis_tree(), tree)
418
490
        self.assertContainsRe(d, "=== modified file 'file'\n")
419
491
        self.assertContainsRe(d, '--- old/file\t')
420
492
        self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
425
497
        """Test when a file is modified in a renamed directory."""
426
498
        tree = self.make_branch_and_tree('tree')
427
499
        self.build_tree(['tree/dir/'])
428
 
        self.build_tree_contents([('tree/dir/file', 'contents\n')])
429
 
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
430
 
        tree.commit('one', rev_id='rev-1')
 
500
        self.build_tree_contents([('tree/dir/file', b'contents\n')])
 
501
        tree.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
 
502
        tree.commit('one', rev_id=b'rev-1')
431
503
 
432
504
        tree.rename_one('dir', 'other')
433
 
        self.build_tree_contents([('tree/other/file', 'new contents\n')])
434
 
        d = self.get_diff(tree.basis_tree(), tree)
 
505
        self.build_tree_contents([('tree/other/file', b'new contents\n')])
 
506
        d = get_diff_as_string(tree.basis_tree(), tree)
435
507
        self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
436
508
        self.assertContainsRe(d, "=== modified file 'other/file'\n")
437
509
        # XXX: This is technically incorrect, because it used to be at another
445
517
        """Test when only a directory is only renamed."""
446
518
        tree = self.make_branch_and_tree('tree')
447
519
        self.build_tree(['tree/dir/'])
448
 
        self.build_tree_contents([('tree/dir/file', 'contents\n')])
449
 
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
450
 
        tree.commit('one', rev_id='rev-1')
 
520
        self.build_tree_contents([('tree/dir/file', b'contents\n')])
 
521
        tree.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
 
522
        tree.commit('one', rev_id=b'rev-1')
451
523
 
452
524
        tree.rename_one('dir', 'newdir')
453
 
        d = self.get_diff(tree.basis_tree(), tree)
 
525
        d = get_diff_as_string(tree.basis_tree(), tree)
454
526
        # Renaming a directory should be a single "you renamed this dir" even
455
527
        # when there are files inside.
456
528
        self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
458
530
    def test_renamed_file(self):
459
531
        """Test when a file is only renamed."""
460
532
        tree = self.make_branch_and_tree('tree')
461
 
        self.build_tree_contents([('tree/file', 'contents\n')])
462
 
        tree.add(['file'], ['file-id'])
463
 
        tree.commit('one', rev_id='rev-1')
 
533
        self.build_tree_contents([('tree/file', b'contents\n')])
 
534
        tree.add(['file'], [b'file-id'])
 
535
        tree.commit('one', rev_id=b'rev-1')
464
536
 
465
537
        tree.rename_one('file', 'newname')
466
 
        d = self.get_diff(tree.basis_tree(), tree)
 
538
        d = get_diff_as_string(tree.basis_tree(), tree)
467
539
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
468
540
        # We shouldn't have a --- or +++ line, because there is no content
469
541
        # change
472
544
    def test_renamed_and_modified_file(self):
473
545
        """Test when a file is only renamed."""
474
546
        tree = self.make_branch_and_tree('tree')
475
 
        self.build_tree_contents([('tree/file', 'contents\n')])
476
 
        tree.add(['file'], ['file-id'])
477
 
        tree.commit('one', rev_id='rev-1')
 
547
        self.build_tree_contents([('tree/file', b'contents\n')])
 
548
        tree.add(['file'], [b'file-id'])
 
549
        tree.commit('one', rev_id=b'rev-1')
478
550
 
479
551
        tree.rename_one('file', 'newname')
480
 
        self.build_tree_contents([('tree/newname', 'new contents\n')])
481
 
        d = self.get_diff(tree.basis_tree(), tree)
 
552
        self.build_tree_contents([('tree/newname', b'new contents\n')])
 
553
        d = get_diff_as_string(tree.basis_tree(), tree)
482
554
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
483
555
        self.assertContainsRe(d, '--- old/file\t')
484
556
        self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
490
562
        tree = self.make_branch_and_tree('tree')
491
563
 
492
564
        tt = transform.TreeTransform(tree)
493
 
        tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
494
 
        tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
495
 
        tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
496
 
        tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
497
 
        tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
498
 
        tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
 
565
        tt.new_file('a', tt.root, [b'contents\n'], b'a-id', True)
 
566
        tt.new_file('b', tt.root, [b'contents\n'], b'b-id', False)
 
567
        tt.new_file('c', tt.root, [b'contents\n'], b'c-id', True)
 
568
        tt.new_file('d', tt.root, [b'contents\n'], b'd-id', False)
 
569
        tt.new_file('e', tt.root, [b'contents\n'], b'control-e-id', True)
 
570
        tt.new_file('f', tt.root, [b'contents\n'], b'control-f-id', False)
499
571
        tt.apply()
500
 
        tree.commit('one', rev_id='rev-1')
 
572
        tree.commit('one', rev_id=b'rev-1')
501
573
 
502
574
        tt = transform.TreeTransform(tree)
503
 
        tt.set_executability(False, tt.trans_id_file_id('a-id'))
504
 
        tt.set_executability(True, tt.trans_id_file_id('b-id'))
505
 
        tt.set_executability(False, tt.trans_id_file_id('c-id'))
506
 
        tt.set_executability(True, tt.trans_id_file_id('d-id'))
 
575
        tt.set_executability(False, tt.trans_id_file_id(b'a-id'))
 
576
        tt.set_executability(True, tt.trans_id_file_id(b'b-id'))
 
577
        tt.set_executability(False, tt.trans_id_file_id(b'c-id'))
 
578
        tt.set_executability(True, tt.trans_id_file_id(b'd-id'))
507
579
        tt.apply()
508
580
        tree.rename_one('c', 'new-c')
509
581
        tree.rename_one('d', 'new-d')
510
582
 
511
 
        d = self.get_diff(tree.basis_tree(), tree)
 
583
        d = get_diff_as_string(tree.basis_tree(), tree)
512
584
 
513
585
        self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
514
 
                                  ".*\+x to -x.*\)")
 
586
                                 r".*\+x to -x.*\)")
515
587
        self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
516
 
                                  ".*-x to \+x.*\)")
 
588
                                 r".*-x to \+x.*\)")
517
589
        self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
518
 
                                  ".*\+x to -x.*\)")
 
590
                                 r".*\+x to -x.*\)")
519
591
        self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
520
 
                                  ".*-x to \+x.*\)")
 
592
                                 r".*-x to \+x.*\)")
521
593
        self.assertNotContainsRe(d, r"file 'e'")
522
594
        self.assertNotContainsRe(d, r"file 'f'")
523
595
 
524
 
 
525
596
    def test_binary_unicode_filenames(self):
526
597
        """Test that contents of files are *not* encoded in UTF-8 when there
527
598
        is a binary file in the diff.
528
599
        """
529
600
        # See https://bugs.launchpad.net/bugs/110092.
530
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
601
        self.requireFeature(features.UnicodeFilenameFeature)
531
602
 
532
 
        # This bug isn't triggered with cStringIO.
533
 
        from StringIO import StringIO
534
603
        tree = self.make_branch_and_tree('tree')
535
604
        alpha, omega = u'\u03b1', u'\u03c9'
536
605
        alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
538
607
            [('tree/' + alpha, chr(0)),
539
608
             ('tree/' + omega,
540
609
              ('The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
541
 
        tree.add([alpha], ['file-id'])
542
 
        tree.add([omega], ['file-id-2'])
543
 
        diff_content = StringIO()
 
610
        tree.add([alpha], [b'file-id'])
 
611
        tree.add([omega], [b'file-id-2'])
 
612
        diff_content = StubO()
544
613
        diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
545
 
        d = diff_content.getvalue()
 
614
        diff_content.check_types(self, bytes)
 
615
        d = b''.join(diff_content.write_record)
546
616
        self.assertContainsRe(d, r"=== added file '%s'" % alpha_utf8)
547
617
        self.assertContainsRe(d, "Binary files a/%s.*and b/%s.* differ\n"
548
618
                              % (alpha_utf8, alpha_utf8))
552
622
 
553
623
    def test_unicode_filename(self):
554
624
        """Test when the filename are unicode."""
555
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
625
        self.requireFeature(features.UnicodeFilenameFeature)
556
626
 
557
627
        alpha, omega = u'\u03b1', u'\u03c9'
558
628
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
559
629
 
560
630
        tree = self.make_branch_and_tree('tree')
561
 
        self.build_tree_contents([('tree/ren_'+alpha, 'contents\n')])
562
 
        tree.add(['ren_'+alpha], ['file-id-2'])
563
 
        self.build_tree_contents([('tree/del_'+alpha, 'contents\n')])
564
 
        tree.add(['del_'+alpha], ['file-id-3'])
565
 
        self.build_tree_contents([('tree/mod_'+alpha, 'contents\n')])
566
 
        tree.add(['mod_'+alpha], ['file-id-4'])
 
631
        self.build_tree_contents([('tree/ren_'+alpha, b'contents\n')])
 
632
        tree.add(['ren_'+alpha], [b'file-id-2'])
 
633
        self.build_tree_contents([('tree/del_'+alpha, b'contents\n')])
 
634
        tree.add(['del_'+alpha], [b'file-id-3'])
 
635
        self.build_tree_contents([('tree/mod_'+alpha, b'contents\n')])
 
636
        tree.add(['mod_'+alpha], [b'file-id-4'])
567
637
 
568
 
        tree.commit('one', rev_id='rev-1')
 
638
        tree.commit('one', rev_id=b'rev-1')
569
639
 
570
640
        tree.rename_one('ren_'+alpha, 'ren_'+omega)
571
641
        tree.remove('del_'+alpha)
572
 
        self.build_tree_contents([('tree/add_'+alpha, 'contents\n')])
573
 
        tree.add(['add_'+alpha], ['file-id'])
574
 
        self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
 
642
        self.build_tree_contents([('tree/add_'+alpha, b'contents\n')])
 
643
        tree.add(['add_'+alpha], [b'file-id'])
 
644
        self.build_tree_contents([('tree/mod_'+alpha, b'contents_mod\n')])
575
645
 
576
 
        d = self.get_diff(tree.basis_tree(), tree)
 
646
        d = get_diff_as_string(tree.basis_tree(), tree)
577
647
        self.assertContainsRe(d,
578
648
                "=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
579
649
        self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
580
650
        self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
581
651
        self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
582
652
 
 
653
    def test_unicode_filename_path_encoding(self):
 
654
        """Test for bug #382699: unicode filenames on Windows should be shown
 
655
        in user encoding.
 
656
        """
 
657
        self.requireFeature(features.UnicodeFilenameFeature)
 
658
        # The word 'test' in Russian
 
659
        _russian_test = u'\u0422\u0435\u0441\u0442'
 
660
        directory = _russian_test + u'/'
 
661
        test_txt = _russian_test + u'.txt'
 
662
        u1234 = u'\u1234.txt'
 
663
 
 
664
        tree = self.make_branch_and_tree('.')
 
665
        self.build_tree_contents([
 
666
            (test_txt, b'foo\n'),
 
667
            (u1234, b'foo\n'),
 
668
            (directory, None),
 
669
            ])
 
670
        tree.add([test_txt, u1234, directory])
 
671
 
 
672
        sio = BytesIO()
 
673
        diff.show_diff_trees(tree.basis_tree(), tree, sio,
 
674
            path_encoding='cp1251')
 
675
 
 
676
        output = subst_dates(sio.getvalue())
 
677
        shouldbe = ('''\
 
678
=== added directory '%(directory)s'
 
679
=== added file '%(test_txt)s'
 
680
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
681
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
682
@@ -0,0 +1,1 @@
 
683
+foo
 
684
 
 
685
=== added file '?.txt'
 
686
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
687
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
688
@@ -0,0 +1,1 @@
 
689
+foo
 
690
 
 
691
''' % {'directory': _russian_test.encode('cp1251'),
 
692
       'test_txt': test_txt.encode('cp1251'),
 
693
      })
 
694
        self.assertEqualDiff(output, shouldbe)
 
695
 
583
696
 
584
697
class DiffWasIs(diff.DiffPath):
585
698
 
586
699
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
587
700
        self.to_file.write('was: ')
588
 
        self.to_file.write(self.old_tree.get_file(file_id).read())
 
701
        self.to_file.write(self.old_tree.get_file(old_path).read())
589
702
        self.to_file.write('is: ')
590
 
        self.to_file.write(self.new_tree.get_file(file_id).read())
591
 
        pass
 
703
        self.to_file.write(self.new_tree.get_file(new_path).read())
592
704
 
593
705
 
594
706
class TestDiffTree(tests.TestCaseWithTransport):
601
713
        self.new_tree = self.make_branch_and_tree('new-tree')
602
714
        self.new_tree.lock_write()
603
715
        self.addCleanup(self.new_tree.unlock)
604
 
        self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
 
716
        self.differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
605
717
 
606
718
    def test_diff_text(self):
607
719
        self.build_tree_contents([('old-tree/olddir/',),
608
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
720
                                  ('old-tree/olddir/oldfile', b'old\n')])
609
721
        self.old_tree.add('olddir')
610
 
        self.old_tree.add('olddir/oldfile', 'file-id')
 
722
        self.old_tree.add('olddir/oldfile', b'file-id')
611
723
        self.build_tree_contents([('new-tree/newdir/',),
612
 
                                  ('new-tree/newdir/newfile', 'new\n')])
 
724
                                  ('new-tree/newdir/newfile', b'new\n')])
613
725
        self.new_tree.add('newdir')
614
 
        self.new_tree.add('newdir/newfile', 'file-id')
615
 
        differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
616
 
        differ.diff_text('file-id', None, 'old label', 'new label')
 
726
        self.new_tree.add('newdir/newfile', b'file-id')
 
727
        differ = diff.DiffText(self.old_tree, self.new_tree, BytesIO())
 
728
        differ.diff_text('olddir/oldfile', None, 'old label',
 
729
                         'new label', b'file-id', None)
617
730
        self.assertEqual(
618
731
            '--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
619
732
            differ.to_file.getvalue())
620
733
        differ.to_file.seek(0)
621
 
        differ.diff_text(None, 'file-id', 'old label', 'new label')
 
734
        differ.diff_text(None, 'newdir/newfile',
 
735
                         'old label', 'new label', None, b'file-id')
622
736
        self.assertEqual(
623
737
            '--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
624
738
            differ.to_file.getvalue())
625
739
        differ.to_file.seek(0)
626
 
        differ.diff_text('file-id', 'file-id', 'old label', 'new label')
 
740
        differ.diff_text('olddir/oldfile', 'newdir/newfile',
 
741
                         'old label', 'new label', b'file-id', b'file-id')
627
742
        self.assertEqual(
628
743
            '--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
629
744
            differ.to_file.getvalue())
630
745
 
631
746
    def test_diff_deletion(self):
632
 
        self.build_tree_contents([('old-tree/file', 'contents'),
633
 
                                  ('new-tree/file', 'contents')])
634
 
        self.old_tree.add('file', 'file-id')
635
 
        self.new_tree.add('file', 'file-id')
 
747
        self.build_tree_contents([('old-tree/file', b'contents'),
 
748
                                  ('new-tree/file', b'contents')])
 
749
        self.old_tree.add('file', b'file-id')
 
750
        self.new_tree.add('file', b'file-id')
636
751
        os.unlink('new-tree/file')
637
752
        self.differ.show_diff(None)
638
753
        self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
639
754
 
640
755
    def test_diff_creation(self):
641
 
        self.build_tree_contents([('old-tree/file', 'contents'),
642
 
                                  ('new-tree/file', 'contents')])
643
 
        self.old_tree.add('file', 'file-id')
644
 
        self.new_tree.add('file', 'file-id')
 
756
        self.build_tree_contents([('old-tree/file', b'contents'),
 
757
                                  ('new-tree/file', b'contents')])
 
758
        self.old_tree.add('file', b'file-id')
 
759
        self.new_tree.add('file', b'file-id')
645
760
        os.unlink('old-tree/file')
646
761
        self.differ.show_diff(None)
647
 
        self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
 
762
        self.assertContainsRe(self.differ.to_file.getvalue(), r'\+contents')
648
763
 
649
764
    def test_diff_symlink(self):
650
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
765
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
651
766
        differ.diff_symlink('old target', None)
652
767
        self.assertEqual("=== target was 'old target'\n",
653
768
                         differ.to_file.getvalue())
654
769
 
655
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
770
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
656
771
        differ.diff_symlink(None, 'new target')
657
772
        self.assertEqual("=== target is 'new target'\n",
658
773
                         differ.to_file.getvalue())
659
774
 
660
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
775
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
661
776
        differ.diff_symlink('old target', 'new target')
662
777
        self.assertEqual("=== target changed 'old target' => 'new target'\n",
663
778
                         differ.to_file.getvalue())
664
779
 
665
780
    def test_diff(self):
666
781
        self.build_tree_contents([('old-tree/olddir/',),
667
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
782
                                  ('old-tree/olddir/oldfile', b'old\n')])
668
783
        self.old_tree.add('olddir')
669
 
        self.old_tree.add('olddir/oldfile', 'file-id')
 
784
        self.old_tree.add('olddir/oldfile', b'file-id')
670
785
        self.build_tree_contents([('new-tree/newdir/',),
671
 
                                  ('new-tree/newdir/newfile', 'new\n')])
 
786
                                  ('new-tree/newdir/newfile', b'new\n')])
672
787
        self.new_tree.add('newdir')
673
 
        self.new_tree.add('newdir/newfile', 'file-id')
674
 
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
788
        self.new_tree.add('newdir/newfile', b'file-id')
 
789
        self.differ.diff(b'file-id', 'olddir/oldfile', 'newdir/newfile')
675
790
        self.assertContainsRe(
676
791
            self.differ.to_file.getvalue(),
677
792
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
678
 
             ' \@\@\n-old\n\+new\n\n')
 
793
            r' \@\@\n-old\n\+new\n\n')
679
794
 
680
795
    def test_diff_kind_change(self):
681
 
        self.requireFeature(tests.SymlinkFeature)
 
796
        self.requireFeature(features.SymlinkFeature)
682
797
        self.build_tree_contents([('old-tree/olddir/',),
683
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
798
                                  ('old-tree/olddir/oldfile', b'old\n')])
684
799
        self.old_tree.add('olddir')
685
 
        self.old_tree.add('olddir/oldfile', 'file-id')
 
800
        self.old_tree.add('olddir/oldfile', b'file-id')
686
801
        self.build_tree(['new-tree/newdir/'])
687
802
        os.symlink('new', 'new-tree/newdir/newfile')
688
803
        self.new_tree.add('newdir')
689
 
        self.new_tree.add('newdir/newfile', 'file-id')
690
 
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
804
        self.new_tree.add('newdir/newfile', b'file-id')
 
805
        self.differ.diff(b'file-id', 'olddir/oldfile', 'newdir/newfile')
691
806
        self.assertContainsRe(
692
807
            self.differ.to_file.getvalue(),
693
808
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
694
 
             ' \@\@\n-old\n\n')
 
809
            r' \@\@\n-old\n\n')
695
810
        self.assertContainsRe(self.differ.to_file.getvalue(),
696
811
                              "=== target is u'new'\n")
697
812
 
698
813
    def test_diff_directory(self):
699
814
        self.build_tree(['new-tree/new-dir/'])
700
 
        self.new_tree.add('new-dir', 'new-dir-id')
701
 
        self.differ.diff('new-dir-id', None, 'new-dir')
702
 
        self.assertEqual(self.differ.to_file.getvalue(), '')
 
815
        self.new_tree.add('new-dir', b'new-dir-id')
 
816
        self.differ.diff(b'new-dir-id', None, 'new-dir')
 
817
        self.assertEqual(self.differ.to_file.getvalue(), b'')
703
818
 
704
819
    def create_old_new(self):
705
820
        self.build_tree_contents([('old-tree/olddir/',),
706
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
821
                                  ('old-tree/olddir/oldfile', b'old\n')])
707
822
        self.old_tree.add('olddir')
708
 
        self.old_tree.add('olddir/oldfile', 'file-id')
 
823
        self.old_tree.add('olddir/oldfile', b'file-id')
709
824
        self.build_tree_contents([('new-tree/newdir/',),
710
 
                                  ('new-tree/newdir/newfile', 'new\n')])
 
825
                                  ('new-tree/newdir/newfile', b'new\n')])
711
826
        self.new_tree.add('newdir')
712
 
        self.new_tree.add('newdir/newfile', 'file-id')
 
827
        self.new_tree.add('newdir/newfile', b'file-id')
713
828
 
714
829
    def test_register_diff(self):
715
830
        self.create_old_new()
717
832
        diff.DiffTree.diff_factories=old_diff_factories[:]
718
833
        diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
719
834
        try:
720
 
            differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
 
835
            differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
721
836
        finally:
722
837
            diff.DiffTree.diff_factories = old_diff_factories
723
 
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
838
        differ.diff(b'file-id', 'olddir/oldfile', 'newdir/newfile')
724
839
        self.assertNotContainsRe(
725
840
            differ.to_file.getvalue(),
726
841
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
727
 
             ' \@\@\n-old\n\+new\n\n')
 
842
            r' \@\@\n-old\n\+new\n\n')
728
843
        self.assertContainsRe(differ.to_file.getvalue(),
729
844
                              'was: old\nis: new\n')
730
845
 
731
846
    def test_extra_factories(self):
732
847
        self.create_old_new()
733
 
        differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
 
848
        differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO(),
734
849
                               extra_factories=[DiffWasIs.from_diff_tree])
735
 
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
850
        differ.diff(b'file-id', 'olddir/oldfile', 'newdir/newfile')
736
851
        self.assertNotContainsRe(
737
852
            differ.to_file.getvalue(),
738
853
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
739
 
             ' \@\@\n-old\n\+new\n\n')
 
854
            r' \@\@\n-old\n\+new\n\n')
740
855
        self.assertContainsRe(differ.to_file.getvalue(),
741
856
                              'was: old\nis: new\n')
742
857
 
764
879
        b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
765
880
        sm = self._PatienceSequenceMatcher(None, a, b)
766
881
        mb = sm.get_matching_blocks()
767
 
        self.assertEquals(35, len(mb))
 
882
        self.assertEqual(35, len(mb))
768
883
 
769
884
    def test_unique_lcs(self):
770
885
        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)])
 
886
        self.assertEqual(unique_lcs('', ''), [])
 
887
        self.assertEqual(unique_lcs('', 'a'), [])
 
888
        self.assertEqual(unique_lcs('a', ''), [])
 
889
        self.assertEqual(unique_lcs('a', 'a'), [(0, 0)])
 
890
        self.assertEqual(unique_lcs('a', 'b'), [])
 
891
        self.assertEqual(unique_lcs('ab', 'ab'), [(0, 0), (1, 1)])
 
892
        self.assertEqual(unique_lcs('abcde', 'cdeab'), [(2, 0), (3, 1), (4, 2)])
 
893
        self.assertEqual(unique_lcs('cdeab', 'abcde'), [(0, 2), (1, 3), (2, 4)])
 
894
        self.assertEqual(unique_lcs('abXde', 'abYde'), [(0, 0), (1, 1),
 
895
                                                         (3, 3), (4, 4)])
 
896
        self.assertEqual(unique_lcs('acbac', 'abc'), [(2, 1)])
782
897
 
783
898
    def test_recurse_matches(self):
784
899
        def test_one(a, b, matches):
785
900
            test_matches = []
786
901
            self._recurse_matches(
787
902
                a, b, 0, 0, len(a), len(b), test_matches, 10)
788
 
            self.assertEquals(test_matches, matches)
 
903
            self.assertEqual(test_matches, matches)
789
904
 
790
905
        test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
791
906
                 [(0, 0), (2, 2), (4, 4)])
794
909
        # Even though 'bc' is not unique globally, and is surrounded by
795
910
        # non-matching lines, we should still match, because they are locally
796
911
        # unique
797
 
        test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
 
912
        test_one('abcdbce', 'afbcgdbce', [(0, 0), (1, 2), (2, 3), (3, 5),
798
913
                                          (4, 6), (5, 7), (6, 8)])
799
914
 
800
915
        # recurse_matches doesn't match non-unique
805
920
        #test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
806
921
 
807
922
        # This is what it currently gives:
808
 
        test_one('aBccDe', 'abccde', [(0,0), (5,5)])
 
923
        test_one('aBccDe', 'abccde', [(0, 0), (5, 5)])
809
924
 
810
925
    def assertDiffBlocks(self, a, b, expected_blocks):
811
926
        """Check that the sequence matcher returns the correct blocks.
853
968
 
854
969
        # non unique lines surrounded by non-matching lines
855
970
        # won't be found
856
 
        self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
 
971
        self.assertDiffBlocks('aBccDe', 'abccde', [(0, 0, 1), (5, 5, 1)])
857
972
 
858
973
        # But they only need to be locally unique
859
 
        self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
 
974
        self.assertDiffBlocks('aBcDec', 'abcdec', [(0, 0, 1), (2, 2, 1), (4, 4, 2)])
860
975
 
861
976
        # non unique blocks won't be matched
862
 
        self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
 
977
        self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0, 0, 1), (8, 8, 1)])
863
978
 
864
979
        # 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)])
 
980
        self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0, 0, 1), (2, 2, 2),
 
981
                                              (5, 4, 1), (7, 5, 2), (10, 8, 1)])
867
982
 
868
 
        self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
 
983
        self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7, 7, 1)])
869
984
        self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
870
985
        self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
871
986
 
896
1011
    def test_opcodes(self):
897
1012
        def chk_ops(a, b, expected_codes):
898
1013
            s = self._PatienceSequenceMatcher(None, a, b)
899
 
            self.assertEquals(expected_codes, s.get_opcodes())
 
1014
            self.assertEqual(expected_codes, s.get_opcodes())
900
1015
 
901
1016
        chk_ops('', '', [])
902
1017
        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)
 
1018
        chk_ops('abc', '', [('delete', 0, 3, 0, 0)])
 
1019
        chk_ops('', 'abc', [('insert', 0, 0, 0, 3)])
 
1020
        chk_ops('abcd', 'abcd', [('equal',    0, 4, 0, 4)])
 
1021
        chk_ops('abcd', 'abce', [('equal',   0, 3, 0, 3),
 
1022
                                 ('replace', 3, 4, 3, 4)
 
1023
                                ])
 
1024
        chk_ops('eabc', 'abce', [('delete', 0, 1, 0, 0),
 
1025
                                 ('equal',  1, 4, 0, 3),
 
1026
                                 ('insert', 4, 4, 3, 4)
 
1027
                                ])
 
1028
        chk_ops('eabce', 'abce', [('delete', 0, 1, 0, 0),
 
1029
                                  ('equal',  1, 5, 0, 4)
915
1030
                                 ])
916
 
        chk_ops('abcde', 'abXde', [('equal',   0,2, 0,2),
917
 
                                   ('replace', 2,3, 2,3),
918
 
                                   ('equal',   3,5, 3,5)
 
1031
        chk_ops('abcde', 'abXde', [('equal',   0, 2, 0, 2),
 
1032
                                   ('replace', 2, 3, 2, 3),
 
1033
                                   ('equal',   3, 5, 3, 5)
919
1034
                                  ])
920
 
        chk_ops('abcde', 'abXYZde', [('equal',   0,2, 0,2),
921
 
                                     ('replace', 2,3, 2,5),
922
 
                                     ('equal',   3,5, 5,7)
 
1035
        chk_ops('abcde', 'abXYZde', [('equal',   0, 2, 0, 2),
 
1036
                                     ('replace', 2, 3, 2, 5),
 
1037
                                     ('equal',   3, 5, 5, 7)
923
1038
                                    ])
924
 
        chk_ops('abde', 'abXYZde', [('equal',  0,2, 0,2),
925
 
                                    ('insert', 2,2, 2,5),
926
 
                                    ('equal',  2,4, 5,7)
 
1039
        chk_ops('abde', 'abXYZde', [('equal',  0, 2, 0, 2),
 
1040
                                    ('insert', 2, 2, 2, 5),
 
1041
                                    ('equal',  2, 4, 5, 7)
927
1042
                                   ])
928
1043
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
929
 
                [('equal',  0,6,  0,6),
930
 
                 ('insert', 6,6,  6,11),
931
 
                 ('equal',  6,16, 11,21)
 
1044
                [('equal',  0, 6,  0, 6),
 
1045
                 ('insert', 6, 6,  6, 11),
 
1046
                 ('equal',  6, 16, 11, 21)
932
1047
                ])
933
1048
        chk_ops(
934
1049
                [ 'hello there\n'
936
1051
                , 'how are you today?\n'],
937
1052
                [ 'hello there\n'
938
1053
                , 'how are you today?\n'],
939
 
                [('equal',  0,1, 0,1),
940
 
                 ('delete', 1,2, 1,1),
941
 
                 ('equal',  2,3, 1,2),
 
1054
                [('equal',  0, 1, 0, 1),
 
1055
                 ('delete', 1, 2, 1, 1),
 
1056
                 ('equal',  2, 3, 1, 2),
942
1057
                ])
943
1058
        chk_ops('aBccDe', 'abccde',
944
 
                [('equal',   0,1, 0,1),
945
 
                 ('replace', 1,5, 1,5),
946
 
                 ('equal',   5,6, 5,6),
 
1059
                [('equal',   0, 1, 0, 1),
 
1060
                 ('replace', 1, 5, 1, 5),
 
1061
                 ('equal',   5, 6, 5, 6),
947
1062
                ])
948
1063
        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),
 
1064
                [('equal',   0, 1, 0, 1),
 
1065
                 ('replace', 1, 2, 1, 2),
 
1066
                 ('equal',   2, 3, 2, 3),
 
1067
                 ('replace', 3, 4, 3, 4),
 
1068
                 ('equal',   4, 6, 4, 6),
954
1069
                ])
955
1070
        chk_ops('aBcdEcdFg', 'abcdecdfg',
956
 
                [('equal',   0,1, 0,1),
957
 
                 ('replace', 1,8, 1,8),
958
 
                 ('equal',   8,9, 8,9)
 
1071
                [('equal',   0, 1, 0, 1),
 
1072
                 ('replace', 1, 8, 1, 8),
 
1073
                 ('equal',   8, 9, 8, 9)
959
1074
                ])
960
1075
        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)
 
1076
                [('equal',   0, 1, 0, 1),
 
1077
                 ('replace', 1, 2, 1, 2),
 
1078
                 ('equal',   2, 4, 2, 4),
 
1079
                 ('delete', 4, 5, 4, 4),
 
1080
                 ('equal',   5, 6, 4, 5),
 
1081
                 ('delete', 6, 7, 5, 5),
 
1082
                 ('equal',   7, 9, 5, 7),
 
1083
                 ('replace', 9, 10, 7, 8),
 
1084
                 ('equal',   10, 11, 8, 9)
970
1085
                ])
971
1086
 
972
1087
    def test_grouped_opcodes(self):
973
1088
        def chk_ops(a, b, expected_codes, n=3):
974
1089
            s = self._PatienceSequenceMatcher(None, a, b)
975
 
            self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
 
1090
            self.assertEqual(expected_codes, list(s.get_grouped_opcodes(n)))
976
1091
 
977
1092
        chk_ops('', '', [])
978
1093
        chk_ops([], [], [])
979
 
        chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
980
 
        chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
 
1094
        chk_ops('abc', '', [[('delete', 0, 3, 0, 0)]])
 
1095
        chk_ops('', 'abc', [[('insert', 0, 0, 0, 3)]])
981
1096
        chk_ops('abcd', 'abcd', [])
982
 
        chk_ops('abcd', 'abce', [[('equal',   0,3, 0,3),
983
 
                                  ('replace', 3,4, 3,4)
 
1097
        chk_ops('abcd', 'abce', [[('equal',   0, 3, 0, 3),
 
1098
                                  ('replace', 3, 4, 3, 4)
984
1099
                                 ]])
985
 
        chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
986
 
                                 ('equal',  1,4, 0,3),
987
 
                                 ('insert', 4,4, 3,4)
 
1100
        chk_ops('eabc', 'abce', [[('delete', 0, 1, 0, 0),
 
1101
                                 ('equal',  1, 4, 0, 3),
 
1102
                                 ('insert', 4, 4, 3, 4)
988
1103
                                ]])
989
1104
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
990
 
                [[('equal',  3,6, 3,6),
991
 
                  ('insert', 6,6, 6,11),
992
 
                  ('equal',  6,9, 11,14)
 
1105
                [[('equal',  3, 6, 3, 6),
 
1106
                  ('insert', 6, 6, 6, 11),
 
1107
                  ('equal',  6, 9, 11, 14)
993
1108
                  ]])
994
1109
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
995
 
                [[('equal',  2,6, 2,6),
996
 
                  ('insert', 6,6, 6,11),
997
 
                  ('equal',  6,10, 11,15)
 
1110
                [[('equal',  2, 6, 2, 6),
 
1111
                  ('insert', 6, 6, 6, 11),
 
1112
                  ('equal',  6, 10, 11, 15)
998
1113
                  ]], 4)
999
1114
        chk_ops('Xabcdef', 'abcdef',
1000
 
                [[('delete', 0,1, 0,0),
1001
 
                  ('equal',  1,4, 0,3)
 
1115
                [[('delete', 0, 1, 0, 0),
 
1116
                  ('equal',  1, 4, 0, 3)
1002
1117
                  ]])
1003
1118
        chk_ops('abcdef', 'abcdefX',
1004
 
                [[('equal',  3,6, 3,6),
1005
 
                  ('insert', 6,6, 6,7)
 
1119
                [[('equal',  3, 6, 3, 6),
 
1120
                  ('insert', 6, 6, 6, 7)
1006
1121
                  ]])
1007
1122
 
1008
1123
 
1015
1130
 
1016
1131
        self.assertDiffBlocks('ABCd efghIjk  L',
1017
1132
                              'AxyzBCn mo pqrstuvwI1 2  L',
1018
 
                              [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
1133
                              [(0, 0, 1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1019
1134
 
1020
1135
        # These are rot13 code snippets.
1021
1136
        self.assertDiffBlocks('''\
1062
1177
 
1063
1178
pynff pzq_zxqve(Pbzznaq):
1064
1179
'''.splitlines(True)
1065
 
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
1180
, [(0, 0, 1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1066
1181
 
1067
1182
    def test_patience_unified_diff(self):
1068
1183
        txt_a = ['hello there\n',
1072
1187
                 'how are you today?\n']
1073
1188
        unified_diff = patiencediff.unified_diff
1074
1189
        psm = self._PatienceSequenceMatcher
1075
 
        self.assertEquals(['--- \n',
 
1190
        self.assertEqual(['--- \n',
1076
1191
                           '+++ \n',
1077
1192
                           '@@ -1,3 +1,2 @@\n',
1078
1193
                           ' hello there\n',
1081
1196
                          ]
1082
1197
                          , list(unified_diff(txt_a, txt_b,
1083
1198
                                 sequencematcher=psm)))
1084
 
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1085
 
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
 
1199
        txt_a = [x+'\n' for x in 'abcdefghijklmnop']
 
1200
        txt_b = [x+'\n' for x in 'abcdefxydefghijklmnop']
1086
1201
        # This is the result with LongestCommonSubstring matching
1087
 
        self.assertEquals(['--- \n',
 
1202
        self.assertEqual(['--- \n',
1088
1203
                           '+++ \n',
1089
1204
                           '@@ -1,6 +1,11 @@\n',
1090
1205
                           ' a\n',
1100
1215
                           ' f\n']
1101
1216
                          , list(unified_diff(txt_a, txt_b)))
1102
1217
        # And the patience diff
1103
 
        self.assertEquals(['--- \n',
 
1218
        self.assertEqual(['--- \n',
1104
1219
                           '+++ \n',
1105
1220
                           '@@ -4,6 +4,11 @@\n',
1106
1221
                           ' d\n',
1126
1241
                 'how are you today?\n']
1127
1242
        unified_diff = patiencediff.unified_diff
1128
1243
        psm = self._PatienceSequenceMatcher
1129
 
        self.assertEquals(['--- a\t2008-08-08\n',
 
1244
        self.assertEqual(['--- a\t2008-08-08\n',
1130
1245
                           '+++ b\t2008-09-09\n',
1131
1246
                           '@@ -1,3 +1,2 @@\n',
1132
1247
                           ' hello there\n',
1142
1257
 
1143
1258
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1144
1259
 
1145
 
    _test_needs_features = [compiled_patiencediff_feature]
 
1260
    _test_needs_features = [features.compiled_patiencediff_feature]
1146
1261
 
1147
1262
    def setUp(self):
1148
1263
        super(TestPatienceDiffLib_c, self).setUp()
1149
 
        from bzrlib import _patiencediff_c
 
1264
        from breezy import _patiencediff_c
1150
1265
        self._unique_lcs = _patiencediff_c.unique_lcs_c
1151
1266
        self._recurse_matches = _patiencediff_c.recurse_matches_c
1152
1267
        self._PatienceSequenceMatcher = \
1174
1289
            _patiencediff_py.PatienceSequenceMatcher_py
1175
1290
 
1176
1291
    def test_patience_unified_diff_files(self):
1177
 
        txt_a = ['hello there\n',
1178
 
                 'world\n',
1179
 
                 'how are you today?\n']
1180
 
        txt_b = ['hello there\n',
1181
 
                 'how are you today?\n']
1182
 
        open('a1', 'wb').writelines(txt_a)
1183
 
        open('b1', 'wb').writelines(txt_b)
 
1292
        txt_a = [b'hello there\n',
 
1293
                 b'world\n',
 
1294
                 b'how are you today?\n']
 
1295
        txt_b = [b'hello there\n',
 
1296
                 b'how are you today?\n']
 
1297
        with open('a1', 'wb') as f: f.writelines(txt_a)
 
1298
        with open('b1', 'wb') as f: f.writelines(txt_b)
1184
1299
 
1185
1300
        unified_diff_files = patiencediff.unified_diff_files
1186
1301
        psm = self._PatienceSequenceMatcher
1187
 
        self.assertEquals(['--- a1\n',
 
1302
        self.assertEqual(['--- a1\n',
1188
1303
                           '+++ b1\n',
1189
1304
                           '@@ -1,3 +1,2 @@\n',
1190
1305
                           ' hello there\n',
1194
1309
                          , list(unified_diff_files('a1', 'b1',
1195
1310
                                 sequencematcher=psm)))
1196
1311
 
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)
 
1312
        txt_a = [x+'\n' for x in 'abcdefghijklmnop']
 
1313
        txt_b = [x+'\n' for x in 'abcdefxydefghijklmnop']
 
1314
        with open('a2', 'wb') as f: f.writelines(txt_a)
 
1315
        with open('b2', 'wb') as f: f.writelines(txt_b)
1201
1316
 
1202
1317
        # This is the result with LongestCommonSubstring matching
1203
 
        self.assertEquals(['--- a2\n',
 
1318
        self.assertEqual(['--- a2\n',
1204
1319
                           '+++ b2\n',
1205
1320
                           '@@ -1,6 +1,11 @@\n',
1206
1321
                           ' a\n',
1217
1332
                          , list(unified_diff_files('a2', 'b2')))
1218
1333
 
1219
1334
        # 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)))
 
1335
        self.assertEqual(['--- a2\n',
 
1336
                          '+++ b2\n',
 
1337
                          '@@ -4,6 +4,11 @@\n',
 
1338
                          ' d\n',
 
1339
                          ' e\n',
 
1340
                          ' f\n',
 
1341
                          '+x\n',
 
1342
                          '+y\n',
 
1343
                          '+d\n',
 
1344
                          '+e\n',
 
1345
                          '+f\n',
 
1346
                          ' g\n',
 
1347
                          ' h\n',
 
1348
                          ' i\n'],
 
1349
                         list(unified_diff_files('a2', 'b2',
 
1350
                                                 sequencematcher=psm)))
1237
1351
 
1238
1352
 
1239
1353
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1240
1354
 
1241
 
    _test_needs_features = [compiled_patiencediff_feature]
 
1355
    _test_needs_features = [features.compiled_patiencediff_feature]
1242
1356
 
1243
1357
    def setUp(self):
1244
1358
        super(TestPatienceDiffLibFiles_c, self).setUp()
1245
 
        from bzrlib import _patiencediff_c
 
1359
        from breezy import _patiencediff_c
1246
1360
        self._PatienceSequenceMatcher = \
1247
1361
            _patiencediff_c.PatienceSequenceMatcher_c
1248
1362
 
1250
1364
class TestUsingCompiledIfAvailable(tests.TestCase):
1251
1365
 
1252
1366
    def test_PatienceSequenceMatcher(self):
1253
 
        if compiled_patiencediff_feature.available():
1254
 
            from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
 
1367
        if features.compiled_patiencediff_feature.available():
 
1368
            from breezy._patiencediff_c import PatienceSequenceMatcher_c
1255
1369
            self.assertIs(PatienceSequenceMatcher_c,
1256
1370
                          patiencediff.PatienceSequenceMatcher)
1257
1371
        else:
1258
 
            from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
 
1372
            from breezy._patiencediff_py import PatienceSequenceMatcher_py
1259
1373
            self.assertIs(PatienceSequenceMatcher_py,
1260
1374
                          patiencediff.PatienceSequenceMatcher)
1261
1375
 
1262
1376
    def test_unique_lcs(self):
1263
 
        if compiled_patiencediff_feature.available():
1264
 
            from bzrlib._patiencediff_c import unique_lcs_c
 
1377
        if features.compiled_patiencediff_feature.available():
 
1378
            from breezy._patiencediff_c import unique_lcs_c
1265
1379
            self.assertIs(unique_lcs_c,
1266
1380
                          patiencediff.unique_lcs)
1267
1381
        else:
1268
 
            from bzrlib._patiencediff_py import unique_lcs_py
 
1382
            from breezy._patiencediff_py import unique_lcs_py
1269
1383
            self.assertIs(unique_lcs_py,
1270
1384
                          patiencediff.unique_lcs)
1271
1385
 
1272
1386
    def test_recurse_matches(self):
1273
 
        if compiled_patiencediff_feature.available():
1274
 
            from bzrlib._patiencediff_c import recurse_matches_c
 
1387
        if features.compiled_patiencediff_feature.available():
 
1388
            from breezy._patiencediff_c import recurse_matches_c
1275
1389
            self.assertIs(recurse_matches_c,
1276
1390
                          patiencediff.recurse_matches)
1277
1391
        else:
1278
 
            from bzrlib._patiencediff_py import recurse_matches_py
 
1392
            from breezy._patiencediff_py import recurse_matches_py
1279
1393
            self.assertIs(recurse_matches_py,
1280
1394
                          patiencediff.recurse_matches)
1281
1395
 
1298
1412
                         diff_obj._get_command('old-path', 'new-path'))
1299
1413
 
1300
1414
    def test_from_string_path_with_backslashes(self):
1301
 
        self.requireFeature(test_win32utils.BackslashDirSeparatorFeature)
 
1415
        self.requireFeature(features.backslashdir_feature)
1302
1416
        tool = 'C:\\Tools\\Diff.exe'
1303
1417
        diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
1304
1418
        self.addCleanup(diff_obj.finish)
1308
1422
                         diff_obj._get_command('old-path', 'new-path'))
1309
1423
 
1310
1424
    def test_execute(self):
1311
 
        output = StringIO()
 
1425
        output = BytesIO()
1312
1426
        diff_obj = diff.DiffFromTool(['python', '-c',
1313
 
                                      'print "@old_path @new_path"'],
 
1427
                                      'print("@old_path @new_path")'],
1314
1428
                                     None, None, output)
1315
1429
        self.addCleanup(diff_obj.finish)
1316
1430
        diff_obj._execute('old', 'new')
1317
 
        self.assertEqual(output.getvalue().rstrip(), 'old new')
 
1431
        self.assertEqual(output.getvalue().rstrip(), b'old new')
1318
1432
 
1319
 
    def test_excute_missing(self):
 
1433
    def test_execute_missing(self):
1320
1434
        diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1321
1435
                                     None, None, None)
1322
1436
        self.addCleanup(diff_obj.finish)
1326
1440
                         ' on this machine', str(e))
1327
1441
 
1328
1442
    def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1329
 
        self.requireFeature(AttribFeature)
1330
 
        output = StringIO()
 
1443
        self.requireFeature(features.AttribFeature)
 
1444
        output = BytesIO()
1331
1445
        tree = self.make_branch_and_tree('tree')
1332
 
        self.build_tree_contents([('tree/file', 'content')])
1333
 
        tree.add('file', 'file-id')
 
1446
        self.build_tree_contents([('tree/file', b'content')])
 
1447
        tree.add('file', b'file-id')
1334
1448
        tree.commit('old tree')
1335
1449
        tree.lock_read()
1336
1450
        self.addCleanup(tree.unlock)
1340
1454
        diff_obj = diff.DiffFromTool(['python', '-c',
1341
1455
                                      'print "@old_path @new_path"'],
1342
1456
                                     basis_tree, tree, output)
1343
 
        diff_obj._prepare_files('file-id', 'file', 'file')
 
1457
        diff_obj._prepare_files('file', 'file', file_id=b'file-id')
1344
1458
        # The old content should be readonly
1345
1459
        self.assertReadableByAttrib(diff_obj._root, 'old\\file',
1346
1460
                                    r'R.*old\\file$')
1356
1470
        self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1357
1471
 
1358
1472
    def test_prepare_files(self):
1359
 
        output = StringIO()
 
1473
        output = BytesIO()
1360
1474
        tree = self.make_branch_and_tree('tree')
1361
 
        self.build_tree_contents([('tree/oldname', 'oldcontent')])
1362
 
        self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1363
 
        tree.add('oldname', 'file-id')
1364
 
        tree.add('oldname2', 'file2-id')
 
1475
        self.build_tree_contents([('tree/oldname', b'oldcontent')])
 
1476
        self.build_tree_contents([('tree/oldname2', b'oldcontent2')])
 
1477
        tree.add('oldname', b'file-id')
 
1478
        tree.add('oldname2', b'file2-id')
1365
1479
        # Earliest allowable date on FAT32 filesystems is 1980-01-01
1366
1480
        tree.commit('old tree', timestamp=315532800)
1367
1481
        tree.rename_one('oldname', 'newname')
1368
1482
        tree.rename_one('oldname2', 'newname2')
1369
 
        self.build_tree_contents([('tree/newname', 'newcontent')])
1370
 
        self.build_tree_contents([('tree/newname2', 'newcontent2')])
 
1483
        self.build_tree_contents([('tree/newname', b'newcontent')])
 
1484
        self.build_tree_contents([('tree/newname2', b'newcontent2')])
1371
1485
        old_tree = tree.basis_tree()
1372
1486
        old_tree.lock_read()
1373
1487
        self.addCleanup(old_tree.unlock)
1377
1491
                                      'print "@old_path @new_path"'],
1378
1492
                                     old_tree, tree, output)
1379
1493
        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')
 
1494
        self.assertContainsRe(diff_obj._root, 'brz-diff-[^/]*')
 
1495
        old_path, new_path = diff_obj._prepare_files(
 
1496
                'oldname', 'newname', file_id='file-id')
1383
1497
        self.assertContainsRe(old_path, 'old/oldname$')
1384
1498
        self.assertEqual(315532800, os.stat(old_path).st_mtime)
1385
1499
        self.assertContainsRe(new_path, 'tree/newname$')
1388
1502
        if osutils.host_os_dereferences_symlinks():
1389
1503
            self.assertTrue(os.path.samefile('tree/newname', new_path))
1390
1504
        # make sure we can create files with the same parent directories
1391
 
        diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
 
1505
        diff_obj._prepare_files('oldname2', 'newname2', file_id='file2-id')
 
1506
 
 
1507
 
 
1508
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
 
1509
 
 
1510
    def test_encodable_filename(self):
 
1511
        # Just checks file path for external diff tool.
 
1512
        # We cannot change CPython's internal encoding used by os.exec*.
 
1513
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
 
1514
                                    None, None, None)
 
1515
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
1516
            encoding = scenario['encoding']
 
1517
            dirname = scenario['info']['directory']
 
1518
            filename = scenario['info']['filename']
 
1519
 
 
1520
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
1521
            relpath = dirname + u'/' + filename
 
1522
            fullpath = diffobj._safe_filename('safe', relpath)
 
1523
            self.assertEqual(fullpath,
 
1524
                             fullpath.encode(encoding).decode(encoding))
 
1525
            self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
 
1526
 
 
1527
    def test_unencodable_filename(self):
 
1528
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
 
1529
                                    None, None, None)
 
1530
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
1531
            encoding = scenario['encoding']
 
1532
            dirname = scenario['info']['directory']
 
1533
            filename = scenario['info']['filename']
 
1534
 
 
1535
            if encoding == 'iso-8859-1':
 
1536
                encoding = 'iso-8859-2'
 
1537
            else:
 
1538
                encoding = 'iso-8859-1'
 
1539
 
 
1540
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
1541
            relpath = dirname + u'/' + filename
 
1542
            fullpath = diffobj._safe_filename('safe', relpath)
 
1543
            self.assertEqual(fullpath,
 
1544
                             fullpath.encode(encoding).decode(encoding))
 
1545
            self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
1392
1546
 
1393
1547
 
1394
1548
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1395
1549
 
1396
1550
    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
 
        """
 
1551
        """Call get_trees_and_branches_to_diff_locked."""
1400
1552
        return diff.get_trees_and_branches_to_diff_locked(
1401
1553
            path_list, revision_specs, old_url, new_url, self.addCleanup)
1402
1554
 
1418
1570
 
1419
1571
    def test_with_rev_specs(self):
1420
1572
        tree = self.make_branch_and_tree('tree')
1421
 
        self.build_tree_contents([('tree/file', 'oldcontent')])
1422
 
        tree.add('file', 'file-id')
1423
 
        tree.commit('old tree', timestamp=0, rev_id="old-id")
1424
 
        self.build_tree_contents([('tree/file', 'newcontent')])
1425
 
        tree.commit('new tree', timestamp=0, rev_id="new-id")
 
1573
        self.build_tree_contents([('tree/file', b'oldcontent')])
 
1574
        tree.add('file', b'file-id')
 
1575
        tree.commit('old tree', timestamp=0, rev_id=b"old-id")
 
1576
        self.build_tree_contents([('tree/file', b'newcontent')])
 
1577
        tree.commit('new tree', timestamp=0, rev_id=b"new-id")
1426
1578
 
1427
1579
        revisions = [revisionspec.RevisionSpec.from_string('1'),
1428
1580
                     revisionspec.RevisionSpec.from_string('2')]
1432
1584
            ['tree'], revisions, None, None)
1433
1585
 
1434
1586
        self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1435
 
        self.assertEqual("old-id", old_tree.get_revision_id())
 
1587
        self.assertEqual(b"old-id", old_tree.get_revision_id())
1436
1588
        self.assertIsInstance(new_tree, revisiontree.RevisionTree)
1437
 
        self.assertEqual("new-id", new_tree.get_revision_id())
 
1589
        self.assertEqual(b"new-id", new_tree.get_revision_id())
1438
1590
        self.assertEqual(tree.branch.base, old_branch.base)
1439
1591
        self.assertEqual(tree.branch.base, new_branch.base)
1440
1592
        self.assertIs(None, specific_files)
1441
1593
        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