/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/tests/test_diff.py

  • Committer: Jelmer Vernooij
  • Date: 2019-02-03 01:42:11 UTC
  • mto: This revision was merged to the branch mainline in revision 7267.
  • Revision ID: jelmer@jelmer.uk-20190203014211-poj1fv922sejfsb4
Don't require that short git shas have an even number of characters.

Show diffs side-by-side

added added

removed removed

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