/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: Canonical.com Patch Queue Manager
  • Date: 2009-04-09 20:23:07 UTC
  • mfrom: (4265.1.4 bbc-merge)
  • Revision ID: pqm@pqm.ubuntu.com-20090409202307-n0depb16qepoe21o
(jam) Change _fetch_uses_deltas = False for CHK repos until we can
        write a better fix.

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