/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/tests/test_diff.py

  • Committer: Jelmer Vernooij
  • Date: 2018-11-03 22:40:55 UTC
  • mto: This revision was merged to the branch mainline in revision 7157.
  • Revision ID: jelmer@jelmer.uk-20181103224055-wpa8tyxvmo5t60dy
Implement TreeReference.kind_character.

Show diffs side-by-side

added added

removed removed

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