/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: Martin
  • Date: 2017-06-10 01:57:00 UTC
  • mto: This revision was merged to the branch mainline in revision 6679.
  • Revision ID: gzlist@googlemail.com-20170610015700-o3xeuyaqry2obiay
Go back to native str for urls and many other py3 changes

Show diffs side-by-side

added added

removed removed

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