~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil
  • Date: 2017-01-30 14:42:05 UTC
  • mfrom: (6620.1.1 trunk)
  • Revision ID: tarmac-20170130144205-r8fh2xpmiuxyozpv
Merge  2.7 into trunk including fix for bug #1657238 [r=vila]

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2012, 2014, 2016, 2017 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
17
17
import os
18
18
from cStringIO import StringIO
19
19
import subprocess
20
 
import sys
21
20
import tempfile
22
21
 
23
22
from bzrlib import (
32
31
    tests,
33
32
    transform,
34
33
    )
35
 
from bzrlib.symbol_versioning import deprecated_in
36
 
from bzrlib.tests import features
 
34
from bzrlib.tests import (
 
35
    features,
 
36
    EncodingAdapter,
 
37
)
37
38
from bzrlib.tests.blackbox.test_diff import subst_dates
38
 
 
39
 
 
40
 
class _AttribFeature(tests.Feature):
41
 
 
42
 
    def _probe(self):
43
 
        if (sys.platform not in ('cygwin', 'win32')):
44
 
            return False
45
 
        try:
46
 
            proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
47
 
        except OSError, e:
48
 
            return False
49
 
        return (0 == proc.wait())
50
 
 
51
 
    def feature_name(self):
52
 
        return 'attrib Windows command-line tool'
53
 
 
54
 
AttribFeature = _AttribFeature()
55
 
 
56
 
 
57
 
compiled_patiencediff_feature = tests.ModuleAvailableFeature(
58
 
                                    'bzrlib._patiencediff_c')
 
39
from bzrlib.tests.scenarios import load_tests_apply_scenarios
 
40
 
 
41
 
 
42
load_tests = load_tests_apply_scenarios
59
43
 
60
44
 
61
45
def udiff_lines(old, new, allow_binary=False):
81
65
    return lines
82
66
 
83
67
 
 
68
class TestDiffOptions(tests.TestCase):
 
69
 
 
70
    def test_unified_added(self):
 
71
        """Check for default style '-u' only if no other style specified
 
72
        in 'diff-options'.
 
73
        """
 
74
        # Verify that style defaults to unified, id est '-u' appended
 
75
        # to option list, in the absence of an alternative style.
 
76
        self.assertEqual(['-a', '-u'], diff.default_style_unified(['-a']))
 
77
 
 
78
 
 
79
class TestDiffOptionsScenarios(tests.TestCase):
 
80
 
 
81
    scenarios = [(s, dict(style=s)) for s in diff.style_option_list]
 
82
    style = None # Set by load_tests_apply_scenarios from scenarios
 
83
 
 
84
    def test_unified_not_added(self):
 
85
        # Verify that for all valid style options, '-u' is not
 
86
        # appended to option list.
 
87
        ret_opts = diff.default_style_unified(diff_opts=["%s" % (self.style,)])
 
88
        self.assertEqual(["%s" % (self.style,)], ret_opts)
 
89
 
 
90
 
84
91
class TestDiff(tests.TestCase):
85
92
 
86
93
    def test_add_nl(self):
87
94
        """diff generates a valid diff for patches that add a newline"""
88
95
        lines = udiff_lines(['boo'], ['boo\n'])
89
96
        self.check_patch(lines)
90
 
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
 
97
        self.assertEqual(lines[4], '\\ No newline at end of file\n')
91
98
            ## "expected no-nl, got %r" % lines[4]
92
99
 
93
100
    def test_add_nl_2(self):
96
103
        """
97
104
        lines = udiff_lines(['boo'], ['goo\n'])
98
105
        self.check_patch(lines)
99
 
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
 
106
        self.assertEqual(lines[4], '\\ No newline at end of file\n')
100
107
            ## "expected no-nl, got %r" % lines[4]
101
108
 
102
109
    def test_remove_nl(self):
105
112
        """
106
113
        lines = udiff_lines(['boo\n'], ['boo'])
107
114
        self.check_patch(lines)
108
 
        self.assertEquals(lines[5], '\\ No newline at end of file\n')
 
115
        self.assertEqual(lines[5], '\\ No newline at end of file\n')
109
116
            ## "expected no-nl, got %r" % lines[5]
110
117
 
111
118
    def check_patch(self, lines):
112
 
        self.assert_(len(lines) > 1)
 
119
        self.assertTrue(len(lines) > 1)
113
120
            ## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
114
 
        self.assert_(lines[0].startswith ('---'))
 
121
        self.assertTrue(lines[0].startswith ('---'))
115
122
            ## 'No orig line for patch:\n%s' % "".join(lines)
116
 
        self.assert_(lines[1].startswith ('+++'))
 
123
        self.assertTrue(lines[1].startswith ('+++'))
117
124
            ## 'No mod line for patch:\n%s' % "".join(lines)
118
 
        self.assert_(len(lines) > 2)
 
125
        self.assertTrue(len(lines) > 2)
119
126
            ## "No hunks for patch:\n%s" % "".join(lines)
120
 
        self.assert_(lines[2].startswith('@@'))
 
127
        self.assertTrue(lines[2].startswith('@@'))
121
128
            ## "No hunk header for patch:\n%s" % "".join(lines)
122
 
        self.assert_('@@' in lines[2][2:])
 
129
        self.assertTrue('@@' in lines[2][2:])
123
130
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
124
131
 
125
132
    def test_binary_lines(self):
144
151
        self.check_patch(lines)
145
152
 
146
153
    def test_external_diff_binary_lang_c(self):
147
 
        old_env = {}
148
154
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
149
 
            old_env[lang] = osutils.set_or_unset_env(lang, 'C')
150
 
        try:
151
 
            lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
152
 
            # Older versions of diffutils say "Binary files", newer
153
 
            # versions just say "Files".
154
 
            self.assertContainsRe(lines[0],
155
 
                                  '(Binary f|F)iles old and new differ\n')
156
 
            self.assertEquals(lines[1:], ['\n'])
157
 
        finally:
158
 
            for lang, old_val in old_env.iteritems():
159
 
                osutils.set_or_unset_env(lang, old_val)
 
155
            self.overrideEnv(lang, 'C')
 
156
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
157
        # Older versions of diffutils say "Binary files", newer
 
158
        # versions just say "Files".
 
159
        self.assertContainsRe(lines[0], '(Binary f|F)iles old and new differ\n')
 
160
        self.assertEqual(lines[1:], ['\n'])
160
161
 
161
162
    def test_no_external_diff(self):
162
163
        """Check that NoDiff is raised when diff is not available"""
163
 
        # Use os.environ['PATH'] to make sure no 'diff' command is available
164
 
        orig_path = os.environ['PATH']
165
 
        try:
166
 
            os.environ['PATH'] = ''
167
 
            self.assertRaises(errors.NoDiff, diff.external_diff,
168
 
                              'old', ['boo\n'], 'new', ['goo\n'],
169
 
                              StringIO(), diff_opts=['-u'])
170
 
        finally:
171
 
            os.environ['PATH'] = orig_path
 
164
        # Make sure no 'diff' command is available
 
165
        # XXX: Weird, using None instead of '' breaks the test -- vila 20101216
 
166
        self.overrideEnv('PATH', '')
 
167
        self.assertRaises(errors.NoDiff, diff.external_diff,
 
168
                          'old', ['boo\n'], 'new', ['goo\n'],
 
169
                          StringIO(), diff_opts=['-u'])
172
170
 
173
171
    def test_internal_diff_default(self):
174
172
        # Default internal diff encoding is utf8
177
175
                           u'new_\xe5', ['new_text\n'], output)
178
176
        lines = output.getvalue().splitlines(True)
179
177
        self.check_patch(lines)
180
 
        self.assertEquals(['--- old_\xc2\xb5\n',
 
178
        self.assertEqual(['--- old_\xc2\xb5\n',
181
179
                           '+++ new_\xc3\xa5\n',
182
180
                           '@@ -1,1 +1,1 @@\n',
183
181
                           '-old_text\n',
193
191
                           path_encoding='utf8')
194
192
        lines = output.getvalue().splitlines(True)
195
193
        self.check_patch(lines)
196
 
        self.assertEquals(['--- old_\xc2\xb5\n',
 
194
        self.assertEqual(['--- old_\xc2\xb5\n',
197
195
                           '+++ new_\xc3\xa5\n',
198
196
                           '@@ -1,1 +1,1 @@\n',
199
197
                           '-old_text\n',
209
207
                           path_encoding='iso-8859-1')
210
208
        lines = output.getvalue().splitlines(True)
211
209
        self.check_patch(lines)
212
 
        self.assertEquals(['--- old_\xb5\n',
 
210
        self.assertEqual(['--- old_\xb5\n',
213
211
                           '+++ new_\xe5\n',
214
212
                           '@@ -1,1 +1,1 @@\n',
215
213
                           '-old_text\n',
235
233
        output = StringIO.StringIO()
236
234
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
237
235
                            u'new_\xe5', ['new_text\n'], output)
238
 
        self.failUnless(isinstance(output.getvalue(), str),
 
236
        self.assertIsInstance(output.getvalue(), str,
239
237
            'internal_diff should return bytestrings')
240
238
 
 
239
    def test_internal_diff_default_context(self):
 
240
        output = StringIO()
 
241
        diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
 
242
                           'same_text\n','same_text\n','old_text\n'],
 
243
                           'new', ['same_text\n','same_text\n','same_text\n',
 
244
                           'same_text\n','same_text\n','new_text\n'], output)
 
245
        lines = output.getvalue().splitlines(True)
 
246
        self.check_patch(lines)
 
247
        self.assertEqual(['--- old\n',
 
248
                           '+++ new\n',
 
249
                           '@@ -3,4 +3,4 @@\n',
 
250
                           ' same_text\n',
 
251
                           ' same_text\n',
 
252
                           ' same_text\n',
 
253
                           '-old_text\n',
 
254
                           '+new_text\n',
 
255
                           '\n',
 
256
                          ]
 
257
                          , lines)
 
258
 
 
259
    def test_internal_diff_no_context(self):
 
260
        output = StringIO()
 
261
        diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
 
262
                           'same_text\n','same_text\n','old_text\n'],
 
263
                           'new', ['same_text\n','same_text\n','same_text\n',
 
264
                           'same_text\n','same_text\n','new_text\n'], output,
 
265
                           context_lines=0)
 
266
        lines = output.getvalue().splitlines(True)
 
267
        self.check_patch(lines)
 
268
        self.assertEqual(['--- old\n',
 
269
                           '+++ new\n',
 
270
                           '@@ -6,1 +6,1 @@\n',
 
271
                           '-old_text\n',
 
272
                           '+new_text\n',
 
273
                           '\n',
 
274
                          ]
 
275
                          , lines)
 
276
 
 
277
    def test_internal_diff_more_context(self):
 
278
        output = StringIO()
 
279
        diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
 
280
                           'same_text\n','same_text\n','old_text\n'],
 
281
                           'new', ['same_text\n','same_text\n','same_text\n',
 
282
                           'same_text\n','same_text\n','new_text\n'], output,
 
283
                           context_lines=4)
 
284
        lines = output.getvalue().splitlines(True)
 
285
        self.check_patch(lines)
 
286
        self.assertEqual(['--- old\n',
 
287
                           '+++ new\n',
 
288
                           '@@ -2,5 +2,5 @@\n',
 
289
                           ' same_text\n',
 
290
                           ' same_text\n',
 
291
                           ' same_text\n',
 
292
                           ' same_text\n',
 
293
                           '-old_text\n',
 
294
                           '+new_text\n',
 
295
                           '\n',
 
296
                          ]
 
297
                          , lines)
 
298
 
 
299
 
 
300
 
 
301
 
241
302
 
242
303
class TestDiffFiles(tests.TestCaseInTempDir):
243
304
 
247
308
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
248
309
 
249
310
        cmd = ['diff', '-u', '--binary', 'old', 'new']
250
 
        open('old', 'wb').write('\x00foobar\n')
251
 
        open('new', 'wb').write('foo\x00bar\n')
 
311
        with open('old', 'wb') as f: f.write('\x00foobar\n')
 
312
        with open('new', 'wb') as f: f.write('foo\x00bar\n')
252
313
        pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
253
314
                                     stdin=subprocess.PIPE)
254
315
        out, err = pipe.communicate()
255
 
        # Diff returns '2' on Binary files.
256
 
        self.assertEqual(2, pipe.returncode)
257
316
        # We should output whatever diff tells us, plus a trailing newline
258
317
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
259
318
 
260
319
 
261
 
class TestShowDiffTreesHelper(tests.TestCaseWithTransport):
262
 
    """Has a helper for running show_diff_trees"""
263
 
 
264
 
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
265
 
        output = StringIO()
266
 
        if working_tree is not None:
267
 
            extra_trees = (working_tree,)
268
 
        else:
269
 
            extra_trees = ()
270
 
        diff.show_diff_trees(tree1, tree2, output,
271
 
                             specific_files=specific_files,
272
 
                             extra_trees=extra_trees, old_label='old/',
273
 
                             new_label='new/')
274
 
        return output.getvalue()
275
 
 
276
 
 
277
 
class TestDiffDates(TestShowDiffTreesHelper):
 
320
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
 
321
    output = StringIO()
 
322
    if working_tree is not None:
 
323
        extra_trees = (working_tree,)
 
324
    else:
 
325
        extra_trees = ()
 
326
    diff.show_diff_trees(tree1, tree2, output,
 
327
        specific_files=specific_files,
 
328
        extra_trees=extra_trees, old_label='old/',
 
329
        new_label='new/')
 
330
    return output.getvalue()
 
331
 
 
332
 
 
333
class TestDiffDates(tests.TestCaseWithTransport):
278
334
 
279
335
    def setUp(self):
280
336
        super(TestDiffDates, self).setUp()
315
371
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
316
372
 
317
373
    def test_diff_rev_tree_working_tree(self):
318
 
        output = self.get_diff(self.wt.basis_tree(), self.wt)
 
374
        output = get_diff_as_string(self.wt.basis_tree(), self.wt)
319
375
        # note that the date for old/file1 is from rev 2 rather than from
320
376
        # the basis revision (rev 4)
321
377
        self.assertEqualDiff(output, '''\
331
387
    def test_diff_rev_tree_rev_tree(self):
332
388
        tree1 = self.b.repository.revision_tree('rev-2')
333
389
        tree2 = self.b.repository.revision_tree('rev-3')
334
 
        output = self.get_diff(tree1, tree2)
 
390
        output = get_diff_as_string(tree1, tree2)
335
391
        self.assertEqualDiff(output, '''\
336
392
=== modified file 'file2'
337
393
--- old/file2\t2006-04-01 00:00:00 +0000
345
401
    def test_diff_add_files(self):
346
402
        tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
347
403
        tree2 = self.b.repository.revision_tree('rev-1')
348
 
        output = self.get_diff(tree1, tree2)
 
404
        output = get_diff_as_string(tree1, tree2)
349
405
        # the files have the epoch time stamp for the tree in which
350
406
        # they don't exist.
351
407
        self.assertEqualDiff(output, '''\
366
422
    def test_diff_remove_files(self):
367
423
        tree1 = self.b.repository.revision_tree('rev-3')
368
424
        tree2 = self.b.repository.revision_tree('rev-4')
369
 
        output = self.get_diff(tree1, tree2)
 
425
        output = get_diff_as_string(tree1, tree2)
370
426
        # the file has the epoch time stamp for the tree in which
371
427
        # it doesn't exist.
372
428
        self.assertEqualDiff(output, '''\
383
439
        self.wt.rename_one('file1', 'file1b')
384
440
        old_tree = self.b.repository.revision_tree('rev-1')
385
441
        new_tree = self.b.repository.revision_tree('rev-4')
386
 
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'],
 
442
        out = get_diff_as_string(old_tree, new_tree, specific_files=['file1b'],
387
443
                            working_tree=self.wt)
388
444
        self.assertContainsRe(out, 'file1\t')
389
445
 
395
451
        self.wt.rename_one('file1', 'dir1/file1')
396
452
        old_tree = self.b.repository.revision_tree('rev-1')
397
453
        new_tree = self.b.repository.revision_tree('rev-4')
398
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'],
 
454
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
399
455
                            working_tree=self.wt)
400
456
        self.assertContainsRe(out, 'file1\t')
401
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'],
 
457
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
402
458
                            working_tree=self.wt)
403
459
        self.assertNotContainsRe(out, 'file1\t')
404
460
 
405
461
 
406
 
 
407
 
class TestShowDiffTrees(TestShowDiffTreesHelper):
 
462
class TestShowDiffTrees(tests.TestCaseWithTransport):
408
463
    """Direct tests for show_diff_trees"""
409
464
 
410
465
    def test_modified_file(self):
415
470
        tree.commit('one', rev_id='rev-1')
416
471
 
417
472
        self.build_tree_contents([('tree/file', 'new contents\n')])
418
 
        d = self.get_diff(tree.basis_tree(), tree)
 
473
        d = get_diff_as_string(tree.basis_tree(), tree)
419
474
        self.assertContainsRe(d, "=== modified file 'file'\n")
420
475
        self.assertContainsRe(d, '--- old/file\t')
421
476
        self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
432
487
 
433
488
        tree.rename_one('dir', 'other')
434
489
        self.build_tree_contents([('tree/other/file', 'new contents\n')])
435
 
        d = self.get_diff(tree.basis_tree(), tree)
 
490
        d = get_diff_as_string(tree.basis_tree(), tree)
436
491
        self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
437
492
        self.assertContainsRe(d, "=== modified file 'other/file'\n")
438
493
        # XXX: This is technically incorrect, because it used to be at another
451
506
        tree.commit('one', rev_id='rev-1')
452
507
 
453
508
        tree.rename_one('dir', 'newdir')
454
 
        d = self.get_diff(tree.basis_tree(), tree)
 
509
        d = get_diff_as_string(tree.basis_tree(), tree)
455
510
        # Renaming a directory should be a single "you renamed this dir" even
456
511
        # when there are files inside.
457
512
        self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
464
519
        tree.commit('one', rev_id='rev-1')
465
520
 
466
521
        tree.rename_one('file', 'newname')
467
 
        d = self.get_diff(tree.basis_tree(), tree)
 
522
        d = get_diff_as_string(tree.basis_tree(), tree)
468
523
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
469
524
        # We shouldn't have a --- or +++ line, because there is no content
470
525
        # change
479
534
 
480
535
        tree.rename_one('file', 'newname')
481
536
        self.build_tree_contents([('tree/newname', 'new contents\n')])
482
 
        d = self.get_diff(tree.basis_tree(), tree)
 
537
        d = get_diff_as_string(tree.basis_tree(), tree)
483
538
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
484
539
        self.assertContainsRe(d, '--- old/file\t')
485
540
        self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
509
564
        tree.rename_one('c', 'new-c')
510
565
        tree.rename_one('d', 'new-d')
511
566
 
512
 
        d = self.get_diff(tree.basis_tree(), tree)
 
567
        d = get_diff_as_string(tree.basis_tree(), tree)
513
568
 
514
569
        self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
515
570
                                  ".*\+x to -x.*\)")
527
582
        is a binary file in the diff.
528
583
        """
529
584
        # See https://bugs.launchpad.net/bugs/110092.
530
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
585
        self.requireFeature(features.UnicodeFilenameFeature)
531
586
 
532
587
        # This bug isn't triggered with cStringIO.
533
588
        from StringIO import StringIO
552
607
 
553
608
    def test_unicode_filename(self):
554
609
        """Test when the filename are unicode."""
555
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
610
        self.requireFeature(features.UnicodeFilenameFeature)
556
611
 
557
612
        alpha, omega = u'\u03b1', u'\u03c9'
558
613
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
573
628
        tree.add(['add_'+alpha], ['file-id'])
574
629
        self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
575
630
 
576
 
        d = self.get_diff(tree.basis_tree(), tree)
 
631
        d = get_diff_as_string(tree.basis_tree(), tree)
577
632
        self.assertContainsRe(d,
578
633
                "=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
579
634
        self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
584
639
        """Test for bug #382699: unicode filenames on Windows should be shown
585
640
        in user encoding.
586
641
        """
587
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
642
        self.requireFeature(features.UnicodeFilenameFeature)
588
643
        # The word 'test' in Russian
589
644
        _russian_test = u'\u0422\u0435\u0441\u0442'
590
645
        directory = _russian_test + u'/'
721
776
             ' \@\@\n-old\n\+new\n\n')
722
777
 
723
778
    def test_diff_kind_change(self):
724
 
        self.requireFeature(tests.SymlinkFeature)
 
779
        self.requireFeature(features.SymlinkFeature)
725
780
        self.build_tree_contents([('old-tree/olddir/',),
726
781
                                  ('old-tree/olddir/oldfile', 'old\n')])
727
782
        self.old_tree.add('olddir')
807
862
        b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
808
863
        sm = self._PatienceSequenceMatcher(None, a, b)
809
864
        mb = sm.get_matching_blocks()
810
 
        self.assertEquals(35, len(mb))
 
865
        self.assertEqual(35, len(mb))
811
866
 
812
867
    def test_unique_lcs(self):
813
868
        unique_lcs = self._unique_lcs
814
 
        self.assertEquals(unique_lcs('', ''), [])
815
 
        self.assertEquals(unique_lcs('', 'a'), [])
816
 
        self.assertEquals(unique_lcs('a', ''), [])
817
 
        self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
818
 
        self.assertEquals(unique_lcs('a', 'b'), [])
819
 
        self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
820
 
        self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
821
 
        self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
822
 
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
 
869
        self.assertEqual(unique_lcs('', ''), [])
 
870
        self.assertEqual(unique_lcs('', 'a'), [])
 
871
        self.assertEqual(unique_lcs('a', ''), [])
 
872
        self.assertEqual(unique_lcs('a', 'a'), [(0,0)])
 
873
        self.assertEqual(unique_lcs('a', 'b'), [])
 
874
        self.assertEqual(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
 
875
        self.assertEqual(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
 
876
        self.assertEqual(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
 
877
        self.assertEqual(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
823
878
                                                         (3,3), (4,4)])
824
 
        self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
 
879
        self.assertEqual(unique_lcs('acbac', 'abc'), [(2,1)])
825
880
 
826
881
    def test_recurse_matches(self):
827
882
        def test_one(a, b, matches):
828
883
            test_matches = []
829
884
            self._recurse_matches(
830
885
                a, b, 0, 0, len(a), len(b), test_matches, 10)
831
 
            self.assertEquals(test_matches, matches)
 
886
            self.assertEqual(test_matches, matches)
832
887
 
833
888
        test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
834
889
                 [(0, 0), (2, 2), (4, 4)])
939
994
    def test_opcodes(self):
940
995
        def chk_ops(a, b, expected_codes):
941
996
            s = self._PatienceSequenceMatcher(None, a, b)
942
 
            self.assertEquals(expected_codes, s.get_opcodes())
 
997
            self.assertEqual(expected_codes, s.get_opcodes())
943
998
 
944
999
        chk_ops('', '', [])
945
1000
        chk_ops([], [], [])
1015
1070
    def test_grouped_opcodes(self):
1016
1071
        def chk_ops(a, b, expected_codes, n=3):
1017
1072
            s = self._PatienceSequenceMatcher(None, a, b)
1018
 
            self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
 
1073
            self.assertEqual(expected_codes, list(s.get_grouped_opcodes(n)))
1019
1074
 
1020
1075
        chk_ops('', '', [])
1021
1076
        chk_ops([], [], [])
1115
1170
                 'how are you today?\n']
1116
1171
        unified_diff = patiencediff.unified_diff
1117
1172
        psm = self._PatienceSequenceMatcher
1118
 
        self.assertEquals(['--- \n',
 
1173
        self.assertEqual(['--- \n',
1119
1174
                           '+++ \n',
1120
1175
                           '@@ -1,3 +1,2 @@\n',
1121
1176
                           ' hello there\n',
1127
1182
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1128
1183
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1129
1184
        # This is the result with LongestCommonSubstring matching
1130
 
        self.assertEquals(['--- \n',
 
1185
        self.assertEqual(['--- \n',
1131
1186
                           '+++ \n',
1132
1187
                           '@@ -1,6 +1,11 @@\n',
1133
1188
                           ' a\n',
1143
1198
                           ' f\n']
1144
1199
                          , list(unified_diff(txt_a, txt_b)))
1145
1200
        # And the patience diff
1146
 
        self.assertEquals(['--- \n',
 
1201
        self.assertEqual(['--- \n',
1147
1202
                           '+++ \n',
1148
1203
                           '@@ -4,6 +4,11 @@\n',
1149
1204
                           ' d\n',
1169
1224
                 'how are you today?\n']
1170
1225
        unified_diff = patiencediff.unified_diff
1171
1226
        psm = self._PatienceSequenceMatcher
1172
 
        self.assertEquals(['--- a\t2008-08-08\n',
 
1227
        self.assertEqual(['--- a\t2008-08-08\n',
1173
1228
                           '+++ b\t2008-09-09\n',
1174
1229
                           '@@ -1,3 +1,2 @@\n',
1175
1230
                           ' hello there\n',
1185
1240
 
1186
1241
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1187
1242
 
1188
 
    _test_needs_features = [compiled_patiencediff_feature]
 
1243
    _test_needs_features = [features.compiled_patiencediff_feature]
1189
1244
 
1190
1245
    def setUp(self):
1191
1246
        super(TestPatienceDiffLib_c, self).setUp()
1222
1277
                 'how are you today?\n']
1223
1278
        txt_b = ['hello there\n',
1224
1279
                 'how are you today?\n']
1225
 
        open('a1', 'wb').writelines(txt_a)
1226
 
        open('b1', 'wb').writelines(txt_b)
 
1280
        with open('a1', 'wb') as f: f.writelines(txt_a)
 
1281
        with open('b1', 'wb') as f: f.writelines(txt_b)
1227
1282
 
1228
1283
        unified_diff_files = patiencediff.unified_diff_files
1229
1284
        psm = self._PatienceSequenceMatcher
1230
 
        self.assertEquals(['--- a1\n',
 
1285
        self.assertEqual(['--- a1\n',
1231
1286
                           '+++ b1\n',
1232
1287
                           '@@ -1,3 +1,2 @@\n',
1233
1288
                           ' hello there\n',
1239
1294
 
1240
1295
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1241
1296
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1242
 
        open('a2', 'wb').writelines(txt_a)
1243
 
        open('b2', 'wb').writelines(txt_b)
 
1297
        with open('a2', 'wb') as f: f.writelines(txt_a)
 
1298
        with open('b2', 'wb') as f: f.writelines(txt_b)
1244
1299
 
1245
1300
        # This is the result with LongestCommonSubstring matching
1246
 
        self.assertEquals(['--- a2\n',
 
1301
        self.assertEqual(['--- a2\n',
1247
1302
                           '+++ b2\n',
1248
1303
                           '@@ -1,6 +1,11 @@\n',
1249
1304
                           ' a\n',
1260
1315
                          , list(unified_diff_files('a2', 'b2')))
1261
1316
 
1262
1317
        # And the patience diff
1263
 
        self.assertEquals(['--- a2\n',
1264
 
                           '+++ b2\n',
1265
 
                           '@@ -4,6 +4,11 @@\n',
1266
 
                           ' d\n',
1267
 
                           ' e\n',
1268
 
                           ' f\n',
1269
 
                           '+x\n',
1270
 
                           '+y\n',
1271
 
                           '+d\n',
1272
 
                           '+e\n',
1273
 
                           '+f\n',
1274
 
                           ' g\n',
1275
 
                           ' h\n',
1276
 
                           ' i\n',
1277
 
                          ]
1278
 
                          , list(unified_diff_files('a2', 'b2',
1279
 
                                 sequencematcher=psm)))
 
1318
        self.assertEqual(['--- a2\n',
 
1319
                          '+++ b2\n',
 
1320
                          '@@ -4,6 +4,11 @@\n',
 
1321
                          ' d\n',
 
1322
                          ' e\n',
 
1323
                          ' f\n',
 
1324
                          '+x\n',
 
1325
                          '+y\n',
 
1326
                          '+d\n',
 
1327
                          '+e\n',
 
1328
                          '+f\n',
 
1329
                          ' g\n',
 
1330
                          ' h\n',
 
1331
                          ' i\n'],
 
1332
                         list(unified_diff_files('a2', 'b2',
 
1333
                                                 sequencematcher=psm)))
1280
1334
 
1281
1335
 
1282
1336
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1283
1337
 
1284
 
    _test_needs_features = [compiled_patiencediff_feature]
 
1338
    _test_needs_features = [features.compiled_patiencediff_feature]
1285
1339
 
1286
1340
    def setUp(self):
1287
1341
        super(TestPatienceDiffLibFiles_c, self).setUp()
1293
1347
class TestUsingCompiledIfAvailable(tests.TestCase):
1294
1348
 
1295
1349
    def test_PatienceSequenceMatcher(self):
1296
 
        if compiled_patiencediff_feature.available():
 
1350
        if features.compiled_patiencediff_feature.available():
1297
1351
            from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1298
1352
            self.assertIs(PatienceSequenceMatcher_c,
1299
1353
                          patiencediff.PatienceSequenceMatcher)
1303
1357
                          patiencediff.PatienceSequenceMatcher)
1304
1358
 
1305
1359
    def test_unique_lcs(self):
1306
 
        if compiled_patiencediff_feature.available():
 
1360
        if features.compiled_patiencediff_feature.available():
1307
1361
            from bzrlib._patiencediff_c import unique_lcs_c
1308
1362
            self.assertIs(unique_lcs_c,
1309
1363
                          patiencediff.unique_lcs)
1313
1367
                          patiencediff.unique_lcs)
1314
1368
 
1315
1369
    def test_recurse_matches(self):
1316
 
        if compiled_patiencediff_feature.available():
 
1370
        if features.compiled_patiencediff_feature.available():
1317
1371
            from bzrlib._patiencediff_c import recurse_matches_c
1318
1372
            self.assertIs(recurse_matches_c,
1319
1373
                          patiencediff.recurse_matches)
1359
1413
        diff_obj._execute('old', 'new')
1360
1414
        self.assertEqual(output.getvalue().rstrip(), 'old new')
1361
1415
 
1362
 
    def test_excute_missing(self):
 
1416
    def test_execute_missing(self):
1363
1417
        diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1364
1418
                                     None, None, None)
1365
1419
        self.addCleanup(diff_obj.finish)
1369
1423
                         ' on this machine', str(e))
1370
1424
 
1371
1425
    def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1372
 
        self.requireFeature(AttribFeature)
 
1426
        self.requireFeature(features.AttribFeature)
1373
1427
        output = StringIO()
1374
1428
        tree = self.make_branch_and_tree('tree')
1375
1429
        self.build_tree_contents([('tree/file', 'content')])
1434
1488
        diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
1435
1489
 
1436
1490
 
 
1491
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
 
1492
 
 
1493
    def test_encodable_filename(self):
 
1494
        # Just checks file path for external diff tool.
 
1495
        # We cannot change CPython's internal encoding used by os.exec*.
 
1496
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
 
1497
                                    None, None, None)
 
1498
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
1499
            encoding = scenario['encoding']
 
1500
            dirname = scenario['info']['directory']
 
1501
            filename = scenario['info']['filename']
 
1502
 
 
1503
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
1504
            relpath = dirname + u'/' + filename
 
1505
            fullpath = diffobj._safe_filename('safe', relpath)
 
1506
            self.assertEqual(fullpath,
 
1507
                             fullpath.encode(encoding).decode(encoding))
 
1508
            self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
 
1509
 
 
1510
    def test_unencodable_filename(self):
 
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
            if encoding == 'iso-8859-1':
 
1519
                encoding = 'iso-8859-2'
 
1520
            else:
 
1521
                encoding = 'iso-8859-1'
 
1522
 
 
1523
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
1524
            relpath = dirname + u'/' + filename
 
1525
            fullpath = diffobj._safe_filename('safe', relpath)
 
1526
            self.assertEqual(fullpath,
 
1527
                             fullpath.encode(encoding).decode(encoding))
 
1528
            self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
 
1529
 
 
1530
 
1437
1531
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1438
1532
 
1439
1533
    def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1440
 
        """Call get_trees_and_branches_to_diff_locked.  Overridden by
1441
 
        TestGetTreesAndBranchesToDiff.
1442
 
        """
 
1534
        """Call get_trees_and_branches_to_diff_locked."""
1443
1535
        return diff.get_trees_and_branches_to_diff_locked(
1444
1536
            path_list, revision_specs, old_url, new_url, self.addCleanup)
1445
1537
 
1482
1574
        self.assertEqual(tree.branch.base, new_branch.base)
1483
1575
        self.assertIs(None, specific_files)
1484
1576
        self.assertEqual(tree.basedir, extra_trees[0].basedir)
1485
 
 
1486
 
 
1487
 
class TestGetTreesAndBranchesToDiff(TestGetTreesAndBranchesToDiffLocked):
1488
 
    """Apply the tests for get_trees_and_branches_to_diff_locked to the
1489
 
    deprecated get_trees_and_branches_to_diff function.
1490
 
    """
1491
 
 
1492
 
    def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1493
 
        return self.applyDeprecated(
1494
 
            deprecated_in((2, 2, 0)), diff.get_trees_and_branches_to_diff,
1495
 
            path_list, revision_specs, old_url, new_url)
1496