/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: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2020-08-23 01:15:41 UTC
  • mfrom: (7520.1.4 merge-3.1)
  • Revision ID: breezy.the.bot@gmail.com-20200823011541-nv0oh7nzaganx2qy
Merge lp:brz/3.1.

Merged from https://code.launchpad.net/~jelmer/brz/merge-3.1/+merge/389690

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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
import contextlib
 
18
from io import BytesIO
17
19
import os
18
 
from cStringIO import StringIO
 
20
import re
19
21
import subprocess
20
22
import sys
21
23
import tempfile
22
24
 
23
 
from bzrlib import (
 
25
from .. import (
24
26
    diff,
25
27
    errors,
26
28
    osutils,
27
 
    patiencediff,
28
 
    _patiencediff_py,
29
29
    revision as _mod_revision,
30
30
    revisionspec,
31
31
    revisiontree,
32
32
    tests,
33
 
    transform,
34
 
    )
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
    )
 
34
from ..tests import (
 
35
    features,
 
36
    EncodingAdapter,
 
37
    )
 
38
from ..tests.scenarios import load_tests_apply_scenarios
 
39
 
 
40
 
 
41
load_tests = load_tests_apply_scenarios
 
42
 
 
43
 
 
44
def subst_dates(string):
 
45
    """Replace date strings with constant values."""
 
46
    return re.sub(br'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [-\+]\d{4}',
 
47
                  b'YYYY-MM-DD HH:MM:SS +ZZZZ', string)
58
48
 
59
49
 
60
50
def udiff_lines(old, new, allow_binary=False):
61
 
    output = StringIO()
 
51
    output = BytesIO()
62
52
    diff.internal_diff('old', old, 'new', new, output, allow_binary)
63
53
    output.seek(0, 0)
64
54
    return output.readlines()
66
56
 
67
57
def external_udiff_lines(old, new, use_stringio=False):
68
58
    if use_stringio:
69
 
        # StringIO has no fileno, so it tests a different codepath
70
 
        output = StringIO()
 
59
        # BytesIO has no fileno, so it tests a different codepath
 
60
        output = BytesIO()
71
61
    else:
72
62
        output = tempfile.TemporaryFile()
73
63
    try:
80
70
    return lines
81
71
 
82
72
 
 
73
class StubO(object):
 
74
    """Simple file-like object that allows writes with any type and records."""
 
75
 
 
76
    def __init__(self):
 
77
        self.write_record = []
 
78
 
 
79
    def write(self, data):
 
80
        self.write_record.append(data)
 
81
 
 
82
    def check_types(self, testcase, expected_type):
 
83
        testcase.assertFalse(
 
84
            any(not isinstance(o, expected_type) for o in self.write_record),
 
85
            "Not all writes of type %s: %r" % (
 
86
                expected_type.__name__, self.write_record))
 
87
 
 
88
 
 
89
class TestDiffOptions(tests.TestCase):
 
90
 
 
91
    def test_unified_added(self):
 
92
        """Check for default style '-u' only if no other style specified
 
93
        in 'diff-options'.
 
94
        """
 
95
        # Verify that style defaults to unified, id est '-u' appended
 
96
        # to option list, in the absence of an alternative style.
 
97
        self.assertEqual(['-a', '-u'], diff.default_style_unified(['-a']))
 
98
 
 
99
 
 
100
class TestDiffOptionsScenarios(tests.TestCase):
 
101
 
 
102
    scenarios = [(s, dict(style=s)) for s in diff.style_option_list]
 
103
    style = None  # Set by load_tests_apply_scenarios from scenarios
 
104
 
 
105
    def test_unified_not_added(self):
 
106
        # Verify that for all valid style options, '-u' is not
 
107
        # appended to option list.
 
108
        ret_opts = diff.default_style_unified(diff_opts=["%s" % (self.style,)])
 
109
        self.assertEqual(["%s" % (self.style,)], ret_opts)
 
110
 
 
111
 
83
112
class TestDiff(tests.TestCase):
84
113
 
85
114
    def test_add_nl(self):
86
115
        """diff generates a valid diff for patches that add a newline"""
87
 
        lines = udiff_lines(['boo'], ['boo\n'])
 
116
        lines = udiff_lines([b'boo'], [b'boo\n'])
88
117
        self.check_patch(lines)
89
 
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
90
 
            ## "expected no-nl, got %r" % lines[4]
 
118
        self.assertEqual(lines[4], b'\\ No newline at end of file\n')
 
119
        ## "expected no-nl, got %r" % lines[4]
91
120
 
92
121
    def test_add_nl_2(self):
93
122
        """diff generates a valid diff for patches that change last line and
94
123
        add a newline.
95
124
        """
96
 
        lines = udiff_lines(['boo'], ['goo\n'])
 
125
        lines = udiff_lines([b'boo'], [b'goo\n'])
97
126
        self.check_patch(lines)
98
 
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
99
 
            ## "expected no-nl, got %r" % lines[4]
 
127
        self.assertEqual(lines[4], b'\\ No newline at end of file\n')
 
128
        ## "expected no-nl, got %r" % lines[4]
100
129
 
101
130
    def test_remove_nl(self):
102
131
        """diff generates a valid diff for patches that change last line and
103
132
        add a newline.
104
133
        """
105
 
        lines = udiff_lines(['boo\n'], ['boo'])
 
134
        lines = udiff_lines([b'boo\n'], [b'boo'])
106
135
        self.check_patch(lines)
107
 
        self.assertEquals(lines[5], '\\ No newline at end of file\n')
108
 
            ## "expected no-nl, got %r" % lines[5]
 
136
        self.assertEqual(lines[5], b'\\ No newline at end of file\n')
 
137
        ## "expected no-nl, got %r" % lines[5]
109
138
 
110
139
    def check_patch(self, lines):
111
 
        self.assert_(len(lines) > 1)
112
 
            ## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
113
 
        self.assert_(lines[0].startswith ('---'))
114
 
            ## 'No orig line for patch:\n%s' % "".join(lines)
115
 
        self.assert_(lines[1].startswith ('+++'))
116
 
            ## 'No mod line for patch:\n%s' % "".join(lines)
117
 
        self.assert_(len(lines) > 2)
118
 
            ## "No hunks for patch:\n%s" % "".join(lines)
119
 
        self.assert_(lines[2].startswith('@@'))
120
 
            ## "No hunk header for patch:\n%s" % "".join(lines)
121
 
        self.assert_('@@' in lines[2][2:])
122
 
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
 
140
        self.assertTrue(len(lines) > 1)
 
141
        ## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
 
142
        self.assertTrue(lines[0].startswith(b'---'))
 
143
        ## 'No orig line for patch:\n%s' % "".join(lines)
 
144
        self.assertTrue(lines[1].startswith(b'+++'))
 
145
        ## 'No mod line for patch:\n%s' % "".join(lines)
 
146
        self.assertTrue(len(lines) > 2)
 
147
        ## "No hunks for patch:\n%s" % "".join(lines)
 
148
        self.assertTrue(lines[2].startswith(b'@@'))
 
149
        ## "No hunk header for patch:\n%s" % "".join(lines)
 
150
        self.assertTrue(b'@@' in lines[2][2:])
 
151
        ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
123
152
 
124
153
    def test_binary_lines(self):
125
154
        empty = []
126
 
        uni_lines = [1023 * 'a' + '\x00']
127
 
        self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines , empty)
 
155
        uni_lines = [1023 * b'a' + b'\x00']
 
156
        self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines, empty)
128
157
        self.assertRaises(errors.BinaryFile, udiff_lines, empty, uni_lines)
129
 
        udiff_lines(uni_lines , empty, allow_binary=True)
 
158
        udiff_lines(uni_lines, empty, allow_binary=True)
130
159
        udiff_lines(empty, uni_lines, allow_binary=True)
131
160
 
132
161
    def test_external_diff(self):
133
 
        lines = external_udiff_lines(['boo\n'], ['goo\n'])
 
162
        lines = external_udiff_lines([b'boo\n'], [b'goo\n'])
134
163
        self.check_patch(lines)
135
 
        self.assertEqual('\n', lines[-1])
 
164
        self.assertEqual(b'\n', lines[-1])
136
165
 
137
166
    def test_external_diff_no_fileno(self):
138
167
        # Make sure that we can handle not having a fileno, even
139
168
        # if the diff is large
140
 
        lines = external_udiff_lines(['boo\n']*10000,
141
 
                                     ['goo\n']*10000,
 
169
        lines = external_udiff_lines([b'boo\n'] * 10000,
 
170
                                     [b'goo\n'] * 10000,
142
171
                                     use_stringio=True)
143
172
        self.check_patch(lines)
144
173
 
145
174
    def test_external_diff_binary_lang_c(self):
146
 
        old_env = {}
147
175
        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)
 
176
            self.overrideEnv(lang, 'C')
 
177
        lines = external_udiff_lines([b'\x00foobar\n'], [b'foo\x00bar\n'])
 
178
        # Older versions of diffutils say "Binary files", newer
 
179
        # versions just say "Files".
 
180
        self.assertContainsRe(
 
181
            lines[0], b'(Binary f|F)iles old and new differ\n')
 
182
        self.assertEqual(lines[1:], [b'\n'])
159
183
 
160
184
    def test_no_external_diff(self):
161
185
        """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
 
186
        # Make sure no 'diff' command is available
 
187
        # XXX: Weird, using None instead of '' breaks the test -- vila 20101216
 
188
        self.overrideEnv('PATH', '')
 
189
        self.assertRaises(errors.NoDiff, diff.external_diff,
 
190
                          b'old', [b'boo\n'], b'new', [b'goo\n'],
 
191
                          BytesIO(), diff_opts=['-u'])
171
192
 
172
193
    def test_internal_diff_default(self):
173
194
        # Default internal diff encoding is utf8
174
 
        output = StringIO()
175
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
176
 
                           u'new_\xe5', ['new_text\n'], output)
 
195
        output = BytesIO()
 
196
        diff.internal_diff(u'old_\xb5', [b'old_text\n'],
 
197
                           u'new_\xe5', [b'new_text\n'], output)
177
198
        lines = output.getvalue().splitlines(True)
178
199
        self.check_patch(lines)
179
 
        self.assertEquals(['--- old_\xc2\xb5\n',
180
 
                           '+++ new_\xc3\xa5\n',
181
 
                           '@@ -1,1 +1,1 @@\n',
182
 
                           '-old_text\n',
183
 
                           '+new_text\n',
184
 
                           '\n',
185
 
                          ]
186
 
                          , lines)
 
200
        self.assertEqual([b'--- old_\xc2\xb5\n',
 
201
                          b'+++ new_\xc3\xa5\n',
 
202
                          b'@@ -1,1 +1,1 @@\n',
 
203
                          b'-old_text\n',
 
204
                          b'+new_text\n',
 
205
                          b'\n',
 
206
                          ], lines)
187
207
 
188
208
    def test_internal_diff_utf8(self):
189
 
        output = StringIO()
190
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
191
 
                           u'new_\xe5', ['new_text\n'], output,
 
209
        output = BytesIO()
 
210
        diff.internal_diff(u'old_\xb5', [b'old_text\n'],
 
211
                           u'new_\xe5', [b'new_text\n'], output,
192
212
                           path_encoding='utf8')
193
213
        lines = output.getvalue().splitlines(True)
194
214
        self.check_patch(lines)
195
 
        self.assertEquals(['--- old_\xc2\xb5\n',
196
 
                           '+++ new_\xc3\xa5\n',
197
 
                           '@@ -1,1 +1,1 @@\n',
198
 
                           '-old_text\n',
199
 
                           '+new_text\n',
200
 
                           '\n',
201
 
                          ]
202
 
                          , lines)
 
215
        self.assertEqual([b'--- old_\xc2\xb5\n',
 
216
                          b'+++ new_\xc3\xa5\n',
 
217
                          b'@@ -1,1 +1,1 @@\n',
 
218
                          b'-old_text\n',
 
219
                          b'+new_text\n',
 
220
                          b'\n',
 
221
                          ], lines)
203
222
 
204
223
    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,
 
224
        output = BytesIO()
 
225
        diff.internal_diff(u'old_\xb5', [b'old_text\n'],
 
226
                           u'new_\xe5', [b'new_text\n'], output,
208
227
                           path_encoding='iso-8859-1')
209
228
        lines = output.getvalue().splitlines(True)
210
229
        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',
217
 
                          ]
218
 
                          , lines)
 
230
        self.assertEqual([b'--- old_\xb5\n',
 
231
                          b'+++ new_\xe5\n',
 
232
                          b'@@ -1,1 +1,1 @@\n',
 
233
                          b'-old_text\n',
 
234
                          b'+new_text\n',
 
235
                          b'\n',
 
236
                          ], lines)
219
237
 
220
238
    def test_internal_diff_no_content(self):
221
 
        output = StringIO()
 
239
        output = BytesIO()
222
240
        diff.internal_diff(u'old', [], u'new', [], output)
223
 
        self.assertEqual('', output.getvalue())
 
241
        self.assertEqual(b'', output.getvalue())
224
242
 
225
243
    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'],
 
244
        output = BytesIO()
 
245
        diff.internal_diff(u'old', [b'text\n', b'contents\n'],
 
246
                           u'new', [b'text\n', b'contents\n'],
229
247
                           output)
230
 
        self.assertEqual('', output.getvalue())
 
248
        self.assertEqual(b'', output.getvalue())
231
249
 
232
250
    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')
 
251
        output = StubO()
 
252
        diff.internal_diff(u'old_\xb5', [b'old_text\n'],
 
253
                           u'new_\xe5', [b'new_text\n'], output)
 
254
        output.check_types(self, bytes)
 
255
 
 
256
    def test_internal_diff_default_context(self):
 
257
        output = BytesIO()
 
258
        diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
 
259
                                   b'same_text\n', b'same_text\n', b'old_text\n'],
 
260
                           'new', [b'same_text\n', b'same_text\n', b'same_text\n',
 
261
                                   b'same_text\n', b'same_text\n', b'new_text\n'], output)
 
262
        lines = output.getvalue().splitlines(True)
 
263
        self.check_patch(lines)
 
264
        self.assertEqual([b'--- old\n',
 
265
                          b'+++ new\n',
 
266
                          b'@@ -3,4 +3,4 @@\n',
 
267
                          b' same_text\n',
 
268
                          b' same_text\n',
 
269
                          b' same_text\n',
 
270
                          b'-old_text\n',
 
271
                          b'+new_text\n',
 
272
                          b'\n',
 
273
                          ], lines)
 
274
 
 
275
    def test_internal_diff_no_context(self):
 
276
        output = BytesIO()
 
277
        diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
 
278
                                   b'same_text\n', b'same_text\n', b'old_text\n'],
 
279
                           'new', [b'same_text\n', b'same_text\n', b'same_text\n',
 
280
                                   b'same_text\n', b'same_text\n', b'new_text\n'], output,
 
281
                           context_lines=0)
 
282
        lines = output.getvalue().splitlines(True)
 
283
        self.check_patch(lines)
 
284
        self.assertEqual([b'--- old\n',
 
285
                          b'+++ new\n',
 
286
                          b'@@ -6,1 +6,1 @@\n',
 
287
                          b'-old_text\n',
 
288
                          b'+new_text\n',
 
289
                          b'\n',
 
290
                          ], lines)
 
291
 
 
292
    def test_internal_diff_more_context(self):
 
293
        output = BytesIO()
 
294
        diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
 
295
                                   b'same_text\n', b'same_text\n', b'old_text\n'],
 
296
                           'new', [b'same_text\n', b'same_text\n', b'same_text\n',
 
297
                                   b'same_text\n', b'same_text\n', b'new_text\n'], output,
 
298
                           context_lines=4)
 
299
        lines = output.getvalue().splitlines(True)
 
300
        self.check_patch(lines)
 
301
        self.assertEqual([b'--- old\n',
 
302
                          b'+++ new\n',
 
303
                          b'@@ -2,5 +2,5 @@\n',
 
304
                          b' same_text\n',
 
305
                          b' same_text\n',
 
306
                          b' same_text\n',
 
307
                          b' same_text\n',
 
308
                          b'-old_text\n',
 
309
                          b'+new_text\n',
 
310
                          b'\n',
 
311
                          ], lines)
239
312
 
240
313
 
241
314
class TestDiffFiles(tests.TestCaseInTempDir):
242
315
 
243
316
    def test_external_diff_binary(self):
244
317
        """The output when using external diff should use diff's i18n error"""
 
318
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
 
319
            self.overrideEnv(lang, 'C')
245
320
        # Make sure external_diff doesn't fail in the current LANG
246
 
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
321
        lines = external_udiff_lines([b'\x00foobar\n'], [b'foo\x00bar\n'])
247
322
 
248
323
        cmd = ['diff', '-u', '--binary', 'old', 'new']
249
 
        open('old', 'wb').write('\x00foobar\n')
250
 
        open('new', 'wb').write('foo\x00bar\n')
 
324
        with open('old', 'wb') as f:
 
325
            f.write(b'\x00foobar\n')
 
326
        with open('new', 'wb') as f:
 
327
            f.write(b'foo\x00bar\n')
251
328
        pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
252
 
                                     stdin=subprocess.PIPE)
 
329
                                stdin=subprocess.PIPE)
253
330
        out, err = pipe.communicate()
254
 
        # Diff returns '2' on Binary files.
255
 
        self.assertEqual(2, pipe.returncode)
256
331
        # We should output whatever diff tells us, plus a trailing newline
257
 
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
258
 
 
259
 
 
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):
 
332
        self.assertEqual(out.splitlines(True) + [b'\n'], lines)
 
333
 
 
334
 
 
335
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
 
336
    output = BytesIO()
 
337
    if working_tree is not None:
 
338
        extra_trees = (working_tree,)
 
339
    else:
 
340
        extra_trees = ()
 
341
    diff.show_diff_trees(tree1, tree2, output,
 
342
                         specific_files=specific_files,
 
343
                         extra_trees=extra_trees, old_label='old/',
 
344
                         new_label='new/')
 
345
    return output.getvalue()
 
346
 
 
347
 
 
348
class TestDiffDates(tests.TestCaseWithTransport):
277
349
 
278
350
    def setUp(self):
279
351
        super(TestDiffDates, self).setUp()
280
352
        self.wt = self.make_branch_and_tree('.')
281
353
        self.b = self.wt.branch
282
354
        self.build_tree_contents([
283
 
            ('file1', 'file1 contents at rev 1\n'),
284
 
            ('file2', 'file2 contents at rev 1\n')
 
355
            ('file1', b'file1 contents at rev 1\n'),
 
356
            ('file2', b'file2 contents at rev 1\n')
285
357
            ])
286
358
        self.wt.add(['file1', 'file2'])
287
359
        self.wt.commit(
288
360
            message='Revision 1',
289
 
            timestamp=1143849600, # 2006-04-01 00:00:00 UTC
 
361
            timestamp=1143849600,  # 2006-04-01 00:00:00 UTC
290
362
            timezone=0,
291
 
            rev_id='rev-1')
292
 
        self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
 
363
            rev_id=b'rev-1')
 
364
        self.build_tree_contents([('file1', b'file1 contents at rev 2\n')])
293
365
        self.wt.commit(
294
366
            message='Revision 2',
295
 
            timestamp=1143936000, # 2006-04-02 00:00:00 UTC
 
367
            timestamp=1143936000,  # 2006-04-02 00:00:00 UTC
296
368
            timezone=28800,
297
 
            rev_id='rev-2')
298
 
        self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
 
369
            rev_id=b'rev-2')
 
370
        self.build_tree_contents([('file2', b'file2 contents at rev 3\n')])
299
371
        self.wt.commit(
300
372
            message='Revision 3',
301
 
            timestamp=1144022400, # 2006-04-03 00:00:00 UTC
 
373
            timestamp=1144022400,  # 2006-04-03 00:00:00 UTC
302
374
            timezone=-3600,
303
 
            rev_id='rev-3')
 
375
            rev_id=b'rev-3')
304
376
        self.wt.remove(['file2'])
305
377
        self.wt.commit(
306
378
            message='Revision 4',
307
 
            timestamp=1144108800, # 2006-04-04 00:00:00 UTC
 
379
            timestamp=1144108800,  # 2006-04-04 00:00:00 UTC
308
380
            timezone=0,
309
 
            rev_id='rev-4')
 
381
            rev_id=b'rev-4')
310
382
        self.build_tree_contents([
311
 
            ('file1', 'file1 contents in working tree\n')
 
383
            ('file1', b'file1 contents in working tree\n')
312
384
            ])
313
385
        # set the date stamps for files in the working tree to known values
314
 
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
 
386
        os.utime('file1', (1144195200, 1144195200))  # 2006-04-05 00:00:00 UTC
315
387
 
316
388
    def test_diff_rev_tree_working_tree(self):
317
 
        output = self.get_diff(self.wt.basis_tree(), self.wt)
 
389
        output = get_diff_as_string(self.wt.basis_tree(), self.wt)
318
390
        # note that the date for old/file1 is from rev 2 rather than from
319
391
        # the basis revision (rev 4)
320
 
        self.assertEqualDiff(output, '''\
 
392
        self.assertEqualDiff(output, b'''\
321
393
=== modified file 'file1'
322
394
--- old/file1\t2006-04-02 00:00:00 +0000
323
395
+++ new/file1\t2006-04-05 00:00:00 +0000
328
400
''')
329
401
 
330
402
    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)
334
 
        self.assertEqualDiff(output, '''\
 
403
        tree1 = self.b.repository.revision_tree(b'rev-2')
 
404
        tree2 = self.b.repository.revision_tree(b'rev-3')
 
405
        output = get_diff_as_string(tree1, tree2)
 
406
        self.assertEqualDiff(output, b'''\
335
407
=== modified file 'file2'
336
408
--- old/file2\t2006-04-01 00:00:00 +0000
337
409
+++ new/file2\t2006-04-03 00:00:00 +0000
343
415
 
344
416
    def test_diff_add_files(self):
345
417
        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)
 
418
        tree2 = self.b.repository.revision_tree(b'rev-1')
 
419
        output = get_diff_as_string(tree1, tree2)
348
420
        # the files have the epoch time stamp for the tree in which
349
421
        # they don't exist.
350
 
        self.assertEqualDiff(output, '''\
 
422
        self.assertEqualDiff(output, b'''\
351
423
=== added file 'file1'
352
424
--- old/file1\t1970-01-01 00:00:00 +0000
353
425
+++ new/file1\t2006-04-01 00:00:00 +0000
363
435
''')
364
436
 
365
437
    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)
 
438
        tree1 = self.b.repository.revision_tree(b'rev-3')
 
439
        tree2 = self.b.repository.revision_tree(b'rev-4')
 
440
        output = get_diff_as_string(tree1, tree2)
369
441
        # the file has the epoch time stamp for the tree in which
370
442
        # it doesn't exist.
371
 
        self.assertEqualDiff(output, '''\
 
443
        self.assertEqualDiff(output, b'''\
372
444
=== removed file 'file2'
373
445
--- old/file2\t2006-04-03 00:00:00 +0000
374
446
+++ new/file2\t1970-01-01 00:00:00 +0000
380
452
    def test_show_diff_specified(self):
381
453
        """A working tree filename can be used to identify a file"""
382
454
        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'],
386
 
                            working_tree=self.wt)
387
 
        self.assertContainsRe(out, 'file1\t')
 
455
        old_tree = self.b.repository.revision_tree(b'rev-1')
 
456
        new_tree = self.b.repository.revision_tree(b'rev-4')
 
457
        out = get_diff_as_string(old_tree, new_tree, specific_files=['file1b'],
 
458
                                 working_tree=self.wt)
 
459
        self.assertContainsRe(out, b'file1\t')
388
460
 
389
461
    def test_recursive_diff(self):
390
462
        """Children of directories are matched"""
392
464
        os.mkdir('dir2')
393
465
        self.wt.add(['dir1', 'dir2'])
394
466
        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):
 
467
        old_tree = self.b.repository.revision_tree(b'rev-1')
 
468
        new_tree = self.b.repository.revision_tree(b'rev-4')
 
469
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
 
470
                                 working_tree=self.wt)
 
471
        self.assertContainsRe(out, b'file1\t')
 
472
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
 
473
                                 working_tree=self.wt)
 
474
        self.assertNotContainsRe(out, b'file1\t')
 
475
 
 
476
 
 
477
class TestShowDiffTrees(tests.TestCaseWithTransport):
407
478
    """Direct tests for show_diff_trees"""
408
479
 
409
480
    def test_modified_file(self):
410
481
        """Test when a file is modified."""
411
482
        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')
 
483
        self.build_tree_contents([('tree/file', b'contents\n')])
 
484
        tree.add(['file'], [b'file-id'])
 
485
        tree.commit('one', rev_id=b'rev-1')
415
486
 
416
 
        self.build_tree_contents([('tree/file', 'new contents\n')])
417
 
        d = self.get_diff(tree.basis_tree(), tree)
418
 
        self.assertContainsRe(d, "=== modified file 'file'\n")
419
 
        self.assertContainsRe(d, '--- old/file\t')
420
 
        self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
421
 
        self.assertContainsRe(d, '-contents\n'
422
 
                                 '\\+new contents\n')
 
487
        self.build_tree_contents([('tree/file', b'new contents\n')])
 
488
        d = get_diff_as_string(tree.basis_tree(), tree)
 
489
        self.assertContainsRe(d, b"=== modified file 'file'\n")
 
490
        self.assertContainsRe(d, b'--- old/file\t')
 
491
        self.assertContainsRe(d, b'\\+\\+\\+ new/file\t')
 
492
        self.assertContainsRe(d, b'-contents\n'
 
493
                                 b'\\+new contents\n')
423
494
 
424
495
    def test_modified_file_in_renamed_dir(self):
425
496
        """Test when a file is modified in a renamed directory."""
426
497
        tree = self.make_branch_and_tree('tree')
427
498
        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')
 
499
        self.build_tree_contents([('tree/dir/file', b'contents\n')])
 
500
        tree.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
 
501
        tree.commit('one', rev_id=b'rev-1')
431
502
 
432
503
        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)
435
 
        self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
436
 
        self.assertContainsRe(d, "=== modified file 'other/file'\n")
 
504
        self.build_tree_contents([('tree/other/file', b'new contents\n')])
 
505
        d = get_diff_as_string(tree.basis_tree(), tree)
 
506
        self.assertContainsRe(d, b"=== renamed directory 'dir' => 'other'\n")
 
507
        self.assertContainsRe(d, b"=== modified file 'other/file'\n")
437
508
        # XXX: This is technically incorrect, because it used to be at another
438
509
        # location. What to do?
439
 
        self.assertContainsRe(d, '--- old/dir/file\t')
440
 
        self.assertContainsRe(d, '\\+\\+\\+ new/other/file\t')
441
 
        self.assertContainsRe(d, '-contents\n'
442
 
                                 '\\+new contents\n')
 
510
        self.assertContainsRe(d, b'--- old/dir/file\t')
 
511
        self.assertContainsRe(d, b'\\+\\+\\+ new/other/file\t')
 
512
        self.assertContainsRe(d, b'-contents\n'
 
513
                                 b'\\+new contents\n')
443
514
 
444
515
    def test_renamed_directory(self):
445
516
        """Test when only a directory is only renamed."""
446
517
        tree = self.make_branch_and_tree('tree')
447
518
        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')
 
519
        self.build_tree_contents([('tree/dir/file', b'contents\n')])
 
520
        tree.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
 
521
        tree.commit('one', rev_id=b'rev-1')
451
522
 
452
523
        tree.rename_one('dir', 'newdir')
453
 
        d = self.get_diff(tree.basis_tree(), tree)
 
524
        d = get_diff_as_string(tree.basis_tree(), tree)
454
525
        # Renaming a directory should be a single "you renamed this dir" even
455
526
        # when there are files inside.
456
 
        self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
 
527
        self.assertEqual(d, b"=== renamed directory 'dir' => 'newdir'\n")
457
528
 
458
529
    def test_renamed_file(self):
459
530
        """Test when a file is only renamed."""
460
531
        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')
 
532
        self.build_tree_contents([('tree/file', b'contents\n')])
 
533
        tree.add(['file'], [b'file-id'])
 
534
        tree.commit('one', rev_id=b'rev-1')
464
535
 
465
536
        tree.rename_one('file', 'newname')
466
 
        d = self.get_diff(tree.basis_tree(), tree)
467
 
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
 
537
        d = get_diff_as_string(tree.basis_tree(), tree)
 
538
        self.assertContainsRe(d, b"=== renamed file 'file' => 'newname'\n")
468
539
        # We shouldn't have a --- or +++ line, because there is no content
469
540
        # change
470
 
        self.assertNotContainsRe(d, '---')
 
541
        self.assertNotContainsRe(d, b'---')
471
542
 
472
543
    def test_renamed_and_modified_file(self):
473
544
        """Test when a file is only renamed."""
474
545
        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')
 
546
        self.build_tree_contents([('tree/file', b'contents\n')])
 
547
        tree.add(['file'], [b'file-id'])
 
548
        tree.commit('one', rev_id=b'rev-1')
478
549
 
479
550
        tree.rename_one('file', 'newname')
480
 
        self.build_tree_contents([('tree/newname', 'new contents\n')])
481
 
        d = self.get_diff(tree.basis_tree(), tree)
482
 
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
483
 
        self.assertContainsRe(d, '--- old/file\t')
484
 
        self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
485
 
        self.assertContainsRe(d, '-contents\n'
486
 
                                 '\\+new contents\n')
487
 
 
 
551
        self.build_tree_contents([('tree/newname', b'new contents\n')])
 
552
        d = get_diff_as_string(tree.basis_tree(), tree)
 
553
        self.assertContainsRe(d, b"=== renamed file 'file' => 'newname'\n")
 
554
        self.assertContainsRe(d, b'--- old/file\t')
 
555
        self.assertContainsRe(d, b'\\+\\+\\+ new/newname\t')
 
556
        self.assertContainsRe(d, b'-contents\n'
 
557
                                 b'\\+new contents\n')
488
558
 
489
559
    def test_internal_diff_exec_property(self):
490
560
        tree = self.make_branch_and_tree('tree')
491
561
 
492
 
        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)
 
562
        tt = tree.transform()
 
563
        tt.new_file('a', tt.root, [b'contents\n'], b'a-id', True)
 
564
        tt.new_file('b', tt.root, [b'contents\n'], b'b-id', False)
 
565
        tt.new_file('c', tt.root, [b'contents\n'], b'c-id', True)
 
566
        tt.new_file('d', tt.root, [b'contents\n'], b'd-id', False)
 
567
        tt.new_file('e', tt.root, [b'contents\n'], b'control-e-id', True)
 
568
        tt.new_file('f', tt.root, [b'contents\n'], b'control-f-id', False)
499
569
        tt.apply()
500
 
        tree.commit('one', rev_id='rev-1')
 
570
        tree.commit('one', rev_id=b'rev-1')
501
571
 
502
 
        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'))
 
572
        tt = tree.transform()
 
573
        tt.set_executability(False, tt.trans_id_file_id(b'a-id'))
 
574
        tt.set_executability(True, tt.trans_id_file_id(b'b-id'))
 
575
        tt.set_executability(False, tt.trans_id_file_id(b'c-id'))
 
576
        tt.set_executability(True, tt.trans_id_file_id(b'd-id'))
507
577
        tt.apply()
508
578
        tree.rename_one('c', 'new-c')
509
579
        tree.rename_one('d', 'new-d')
510
580
 
511
 
        d = self.get_diff(tree.basis_tree(), tree)
512
 
 
513
 
        self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
514
 
                                  ".*\+x to -x.*\)")
515
 
        self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
516
 
                                  ".*-x to \+x.*\)")
517
 
        self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
518
 
                                  ".*\+x to -x.*\)")
519
 
        self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
520
 
                                  ".*-x to \+x.*\)")
521
 
        self.assertNotContainsRe(d, r"file 'e'")
522
 
        self.assertNotContainsRe(d, r"file 'f'")
523
 
 
 
581
        d = get_diff_as_string(tree.basis_tree(), tree)
 
582
 
 
583
        self.assertContainsRe(d, br"file 'a'.*\(properties changed:"
 
584
                                 br".*\+x to -x.*\)")
 
585
        self.assertContainsRe(d, br"file 'b'.*\(properties changed:"
 
586
                                 br".*-x to \+x.*\)")
 
587
        self.assertContainsRe(d, br"file 'c'.*\(properties changed:"
 
588
                                 br".*\+x to -x.*\)")
 
589
        self.assertContainsRe(d, br"file 'd'.*\(properties changed:"
 
590
                                 br".*-x to \+x.*\)")
 
591
        self.assertNotContainsRe(d, br"file 'e'")
 
592
        self.assertNotContainsRe(d, br"file 'f'")
524
593
 
525
594
    def test_binary_unicode_filenames(self):
526
595
        """Test that contents of files are *not* encoded in UTF-8 when there
527
596
        is a binary file in the diff.
528
597
        """
529
598
        # See https://bugs.launchpad.net/bugs/110092.
530
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
599
        self.requireFeature(features.UnicodeFilenameFeature)
531
600
 
532
 
        # This bug isn't triggered with cStringIO.
533
 
        from StringIO import StringIO
534
601
        tree = self.make_branch_and_tree('tree')
535
602
        alpha, omega = u'\u03b1', u'\u03c9'
536
603
        alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
537
604
        self.build_tree_contents(
538
 
            [('tree/' + alpha, chr(0)),
 
605
            [('tree/' + alpha, b'\0'),
539
606
             ('tree/' + omega,
540
 
              ('The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
541
 
        tree.add([alpha], ['file-id'])
542
 
        tree.add([omega], ['file-id-2'])
543
 
        diff_content = StringIO()
 
607
              (b'The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
 
608
        tree.add([alpha])
 
609
        tree.add([omega])
 
610
        diff_content = StubO()
544
611
        diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
545
 
        d = diff_content.getvalue()
546
 
        self.assertContainsRe(d, r"=== added file '%s'" % alpha_utf8)
547
 
        self.assertContainsRe(d, "Binary files a/%s.*and b/%s.* differ\n"
 
612
        diff_content.check_types(self, bytes)
 
613
        d = b''.join(diff_content.write_record)
 
614
        self.assertContainsRe(d, br"=== added file '%s'" % alpha_utf8)
 
615
        self.assertContainsRe(d, b"Binary files a/%s.*and b/%s.* differ\n"
548
616
                              % (alpha_utf8, alpha_utf8))
549
 
        self.assertContainsRe(d, r"=== added file '%s'" % omega_utf8)
550
 
        self.assertContainsRe(d, r"--- a/%s" % (omega_utf8,))
551
 
        self.assertContainsRe(d, r"\+\+\+ b/%s" % (omega_utf8,))
 
617
        self.assertContainsRe(d, br"=== added file '%s'" % omega_utf8)
 
618
        self.assertContainsRe(d, br"--- a/%s" % (omega_utf8,))
 
619
        self.assertContainsRe(d, br"\+\+\+ b/%s" % (omega_utf8,))
552
620
 
553
621
    def test_unicode_filename(self):
554
622
        """Test when the filename are unicode."""
555
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
623
        self.requireFeature(features.UnicodeFilenameFeature)
556
624
 
557
625
        alpha, omega = u'\u03b1', u'\u03c9'
558
626
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
559
627
 
560
628
        tree = self.make_branch_and_tree('tree')
561
 
        self.build_tree_contents([('tree/ren_'+alpha, 'contents\n')])
562
 
        tree.add(['ren_'+alpha], ['file-id-2'])
563
 
        self.build_tree_contents([('tree/del_'+alpha, 'contents\n')])
564
 
        tree.add(['del_'+alpha], ['file-id-3'])
565
 
        self.build_tree_contents([('tree/mod_'+alpha, 'contents\n')])
566
 
        tree.add(['mod_'+alpha], ['file-id-4'])
567
 
 
568
 
        tree.commit('one', rev_id='rev-1')
569
 
 
570
 
        tree.rename_one('ren_'+alpha, 'ren_'+omega)
571
 
        tree.remove('del_'+alpha)
572
 
        self.build_tree_contents([('tree/add_'+alpha, 'contents\n')])
573
 
        tree.add(['add_'+alpha], ['file-id'])
574
 
        self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
575
 
 
576
 
        d = self.get_diff(tree.basis_tree(), tree)
 
629
        self.build_tree_contents([('tree/ren_' + alpha, b'contents\n')])
 
630
        tree.add(['ren_' + alpha], [b'file-id-2'])
 
631
        self.build_tree_contents([('tree/del_' + alpha, b'contents\n')])
 
632
        tree.add(['del_' + alpha], [b'file-id-3'])
 
633
        self.build_tree_contents([('tree/mod_' + alpha, b'contents\n')])
 
634
        tree.add(['mod_' + alpha], [b'file-id-4'])
 
635
 
 
636
        tree.commit('one', rev_id=b'rev-1')
 
637
 
 
638
        tree.rename_one('ren_' + alpha, 'ren_' + omega)
 
639
        tree.remove('del_' + alpha)
 
640
        self.build_tree_contents([('tree/add_' + alpha, b'contents\n')])
 
641
        tree.add(['add_' + alpha], [b'file-id'])
 
642
        self.build_tree_contents([('tree/mod_' + alpha, b'contents_mod\n')])
 
643
 
 
644
        d = get_diff_as_string(tree.basis_tree(), tree)
577
645
        self.assertContainsRe(d,
578
 
                "=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
579
 
        self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
580
 
        self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
581
 
        self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
 
646
                              b"=== renamed file 'ren_%s' => 'ren_%s'\n" % (autf8, outf8))
 
647
        self.assertContainsRe(d, b"=== added file 'add_%s'" % autf8)
 
648
        self.assertContainsRe(d, b"=== modified file 'mod_%s'" % autf8)
 
649
        self.assertContainsRe(d, b"=== removed file 'del_%s'" % autf8)
 
650
 
 
651
    def test_unicode_filename_path_encoding(self):
 
652
        """Test for bug #382699: unicode filenames on Windows should be shown
 
653
        in user encoding.
 
654
        """
 
655
        self.requireFeature(features.UnicodeFilenameFeature)
 
656
        # The word 'test' in Russian
 
657
        _russian_test = u'\u0422\u0435\u0441\u0442'
 
658
        directory = _russian_test + u'/'
 
659
        test_txt = _russian_test + u'.txt'
 
660
        u1234 = u'\u1234.txt'
 
661
 
 
662
        tree = self.make_branch_and_tree('.')
 
663
        self.build_tree_contents([
 
664
            (test_txt, b'foo\n'),
 
665
            (u1234, b'foo\n'),
 
666
            (directory, None),
 
667
            ])
 
668
        tree.add([test_txt, u1234, directory])
 
669
 
 
670
        sio = BytesIO()
 
671
        diff.show_diff_trees(tree.basis_tree(), tree, sio,
 
672
                             path_encoding='cp1251')
 
673
 
 
674
        output = subst_dates(sio.getvalue())
 
675
        shouldbe = (b'''\
 
676
=== added directory '%(directory)s'
 
677
=== added file '%(test_txt)s'
 
678
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
679
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
680
@@ -0,0 +1,1 @@
 
681
+foo
 
682
 
 
683
=== added file '?.txt'
 
684
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
685
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
686
@@ -0,0 +1,1 @@
 
687
+foo
 
688
 
 
689
''' % {b'directory': _russian_test.encode('cp1251'),
 
690
            b'test_txt': test_txt.encode('cp1251'),
 
691
       })
 
692
        self.assertEqualDiff(output, shouldbe)
582
693
 
583
694
 
584
695
class DiffWasIs(diff.DiffPath):
585
696
 
586
 
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
587
 
        self.to_file.write('was: ')
588
 
        self.to_file.write(self.old_tree.get_file(file_id).read())
589
 
        self.to_file.write('is: ')
590
 
        self.to_file.write(self.new_tree.get_file(file_id).read())
591
 
        pass
 
697
    def diff(self, old_path, new_path, old_kind, new_kind):
 
698
        self.to_file.write(b'was: ')
 
699
        self.to_file.write(self.old_tree.get_file(old_path).read())
 
700
        self.to_file.write(b'is: ')
 
701
        self.to_file.write(self.new_tree.get_file(new_path).read())
592
702
 
593
703
 
594
704
class TestDiffTree(tests.TestCaseWithTransport):
601
711
        self.new_tree = self.make_branch_and_tree('new-tree')
602
712
        self.new_tree.lock_write()
603
713
        self.addCleanup(self.new_tree.unlock)
604
 
        self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
 
714
        self.differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
605
715
 
606
716
    def test_diff_text(self):
607
717
        self.build_tree_contents([('old-tree/olddir/',),
608
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
718
                                  ('old-tree/olddir/oldfile', b'old\n')])
609
719
        self.old_tree.add('olddir')
610
 
        self.old_tree.add('olddir/oldfile', 'file-id')
 
720
        self.old_tree.add('olddir/oldfile', b'file-id')
611
721
        self.build_tree_contents([('new-tree/newdir/',),
612
 
                                  ('new-tree/newdir/newfile', 'new\n')])
 
722
                                  ('new-tree/newdir/newfile', b'new\n')])
613
723
        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')
617
 
        self.assertEqual(
618
 
            '--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
619
 
            differ.to_file.getvalue())
620
 
        differ.to_file.seek(0)
621
 
        differ.diff_text(None, 'file-id', 'old label', 'new label')
622
 
        self.assertEqual(
623
 
            '--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
624
 
            differ.to_file.getvalue())
625
 
        differ.to_file.seek(0)
626
 
        differ.diff_text('file-id', 'file-id', 'old label', 'new label')
627
 
        self.assertEqual(
628
 
            '--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
 
724
        self.new_tree.add('newdir/newfile', b'file-id')
 
725
        differ = diff.DiffText(self.old_tree, self.new_tree, BytesIO())
 
726
        differ.diff_text('olddir/oldfile', None, 'old label', 'new label')
 
727
        self.assertEqual(
 
728
            b'--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
 
729
            differ.to_file.getvalue())
 
730
        differ.to_file.seek(0)
 
731
        differ.diff_text(None, 'newdir/newfile',
 
732
                         'old label', 'new label')
 
733
        self.assertEqual(
 
734
            b'--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
 
735
            differ.to_file.getvalue())
 
736
        differ.to_file.seek(0)
 
737
        differ.diff_text('olddir/oldfile', 'newdir/newfile',
 
738
                         'old label', 'new label')
 
739
        self.assertEqual(
 
740
            b'--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
629
741
            differ.to_file.getvalue())
630
742
 
631
743
    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')
 
744
        self.build_tree_contents([('old-tree/file', b'contents'),
 
745
                                  ('new-tree/file', b'contents')])
 
746
        self.old_tree.add('file', b'file-id')
 
747
        self.new_tree.add('file', b'file-id')
636
748
        os.unlink('new-tree/file')
637
749
        self.differ.show_diff(None)
638
 
        self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
 
750
        self.assertContainsRe(self.differ.to_file.getvalue(), b'-contents')
639
751
 
640
752
    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')
 
753
        self.build_tree_contents([('old-tree/file', b'contents'),
 
754
                                  ('new-tree/file', b'contents')])
 
755
        self.old_tree.add('file', b'file-id')
 
756
        self.new_tree.add('file', b'file-id')
645
757
        os.unlink('old-tree/file')
646
758
        self.differ.show_diff(None)
647
 
        self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
 
759
        self.assertContainsRe(self.differ.to_file.getvalue(), br'\+contents')
648
760
 
649
761
    def test_diff_symlink(self):
650
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
762
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
651
763
        differ.diff_symlink('old target', None)
652
 
        self.assertEqual("=== target was 'old target'\n",
 
764
        self.assertEqual(b"=== target was 'old target'\n",
653
765
                         differ.to_file.getvalue())
654
766
 
655
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
767
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
656
768
        differ.diff_symlink(None, 'new target')
657
 
        self.assertEqual("=== target is 'new target'\n",
 
769
        self.assertEqual(b"=== target is 'new target'\n",
658
770
                         differ.to_file.getvalue())
659
771
 
660
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
 
772
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
661
773
        differ.diff_symlink('old target', 'new target')
662
 
        self.assertEqual("=== target changed 'old target' => 'new target'\n",
 
774
        self.assertEqual(b"=== target changed 'old target' => 'new target'\n",
663
775
                         differ.to_file.getvalue())
664
776
 
665
777
    def test_diff(self):
666
778
        self.build_tree_contents([('old-tree/olddir/',),
667
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
779
                                  ('old-tree/olddir/oldfile', b'old\n')])
668
780
        self.old_tree.add('olddir')
669
 
        self.old_tree.add('olddir/oldfile', 'file-id')
 
781
        self.old_tree.add('olddir/oldfile', b'file-id')
670
782
        self.build_tree_contents([('new-tree/newdir/',),
671
 
                                  ('new-tree/newdir/newfile', 'new\n')])
 
783
                                  ('new-tree/newdir/newfile', b'new\n')])
672
784
        self.new_tree.add('newdir')
673
 
        self.new_tree.add('newdir/newfile', 'file-id')
674
 
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
785
        self.new_tree.add('newdir/newfile', b'file-id')
 
786
        self.differ.diff('olddir/oldfile', 'newdir/newfile')
675
787
        self.assertContainsRe(
676
788
            self.differ.to_file.getvalue(),
677
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
678
 
             ' \@\@\n-old\n\+new\n\n')
 
789
            br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
790
            br' \@\@\n-old\n\+new\n\n')
679
791
 
680
792
    def test_diff_kind_change(self):
681
 
        self.requireFeature(tests.SymlinkFeature)
 
793
        self.requireFeature(features.SymlinkFeature)
682
794
        self.build_tree_contents([('old-tree/olddir/',),
683
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
 
795
                                  ('old-tree/olddir/oldfile', b'old\n')])
684
796
        self.old_tree.add('olddir')
685
 
        self.old_tree.add('olddir/oldfile', 'file-id')
 
797
        self.old_tree.add('olddir/oldfile', b'file-id')
686
798
        self.build_tree(['new-tree/newdir/'])
687
799
        os.symlink('new', 'new-tree/newdir/newfile')
688
800
        self.new_tree.add('newdir')
689
 
        self.new_tree.add('newdir/newfile', 'file-id')
690
 
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
 
801
        self.new_tree.add('newdir/newfile', b'file-id')
 
802
        self.differ.diff('olddir/oldfile', 'newdir/newfile')
691
803
        self.assertContainsRe(
692
804
            self.differ.to_file.getvalue(),
693
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
694
 
             ' \@\@\n-old\n\n')
 
805
            br'--- olddir/oldfile.*\n'
 
806
            br'\+\+\+ newdir/newfile.*\n'
 
807
            br'\@\@ -1,1 \+0,0 \@\@\n'
 
808
            br'-old\n'
 
809
            br'\n')
695
810
        self.assertContainsRe(self.differ.to_file.getvalue(),
696
 
                              "=== target is u'new'\n")
 
811
                              b"=== target is '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(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()
716
831
        old_diff_factories = diff.DiffTree.diff_factories
717
 
        diff.DiffTree.diff_factories=old_diff_factories[:]
 
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('olddir/oldfile', 'newdir/newfile')
724
839
        self.assertNotContainsRe(
725
840
            differ.to_file.getvalue(),
726
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
727
 
             ' \@\@\n-old\n\+new\n\n')
 
841
            br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
842
            br' \@\@\n-old\n\+new\n\n')
728
843
        self.assertContainsRe(differ.to_file.getvalue(),
729
 
                              'was: old\nis: new\n')
 
844
                              b'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('olddir/oldfile', 'newdir/newfile')
736
851
        self.assertNotContainsRe(
737
852
            differ.to_file.getvalue(),
738
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
739
 
             ' \@\@\n-old\n\+new\n\n')
 
853
            br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
 
854
            br' \@\@\n-old\n\+new\n\n')
740
855
        self.assertContainsRe(differ.to_file.getvalue(),
741
 
                              'was: old\nis: new\n')
 
856
                              b'was: old\nis: new\n')
742
857
 
743
858
    def test_alphabetical_order(self):
744
859
        self.build_tree(['new-tree/a-file'])
747
862
        self.old_tree.add('b-file')
748
863
        self.differ.show_diff(None)
749
864
        self.assertContainsRe(self.differ.to_file.getvalue(),
750
 
            '.*a-file(.|\n)*b-file')
751
 
 
752
 
 
753
 
class TestPatienceDiffLib(tests.TestCase):
754
 
 
755
 
    def setUp(self):
756
 
        super(TestPatienceDiffLib, self).setUp()
757
 
        self._unique_lcs = _patiencediff_py.unique_lcs_py
758
 
        self._recurse_matches = _patiencediff_py.recurse_matches_py
759
 
        self._PatienceSequenceMatcher = \
760
 
            _patiencediff_py.PatienceSequenceMatcher_py
761
 
 
762
 
    def test_diff_unicode_string(self):
763
 
        a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
764
 
        b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
765
 
        sm = self._PatienceSequenceMatcher(None, a, b)
766
 
        mb = sm.get_matching_blocks()
767
 
        self.assertEquals(35, len(mb))
768
 
 
769
 
    def test_unique_lcs(self):
770
 
        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)])
782
 
 
783
 
    def test_recurse_matches(self):
784
 
        def test_one(a, b, matches):
785
 
            test_matches = []
786
 
            self._recurse_matches(
787
 
                a, b, 0, 0, len(a), len(b), test_matches, 10)
788
 
            self.assertEquals(test_matches, matches)
789
 
 
790
 
        test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
791
 
                 [(0, 0), (2, 2), (4, 4)])
792
 
        test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
793
 
                 [(0, 0), (2, 1), (4, 2)])
794
 
        # Even though 'bc' is not unique globally, and is surrounded by
795
 
        # non-matching lines, we should still match, because they are locally
796
 
        # unique
797
 
        test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
798
 
                                          (4, 6), (5, 7), (6, 8)])
799
 
 
800
 
        # recurse_matches doesn't match non-unique
801
 
        # lines surrounded by bogus text.
802
 
        # The update has been done in patiencediff.SequenceMatcher instead
803
 
 
804
 
        # This is what it could be
805
 
        #test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
806
 
 
807
 
        # This is what it currently gives:
808
 
        test_one('aBccDe', 'abccde', [(0,0), (5,5)])
809
 
 
810
 
    def assertDiffBlocks(self, a, b, expected_blocks):
811
 
        """Check that the sequence matcher returns the correct blocks.
812
 
 
813
 
        :param a: A sequence to match
814
 
        :param b: Another sequence to match
815
 
        :param expected_blocks: The expected output, not including the final
816
 
            matching block (len(a), len(b), 0)
817
 
        """
818
 
        matcher = self._PatienceSequenceMatcher(None, a, b)
819
 
        blocks = matcher.get_matching_blocks()
820
 
        last = blocks.pop()
821
 
        self.assertEqual((len(a), len(b), 0), last)
822
 
        self.assertEqual(expected_blocks, blocks)
823
 
 
824
 
    def test_matching_blocks(self):
825
 
        # Some basic matching tests
826
 
        self.assertDiffBlocks('', '', [])
827
 
        self.assertDiffBlocks([], [], [])
828
 
        self.assertDiffBlocks('abc', '', [])
829
 
        self.assertDiffBlocks('', 'abc', [])
830
 
        self.assertDiffBlocks('abcd', 'abcd', [(0, 0, 4)])
831
 
        self.assertDiffBlocks('abcd', 'abce', [(0, 0, 3)])
832
 
        self.assertDiffBlocks('eabc', 'abce', [(1, 0, 3)])
833
 
        self.assertDiffBlocks('eabce', 'abce', [(1, 0, 4)])
834
 
        self.assertDiffBlocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
835
 
        self.assertDiffBlocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
836
 
        self.assertDiffBlocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
837
 
        # This may check too much, but it checks to see that
838
 
        # a copied block stays attached to the previous section,
839
 
        # not the later one.
840
 
        # difflib would tend to grab the trailing longest match
841
 
        # which would make the diff not look right
842
 
        self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
843
 
                              [(0, 0, 6), (6, 11, 10)])
844
 
 
845
 
        # make sure it supports passing in lists
846
 
        self.assertDiffBlocks(
847
 
                   ['hello there\n',
848
 
                    'world\n',
849
 
                    'how are you today?\n'],
850
 
                   ['hello there\n',
851
 
                    'how are you today?\n'],
852
 
                [(0, 0, 1), (2, 1, 1)])
853
 
 
854
 
        # non unique lines surrounded by non-matching lines
855
 
        # won't be found
856
 
        self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
857
 
 
858
 
        # But they only need to be locally unique
859
 
        self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
860
 
 
861
 
        # non unique blocks won't be matched
862
 
        self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
863
 
 
864
 
        # 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)])
867
 
 
868
 
        self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
869
 
        self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
870
 
        self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
871
 
 
872
 
    def test_matching_blocks_tuples(self):
873
 
        # Some basic matching tests
874
 
        self.assertDiffBlocks([], [], [])
875
 
        self.assertDiffBlocks([('a',), ('b',), ('c,')], [], [])
876
 
        self.assertDiffBlocks([], [('a',), ('b',), ('c,')], [])
877
 
        self.assertDiffBlocks([('a',), ('b',), ('c,')],
878
 
                              [('a',), ('b',), ('c,')],
879
 
                              [(0, 0, 3)])
880
 
        self.assertDiffBlocks([('a',), ('b',), ('c,')],
881
 
                              [('a',), ('b',), ('d,')],
882
 
                              [(0, 0, 2)])
883
 
        self.assertDiffBlocks([('d',), ('b',), ('c,')],
884
 
                              [('a',), ('b',), ('c,')],
885
 
                              [(1, 1, 2)])
886
 
        self.assertDiffBlocks([('d',), ('a',), ('b',), ('c,')],
887
 
                              [('a',), ('b',), ('c,')],
888
 
                              [(1, 0, 3)])
889
 
        self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
890
 
                              [('a', 'b'), ('c', 'X'), ('e', 'f')],
891
 
                              [(0, 0, 1), (2, 2, 1)])
892
 
        self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
893
 
                              [('a', 'b'), ('c', 'dX'), ('e', 'f')],
894
 
                              [(0, 0, 1), (2, 2, 1)])
895
 
 
896
 
    def test_opcodes(self):
897
 
        def chk_ops(a, b, expected_codes):
898
 
            s = self._PatienceSequenceMatcher(None, a, b)
899
 
            self.assertEquals(expected_codes, s.get_opcodes())
900
 
 
901
 
        chk_ops('', '', [])
902
 
        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)
915
 
                                 ])
916
 
        chk_ops('abcde', 'abXde', [('equal',   0,2, 0,2),
917
 
                                   ('replace', 2,3, 2,3),
918
 
                                   ('equal',   3,5, 3,5)
919
 
                                  ])
920
 
        chk_ops('abcde', 'abXYZde', [('equal',   0,2, 0,2),
921
 
                                     ('replace', 2,3, 2,5),
922
 
                                     ('equal',   3,5, 5,7)
923
 
                                    ])
924
 
        chk_ops('abde', 'abXYZde', [('equal',  0,2, 0,2),
925
 
                                    ('insert', 2,2, 2,5),
926
 
                                    ('equal',  2,4, 5,7)
927
 
                                   ])
928
 
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
929
 
                [('equal',  0,6,  0,6),
930
 
                 ('insert', 6,6,  6,11),
931
 
                 ('equal',  6,16, 11,21)
932
 
                ])
933
 
        chk_ops(
934
 
                [ 'hello there\n'
935
 
                , 'world\n'
936
 
                , 'how are you today?\n'],
937
 
                [ 'hello there\n'
938
 
                , 'how are you today?\n'],
939
 
                [('equal',  0,1, 0,1),
940
 
                 ('delete', 1,2, 1,1),
941
 
                 ('equal',  2,3, 1,2),
942
 
                ])
943
 
        chk_ops('aBccDe', 'abccde',
944
 
                [('equal',   0,1, 0,1),
945
 
                 ('replace', 1,5, 1,5),
946
 
                 ('equal',   5,6, 5,6),
947
 
                ])
948
 
        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),
954
 
                ])
955
 
        chk_ops('aBcdEcdFg', 'abcdecdfg',
956
 
                [('equal',   0,1, 0,1),
957
 
                 ('replace', 1,8, 1,8),
958
 
                 ('equal',   8,9, 8,9)
959
 
                ])
960
 
        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)
970
 
                ])
971
 
 
972
 
    def test_grouped_opcodes(self):
973
 
        def chk_ops(a, b, expected_codes, n=3):
974
 
            s = self._PatienceSequenceMatcher(None, a, b)
975
 
            self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
976
 
 
977
 
        chk_ops('', '', [])
978
 
        chk_ops([], [], [])
979
 
        chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
980
 
        chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
981
 
        chk_ops('abcd', 'abcd', [])
982
 
        chk_ops('abcd', 'abce', [[('equal',   0,3, 0,3),
983
 
                                  ('replace', 3,4, 3,4)
984
 
                                 ]])
985
 
        chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
986
 
                                 ('equal',  1,4, 0,3),
987
 
                                 ('insert', 4,4, 3,4)
988
 
                                ]])
989
 
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
990
 
                [[('equal',  3,6, 3,6),
991
 
                  ('insert', 6,6, 6,11),
992
 
                  ('equal',  6,9, 11,14)
993
 
                  ]])
994
 
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
995
 
                [[('equal',  2,6, 2,6),
996
 
                  ('insert', 6,6, 6,11),
997
 
                  ('equal',  6,10, 11,15)
998
 
                  ]], 4)
999
 
        chk_ops('Xabcdef', 'abcdef',
1000
 
                [[('delete', 0,1, 0,0),
1001
 
                  ('equal',  1,4, 0,3)
1002
 
                  ]])
1003
 
        chk_ops('abcdef', 'abcdefX',
1004
 
                [[('equal',  3,6, 3,6),
1005
 
                  ('insert', 6,6, 6,7)
1006
 
                  ]])
1007
 
 
1008
 
 
1009
 
    def test_multiple_ranges(self):
1010
 
        # There was an earlier bug where we used a bad set of ranges,
1011
 
        # this triggers that specific bug, to make sure it doesn't regress
1012
 
        self.assertDiffBlocks('abcdefghijklmnop',
1013
 
                              'abcXghiYZQRSTUVWXYZijklmnop',
1014
 
                              [(0, 0, 3), (6, 4, 3), (9, 20, 7)])
1015
 
 
1016
 
        self.assertDiffBlocks('ABCd efghIjk  L',
1017
 
                              'AxyzBCn mo pqrstuvwI1 2  L',
1018
 
                              [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1019
 
 
1020
 
        # These are rot13 code snippets.
1021
 
        self.assertDiffBlocks('''\
1022
 
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1023
 
    """
1024
 
    gnxrf_netf = ['svyr*']
1025
 
    gnxrf_bcgvbaf = ['ab-erphefr']
1026
 
 
1027
 
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
1028
 
        sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
1029
 
        vs vf_dhvrg():
1030
 
            ercbegre = nqq_ercbegre_ahyy
1031
 
        ryfr:
1032
 
            ercbegre = nqq_ercbegre_cevag
1033
 
        fzneg_nqq(svyr_yvfg, abg ab_erphefr, ercbegre)
1034
 
 
1035
 
 
1036
 
pynff pzq_zxqve(Pbzznaq):
1037
 
'''.splitlines(True), '''\
1038
 
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1039
 
 
1040
 
    --qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl
1041
 
    nqq gurz.
1042
 
    """
1043
 
    gnxrf_netf = ['svyr*']
1044
 
    gnxrf_bcgvbaf = ['ab-erphefr', 'qel-eha']
1045
 
 
1046
 
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr, qel_eha=Snyfr):
1047
 
        vzcbeg omeyvo.nqq
1048
 
 
1049
 
        vs qel_eha:
1050
 
            vs vf_dhvrg():
1051
 
                # Guvf vf cbvagyrff, ohg V'q engure abg envfr na reebe
1052
 
                npgvba = omeyvo.nqq.nqq_npgvba_ahyy
1053
 
            ryfr:
1054
 
  npgvba = omeyvo.nqq.nqq_npgvba_cevag
1055
 
        ryvs vf_dhvrg():
1056
 
            npgvba = omeyvo.nqq.nqq_npgvba_nqq
1057
 
        ryfr:
1058
 
       npgvba = omeyvo.nqq.nqq_npgvba_nqq_naq_cevag
1059
 
 
1060
 
        omeyvo.nqq.fzneg_nqq(svyr_yvfg, abg ab_erphefr, npgvba)
1061
 
 
1062
 
 
1063
 
pynff pzq_zxqve(Pbzznaq):
1064
 
'''.splitlines(True)
1065
 
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1066
 
 
1067
 
    def test_patience_unified_diff(self):
1068
 
        txt_a = ['hello there\n',
1069
 
                 'world\n',
1070
 
                 'how are you today?\n']
1071
 
        txt_b = ['hello there\n',
1072
 
                 'how are you today?\n']
1073
 
        unified_diff = patiencediff.unified_diff
1074
 
        psm = self._PatienceSequenceMatcher
1075
 
        self.assertEquals(['--- \n',
1076
 
                           '+++ \n',
1077
 
                           '@@ -1,3 +1,2 @@\n',
1078
 
                           ' hello there\n',
1079
 
                           '-world\n',
1080
 
                           ' how are you today?\n'
1081
 
                          ]
1082
 
                          , list(unified_diff(txt_a, txt_b,
1083
 
                                 sequencematcher=psm)))
1084
 
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1085
 
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1086
 
        # This is the result with LongestCommonSubstring matching
1087
 
        self.assertEquals(['--- \n',
1088
 
                           '+++ \n',
1089
 
                           '@@ -1,6 +1,11 @@\n',
1090
 
                           ' a\n',
1091
 
                           ' b\n',
1092
 
                           ' c\n',
1093
 
                           '+d\n',
1094
 
                           '+e\n',
1095
 
                           '+f\n',
1096
 
                           '+x\n',
1097
 
                           '+y\n',
1098
 
                           ' d\n',
1099
 
                           ' e\n',
1100
 
                           ' f\n']
1101
 
                          , list(unified_diff(txt_a, txt_b)))
1102
 
        # And the patience diff
1103
 
        self.assertEquals(['--- \n',
1104
 
                           '+++ \n',
1105
 
                           '@@ -4,6 +4,11 @@\n',
1106
 
                           ' d\n',
1107
 
                           ' e\n',
1108
 
                           ' f\n',
1109
 
                           '+x\n',
1110
 
                           '+y\n',
1111
 
                           '+d\n',
1112
 
                           '+e\n',
1113
 
                           '+f\n',
1114
 
                           ' g\n',
1115
 
                           ' h\n',
1116
 
                           ' i\n',
1117
 
                          ]
1118
 
                          , list(unified_diff(txt_a, txt_b,
1119
 
                                 sequencematcher=psm)))
1120
 
 
1121
 
    def test_patience_unified_diff_with_dates(self):
1122
 
        txt_a = ['hello there\n',
1123
 
                 'world\n',
1124
 
                 'how are you today?\n']
1125
 
        txt_b = ['hello there\n',
1126
 
                 'how are you today?\n']
1127
 
        unified_diff = patiencediff.unified_diff
1128
 
        psm = self._PatienceSequenceMatcher
1129
 
        self.assertEquals(['--- a\t2008-08-08\n',
1130
 
                           '+++ b\t2008-09-09\n',
1131
 
                           '@@ -1,3 +1,2 @@\n',
1132
 
                           ' hello there\n',
1133
 
                           '-world\n',
1134
 
                           ' how are you today?\n'
1135
 
                          ]
1136
 
                          , list(unified_diff(txt_a, txt_b,
1137
 
                                 fromfile='a', tofile='b',
1138
 
                                 fromfiledate='2008-08-08',
1139
 
                                 tofiledate='2008-09-09',
1140
 
                                 sequencematcher=psm)))
1141
 
 
1142
 
 
1143
 
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1144
 
 
1145
 
    _test_needs_features = [compiled_patiencediff_feature]
1146
 
 
1147
 
    def setUp(self):
1148
 
        super(TestPatienceDiffLib_c, self).setUp()
1149
 
        from bzrlib import _patiencediff_c
1150
 
        self._unique_lcs = _patiencediff_c.unique_lcs_c
1151
 
        self._recurse_matches = _patiencediff_c.recurse_matches_c
1152
 
        self._PatienceSequenceMatcher = \
1153
 
            _patiencediff_c.PatienceSequenceMatcher_c
1154
 
 
1155
 
    def test_unhashable(self):
1156
 
        """We should get a proper exception here."""
1157
 
        # We need to be able to hash items in the sequence, lists are
1158
 
        # unhashable, and thus cannot be diffed
1159
 
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1160
 
                                         None, [[]], [])
1161
 
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1162
 
                                         None, ['valid', []], [])
1163
 
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1164
 
                                         None, ['valid'], [[]])
1165
 
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1166
 
                                         None, ['valid'], ['valid', []])
1167
 
 
1168
 
 
1169
 
class TestPatienceDiffLibFiles(tests.TestCaseInTempDir):
1170
 
 
1171
 
    def setUp(self):
1172
 
        super(TestPatienceDiffLibFiles, self).setUp()
1173
 
        self._PatienceSequenceMatcher = \
1174
 
            _patiencediff_py.PatienceSequenceMatcher_py
1175
 
 
1176
 
    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)
1184
 
 
1185
 
        unified_diff_files = patiencediff.unified_diff_files
1186
 
        psm = self._PatienceSequenceMatcher
1187
 
        self.assertEquals(['--- a1\n',
1188
 
                           '+++ b1\n',
1189
 
                           '@@ -1,3 +1,2 @@\n',
1190
 
                           ' hello there\n',
1191
 
                           '-world\n',
1192
 
                           ' how are you today?\n',
1193
 
                          ]
1194
 
                          , list(unified_diff_files('a1', 'b1',
1195
 
                                 sequencematcher=psm)))
1196
 
 
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)
1201
 
 
1202
 
        # This is the result with LongestCommonSubstring matching
1203
 
        self.assertEquals(['--- a2\n',
1204
 
                           '+++ b2\n',
1205
 
                           '@@ -1,6 +1,11 @@\n',
1206
 
                           ' a\n',
1207
 
                           ' b\n',
1208
 
                           ' c\n',
1209
 
                           '+d\n',
1210
 
                           '+e\n',
1211
 
                           '+f\n',
1212
 
                           '+x\n',
1213
 
                           '+y\n',
1214
 
                           ' d\n',
1215
 
                           ' e\n',
1216
 
                           ' f\n']
1217
 
                          , list(unified_diff_files('a2', 'b2')))
1218
 
 
1219
 
        # 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)))
1237
 
 
1238
 
 
1239
 
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1240
 
 
1241
 
    _test_needs_features = [compiled_patiencediff_feature]
1242
 
 
1243
 
    def setUp(self):
1244
 
        super(TestPatienceDiffLibFiles_c, self).setUp()
1245
 
        from bzrlib import _patiencediff_c
1246
 
        self._PatienceSequenceMatcher = \
1247
 
            _patiencediff_c.PatienceSequenceMatcher_c
1248
 
 
1249
 
 
1250
 
class TestUsingCompiledIfAvailable(tests.TestCase):
1251
 
 
1252
 
    def test_PatienceSequenceMatcher(self):
1253
 
        if compiled_patiencediff_feature.available():
1254
 
            from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1255
 
            self.assertIs(PatienceSequenceMatcher_c,
1256
 
                          patiencediff.PatienceSequenceMatcher)
1257
 
        else:
1258
 
            from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1259
 
            self.assertIs(PatienceSequenceMatcher_py,
1260
 
                          patiencediff.PatienceSequenceMatcher)
1261
 
 
1262
 
    def test_unique_lcs(self):
1263
 
        if compiled_patiencediff_feature.available():
1264
 
            from bzrlib._patiencediff_c import unique_lcs_c
1265
 
            self.assertIs(unique_lcs_c,
1266
 
                          patiencediff.unique_lcs)
1267
 
        else:
1268
 
            from bzrlib._patiencediff_py import unique_lcs_py
1269
 
            self.assertIs(unique_lcs_py,
1270
 
                          patiencediff.unique_lcs)
1271
 
 
1272
 
    def test_recurse_matches(self):
1273
 
        if compiled_patiencediff_feature.available():
1274
 
            from bzrlib._patiencediff_c import recurse_matches_c
1275
 
            self.assertIs(recurse_matches_c,
1276
 
                          patiencediff.recurse_matches)
1277
 
        else:
1278
 
            from bzrlib._patiencediff_py import recurse_matches_py
1279
 
            self.assertIs(recurse_matches_py,
1280
 
                          patiencediff.recurse_matches)
 
865
                              b'.*a-file(.|\n)*b-file')
1281
866
 
1282
867
 
1283
868
class TestDiffFromTool(tests.TestCaseWithTransport):
1284
869
 
1285
870
    def test_from_string(self):
1286
 
        diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
1287
 
        self.addCleanup(diff_obj.finish)
1288
 
        self.assertEqual(['diff', '@old_path', '@new_path'],
1289
 
            diff_obj.command_template)
 
871
        diff_obj = diff.DiffFromTool.from_string(
 
872
            ['diff', '{old_path}', '{new_path}'],
 
873
            None, None, None)
 
874
        self.addCleanup(diff_obj.finish)
 
875
        self.assertEqual(['diff', '{old_path}', '{new_path}'],
 
876
                         diff_obj.command_template)
 
877
 
 
878
    def test_from_string_no_paths(self):
 
879
        diff_obj = diff.DiffFromTool.from_string(
 
880
            ['diff', "-u5"], None, None, None)
 
881
        self.addCleanup(diff_obj.finish)
 
882
        self.assertEqual(['diff', '-u5'],
 
883
                         diff_obj.command_template)
 
884
        self.assertEqual(['diff', '-u5', 'old-path', 'new-path'],
 
885
                         diff_obj._get_command('old-path', 'new-path'))
1290
886
 
1291
887
    def test_from_string_u5(self):
1292
 
        diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
1293
 
                                                 None, None, None)
 
888
        diff_obj = diff.DiffFromTool.from_string(
 
889
            ['diff', "-u 5", '{old_path}', '{new_path}'], None, None, None)
1294
890
        self.addCleanup(diff_obj.finish)
1295
 
        self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
 
891
        self.assertEqual(['diff', '-u 5', '{old_path}', '{new_path}'],
1296
892
                         diff_obj.command_template)
1297
893
        self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
1298
894
                         diff_obj._get_command('old-path', 'new-path'))
1299
895
 
1300
896
    def test_from_string_path_with_backslashes(self):
1301
 
        self.requireFeature(test_win32utils.BackslashDirSeparatorFeature)
1302
 
        tool = 'C:\\Tools\\Diff.exe'
 
897
        self.requireFeature(features.backslashdir_feature)
 
898
        tool = ['C:\\Tools\\Diff.exe', '{old_path}', '{new_path}']
1303
899
        diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
1304
900
        self.addCleanup(diff_obj.finish)
1305
 
        self.assertEqual(['C:\\Tools\\Diff.exe', '@old_path', '@new_path'],
 
901
        self.assertEqual(['C:\\Tools\\Diff.exe', '{old_path}', '{new_path}'],
1306
902
                         diff_obj.command_template)
1307
903
        self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
1308
904
                         diff_obj._get_command('old-path', 'new-path'))
1309
905
 
1310
906
    def test_execute(self):
1311
 
        output = StringIO()
1312
 
        diff_obj = diff.DiffFromTool(['python', '-c',
1313
 
                                      'print "@old_path @new_path"'],
 
907
        output = BytesIO()
 
908
        diff_obj = diff.DiffFromTool([sys.executable, '-c',
 
909
                                      'print("{old_path} {new_path}")'],
1314
910
                                     None, None, output)
1315
911
        self.addCleanup(diff_obj.finish)
1316
912
        diff_obj._execute('old', 'new')
1317
 
        self.assertEqual(output.getvalue().rstrip(), 'old new')
 
913
        self.assertEqual(output.getvalue().rstrip(), b'old new')
1318
914
 
1319
 
    def test_excute_missing(self):
 
915
    def test_execute_missing(self):
1320
916
        diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1321
917
                                     None, None, None)
1322
918
        self.addCleanup(diff_obj.finish)
1326
922
                         ' on this machine', str(e))
1327
923
 
1328
924
    def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1329
 
        self.requireFeature(AttribFeature)
1330
 
        output = StringIO()
 
925
        self.requireFeature(features.AttribFeature)
 
926
        output = BytesIO()
1331
927
        tree = self.make_branch_and_tree('tree')
1332
 
        self.build_tree_contents([('tree/file', 'content')])
1333
 
        tree.add('file', 'file-id')
 
928
        self.build_tree_contents([('tree/file', b'content')])
 
929
        tree.add('file', b'file-id')
1334
930
        tree.commit('old tree')
1335
931
        tree.lock_read()
1336
932
        self.addCleanup(tree.unlock)
1337
933
        basis_tree = tree.basis_tree()
1338
934
        basis_tree.lock_read()
1339
935
        self.addCleanup(basis_tree.unlock)
1340
 
        diff_obj = diff.DiffFromTool(['python', '-c',
1341
 
                                      'print "@old_path @new_path"'],
 
936
        diff_obj = diff.DiffFromTool([sys.executable, '-c',
 
937
                                      'print "{old_path} {new_path}"'],
1342
938
                                     basis_tree, tree, output)
1343
 
        diff_obj._prepare_files('file-id', 'file', 'file')
 
939
        diff_obj._prepare_files('file', 'file', file_id=b'file-id')
1344
940
        # The old content should be readonly
1345
941
        self.assertReadableByAttrib(diff_obj._root, 'old\\file',
1346
942
                                    r'R.*old\\file$')
1356
952
        self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1357
953
 
1358
954
    def test_prepare_files(self):
1359
 
        output = StringIO()
 
955
        output = BytesIO()
1360
956
        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')
 
957
        self.build_tree_contents([('tree/oldname', b'oldcontent')])
 
958
        self.build_tree_contents([('tree/oldname2', b'oldcontent2')])
 
959
        tree.add('oldname', b'file-id')
 
960
        tree.add('oldname2', b'file2-id')
1365
961
        # Earliest allowable date on FAT32 filesystems is 1980-01-01
1366
962
        tree.commit('old tree', timestamp=315532800)
1367
963
        tree.rename_one('oldname', 'newname')
1368
964
        tree.rename_one('oldname2', 'newname2')
1369
 
        self.build_tree_contents([('tree/newname', 'newcontent')])
1370
 
        self.build_tree_contents([('tree/newname2', 'newcontent2')])
 
965
        self.build_tree_contents([('tree/newname', b'newcontent')])
 
966
        self.build_tree_contents([('tree/newname2', b'newcontent2')])
1371
967
        old_tree = tree.basis_tree()
1372
968
        old_tree.lock_read()
1373
969
        self.addCleanup(old_tree.unlock)
1374
970
        tree.lock_read()
1375
971
        self.addCleanup(tree.unlock)
1376
 
        diff_obj = diff.DiffFromTool(['python', '-c',
1377
 
                                      'print "@old_path @new_path"'],
 
972
        diff_obj = diff.DiffFromTool([sys.executable, '-c',
 
973
                                      'print "{old_path} {new_path}"'],
1378
974
                                     old_tree, tree, output)
1379
975
        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')
 
976
        self.assertContainsRe(diff_obj._root, 'brz-diff-[^/]*')
 
977
        old_path, new_path = diff_obj._prepare_files(
 
978
            'oldname', 'newname')
1383
979
        self.assertContainsRe(old_path, 'old/oldname$')
1384
980
        self.assertEqual(315532800, os.stat(old_path).st_mtime)
1385
981
        self.assertContainsRe(new_path, 'tree/newname$')
1386
 
        self.assertFileEqual('oldcontent', old_path)
1387
 
        self.assertFileEqual('newcontent', new_path)
 
982
        self.assertFileEqual(b'oldcontent', old_path)
 
983
        self.assertFileEqual(b'newcontent', new_path)
1388
984
        if osutils.host_os_dereferences_symlinks():
1389
985
            self.assertTrue(os.path.samefile('tree/newname', new_path))
1390
986
        # make sure we can create files with the same parent directories
1391
 
        diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
 
987
        diff_obj._prepare_files('oldname2', 'newname2')
 
988
 
 
989
 
 
990
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
 
991
 
 
992
    def test_encodable_filename(self):
 
993
        # Just checks file path for external diff tool.
 
994
        # We cannot change CPython's internal encoding used by os.exec*.
 
995
        diffobj = diff.DiffFromTool(['dummy', '{old_path}', '{new_path}'],
 
996
                                    None, None, None)
 
997
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
998
            encoding = scenario['encoding']
 
999
            dirname = scenario['info']['directory']
 
1000
            filename = scenario['info']['filename']
 
1001
 
 
1002
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
1003
            relpath = dirname + u'/' + filename
 
1004
            fullpath = diffobj._safe_filename('safe', relpath)
 
1005
            self.assertEqual(fullpath,
 
1006
                             fullpath.encode(encoding).decode(encoding))
 
1007
            self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
 
1008
 
 
1009
    def test_unencodable_filename(self):
 
1010
        diffobj = diff.DiffFromTool(['dummy', '{old_path}', '{new_path}'],
 
1011
                                    None, None, None)
 
1012
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
1013
            encoding = scenario['encoding']
 
1014
            dirname = scenario['info']['directory']
 
1015
            filename = scenario['info']['filename']
 
1016
 
 
1017
            if encoding == 'iso-8859-1':
 
1018
                encoding = 'iso-8859-2'
 
1019
            else:
 
1020
                encoding = 'iso-8859-1'
 
1021
 
 
1022
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
1023
            relpath = dirname + u'/' + filename
 
1024
            fullpath = diffobj._safe_filename('safe', relpath)
 
1025
            self.assertEqual(fullpath,
 
1026
                             fullpath.encode(encoding).decode(encoding))
 
1027
            self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
1392
1028
 
1393
1029
 
1394
1030
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1395
1031
 
1396
1032
    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
 
        """
 
1033
        """Call get_trees_and_branches_to_diff_locked."""
 
1034
        exit_stack = contextlib.ExitStack()
 
1035
        self.addCleanup(exit_stack.close)
1400
1036
        return diff.get_trees_and_branches_to_diff_locked(
1401
 
            path_list, revision_specs, old_url, new_url, self.addCleanup)
 
1037
            path_list, revision_specs, old_url, new_url, exit_stack)
1402
1038
 
1403
1039
    def test_basic(self):
1404
1040
        tree = self.make_branch_and_tree('tree')
1418
1054
 
1419
1055
    def test_with_rev_specs(self):
1420
1056
        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")
 
1057
        self.build_tree_contents([('tree/file', b'oldcontent')])
 
1058
        tree.add('file', b'file-id')
 
1059
        tree.commit('old tree', timestamp=0, rev_id=b"old-id")
 
1060
        self.build_tree_contents([('tree/file', b'newcontent')])
 
1061
        tree.commit('new tree', timestamp=0, rev_id=b"new-id")
1426
1062
 
1427
1063
        revisions = [revisionspec.RevisionSpec.from_string('1'),
1428
1064
                     revisionspec.RevisionSpec.from_string('2')]
1432
1068
            ['tree'], revisions, None, None)
1433
1069
 
1434
1070
        self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1435
 
        self.assertEqual("old-id", old_tree.get_revision_id())
 
1071
        self.assertEqual(b"old-id", old_tree.get_revision_id())
1436
1072
        self.assertIsInstance(new_tree, revisiontree.RevisionTree)
1437
 
        self.assertEqual("new-id", new_tree.get_revision_id())
 
1073
        self.assertEqual(b"new-id", new_tree.get_revision_id())
1438
1074
        self.assertEqual(tree.branch.base, old_branch.base)
1439
1075
        self.assertEqual(tree.branch.base, new_branch.base)
1440
1076
        self.assertIs(None, specific_files)
1441
1077
        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