/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: Robert Collins
  • Date: 2010-05-06 23:41:35 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506234135-yivbzczw1sejxnxc
Lock methods on ``Tree``, ``Branch`` and ``Repository`` are now
expected to return an object which can be used to unlock them. This reduces
duplicate code when using cleanups. The previous 'tokens's returned by
``Branch.lock_write`` and ``Repository.lock_write`` are now attributes
on the result of the lock_write. ``repository.RepositoryWriteLockResult``
and ``branch.BranchWriteLockResult`` document this. (Robert Collins)

``log._get_info_for_log_files`` now takes an add_cleanup callable.
(Robert Collins)

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