/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 bzrlib/tests/test_diff.py

  • Committer: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

Show diffs side-by-side

added added

removed removed

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