~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

  • Committer: John Arbash Meinel
  • Date: 2007-11-13 20:37:09 UTC
  • mto: This revision was merged to the branch mainline in revision 3001.
  • Revision ID: john@arbash-meinel.com-20071113203709-kysdte0emqv84pnj
Fix bug #162486, by having RemoteBranch properly initialize self._revision_id_to_revno_map.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
from bzrlib.tests import TestCase
2
 
from bzrlib.diff import internal_diff
 
1
# Copyright (C) 2005, 2006 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import os
3
18
from cStringIO import StringIO
4
 
def udiff_lines(old, new):
 
19
import errno
 
20
import subprocess
 
21
from tempfile import TemporaryFile
 
22
 
 
23
from bzrlib.diff import internal_diff, external_diff, show_diff_trees
 
24
from bzrlib.errors import BinaryFile, NoDiff
 
25
import bzrlib.osutils as osutils
 
26
import bzrlib.patiencediff
 
27
import bzrlib._patiencediff_py
 
28
from bzrlib.tests import (Feature, TestCase, TestCaseWithTransport,
 
29
                          TestCaseInTempDir, TestSkipped)
 
30
 
 
31
 
 
32
class _CompiledPatienceDiffFeature(Feature):
 
33
 
 
34
    def _probe(self):
 
35
        try:
 
36
            import bzrlib._patiencediff_c
 
37
        except ImportError:
 
38
            return False
 
39
        return True
 
40
 
 
41
    def feature_name(self):
 
42
        return 'bzrlib._patiencediff_c'
 
43
 
 
44
CompiledPatienceDiffFeature = _CompiledPatienceDiffFeature()
 
45
 
 
46
 
 
47
class _UnicodeFilename(Feature):
 
48
    """Does the filesystem support Unicode filenames?"""
 
49
 
 
50
    def _probe(self):
 
51
        try:
 
52
            os.stat(u'\u03b1')
 
53
        except UnicodeEncodeError:
 
54
            return False
 
55
        except (IOError, OSError):
 
56
            # The filesystem allows the Unicode filename but the file doesn't
 
57
            # exist.
 
58
            return True
 
59
        else:
 
60
            # The filesystem allows the Unicode filename and the file exists,
 
61
            # for some reason.
 
62
            return True
 
63
 
 
64
UnicodeFilename = _UnicodeFilename()
 
65
 
 
66
 
 
67
class TestUnicodeFilename(TestCase):
 
68
 
 
69
    def test_probe_passes(self):
 
70
        """UnicodeFilename._probe passes."""
 
71
        # We can't test much more than that because the behaviour depends
 
72
        # on the platform.
 
73
        UnicodeFilename._probe()
 
74
        
 
75
 
 
76
def udiff_lines(old, new, allow_binary=False):
5
77
    output = StringIO()
6
 
    internal_diff('old', old, 'new', new, output)
 
78
    internal_diff('old', old, 'new', new, output, allow_binary)
7
79
    output.seek(0, 0)
8
80
    return output.readlines()
9
81
 
 
82
 
 
83
def external_udiff_lines(old, new, use_stringio=False):
 
84
    if use_stringio:
 
85
        # StringIO has no fileno, so it tests a different codepath
 
86
        output = StringIO()
 
87
    else:
 
88
        output = TemporaryFile()
 
89
    try:
 
90
        external_diff('old', old, 'new', new, output, diff_opts=['-u'])
 
91
    except NoDiff:
 
92
        raise TestSkipped('external "diff" not present to test')
 
93
    output.seek(0, 0)
 
94
    lines = output.readlines()
 
95
    output.close()
 
96
    return lines
 
97
 
 
98
 
10
99
class TestDiff(TestCase):
 
100
 
11
101
    def test_add_nl(self):
12
102
        """diff generates a valid diff for patches that add a newline"""
13
103
        lines = udiff_lines(['boo'], ['boo\n'])
47
137
        self.assert_('@@' in lines[2][2:])
48
138
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
49
139
 
 
140
    def test_binary_lines(self):
 
141
        self.assertRaises(BinaryFile, udiff_lines, [1023 * 'a' + '\x00'], [])
 
142
        self.assertRaises(BinaryFile, udiff_lines, [], [1023 * 'a' + '\x00'])
 
143
        udiff_lines([1023 * 'a' + '\x00'], [], allow_binary=True)
 
144
        udiff_lines([], [1023 * 'a' + '\x00'], allow_binary=True)
 
145
 
 
146
    def test_external_diff(self):
 
147
        lines = external_udiff_lines(['boo\n'], ['goo\n'])
 
148
        self.check_patch(lines)
 
149
        self.assertEqual('\n', lines[-1])
 
150
 
 
151
    def test_external_diff_no_fileno(self):
 
152
        # Make sure that we can handle not having a fileno, even
 
153
        # if the diff is large
 
154
        lines = external_udiff_lines(['boo\n']*10000,
 
155
                                     ['goo\n']*10000,
 
156
                                     use_stringio=True)
 
157
        self.check_patch(lines)
 
158
 
 
159
    def test_external_diff_binary_lang_c(self):
 
160
        old_env = {}
 
161
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
 
162
            old_env[lang] = osutils.set_or_unset_env(lang, 'C')
 
163
        try:
 
164
            lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
165
            # Older versions of diffutils say "Binary files", newer
 
166
            # versions just say "Files".
 
167
            self.assertContainsRe(lines[0],
 
168
                                  '(Binary f|F)iles old and new differ\n')
 
169
            self.assertEquals(lines[1:], ['\n'])
 
170
        finally:
 
171
            for lang, old_val in old_env.iteritems():
 
172
                osutils.set_or_unset_env(lang, old_val)
 
173
 
 
174
    def test_no_external_diff(self):
 
175
        """Check that NoDiff is raised when diff is not available"""
 
176
        # Use os.environ['PATH'] to make sure no 'diff' command is available
 
177
        orig_path = os.environ['PATH']
 
178
        try:
 
179
            os.environ['PATH'] = ''
 
180
            self.assertRaises(NoDiff, external_diff,
 
181
                              'old', ['boo\n'], 'new', ['goo\n'],
 
182
                              StringIO(), diff_opts=['-u'])
 
183
        finally:
 
184
            os.environ['PATH'] = orig_path
 
185
        
 
186
    def test_internal_diff_default(self):
 
187
        # Default internal diff encoding is utf8
 
188
        output = StringIO()
 
189
        internal_diff(u'old_\xb5', ['old_text\n'],
 
190
                    u'new_\xe5', ['new_text\n'], output)
 
191
        lines = output.getvalue().splitlines(True)
 
192
        self.check_patch(lines)
 
193
        self.assertEquals(['--- old_\xc2\xb5\n',
 
194
                           '+++ new_\xc3\xa5\n',
 
195
                           '@@ -1,1 +1,1 @@\n',
 
196
                           '-old_text\n',
 
197
                           '+new_text\n',
 
198
                           '\n',
 
199
                          ]
 
200
                          , lines)
 
201
 
 
202
    def test_internal_diff_utf8(self):
 
203
        output = StringIO()
 
204
        internal_diff(u'old_\xb5', ['old_text\n'],
 
205
                    u'new_\xe5', ['new_text\n'], output,
 
206
                    path_encoding='utf8')
 
207
        lines = output.getvalue().splitlines(True)
 
208
        self.check_patch(lines)
 
209
        self.assertEquals(['--- old_\xc2\xb5\n',
 
210
                           '+++ new_\xc3\xa5\n',
 
211
                           '@@ -1,1 +1,1 @@\n',
 
212
                           '-old_text\n',
 
213
                           '+new_text\n',
 
214
                           '\n',
 
215
                          ]
 
216
                          , lines)
 
217
 
 
218
    def test_internal_diff_iso_8859_1(self):
 
219
        output = StringIO()
 
220
        internal_diff(u'old_\xb5', ['old_text\n'],
 
221
                    u'new_\xe5', ['new_text\n'], output,
 
222
                    path_encoding='iso-8859-1')
 
223
        lines = output.getvalue().splitlines(True)
 
224
        self.check_patch(lines)
 
225
        self.assertEquals(['--- old_\xb5\n',
 
226
                           '+++ new_\xe5\n',
 
227
                           '@@ -1,1 +1,1 @@\n',
 
228
                           '-old_text\n',
 
229
                           '+new_text\n',
 
230
                           '\n',
 
231
                          ]
 
232
                          , lines)
 
233
 
 
234
    def test_internal_diff_returns_bytes(self):
 
235
        import StringIO
 
236
        output = StringIO.StringIO()
 
237
        internal_diff(u'old_\xb5', ['old_text\n'],
 
238
                    u'new_\xe5', ['new_text\n'], output)
 
239
        self.failUnless(isinstance(output.getvalue(), str),
 
240
            'internal_diff should return bytestrings')
 
241
 
 
242
 
 
243
class TestDiffFiles(TestCaseInTempDir):
 
244
 
 
245
    def test_external_diff_binary(self):
 
246
        """The output when using external diff should use diff's i18n error"""
 
247
        # Make sure external_diff doesn't fail in the current LANG
 
248
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
249
 
 
250
        cmd = ['diff', '-u', '--binary', 'old', 'new']
 
251
        open('old', 'wb').write('\x00foobar\n')
 
252
        open('new', 'wb').write('foo\x00bar\n')
 
253
        pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
 
254
                                     stdin=subprocess.PIPE)
 
255
        out, err = pipe.communicate()
 
256
        # Diff returns '2' on Binary files.
 
257
        self.assertEqual(2, pipe.returncode)
 
258
        # We should output whatever diff tells us, plus a trailing newline
 
259
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
 
260
 
 
261
 
 
262
class TestShowDiffTreesHelper(TestCaseWithTransport):
 
263
    """Has a helper for running show_diff_trees"""
 
264
 
 
265
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
 
266
        output = StringIO()
 
267
        if working_tree is not None:
 
268
            extra_trees = (working_tree,)
 
269
        else:
 
270
            extra_trees = ()
 
271
        show_diff_trees(tree1, tree2, output, 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):
 
278
 
 
279
    def setUp(self):
 
280
        super(TestDiffDates, self).setUp()
 
281
        self.wt = self.make_branch_and_tree('.')
 
282
        self.b = self.wt.branch
 
283
        self.build_tree_contents([
 
284
            ('file1', 'file1 contents at rev 1\n'),
 
285
            ('file2', 'file2 contents at rev 1\n')
 
286
            ])
 
287
        self.wt.add(['file1', 'file2'])
 
288
        self.wt.commit(
 
289
            message='Revision 1',
 
290
            timestamp=1143849600, # 2006-04-01 00:00:00 UTC
 
291
            timezone=0,
 
292
            rev_id='rev-1')
 
293
        self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
 
294
        self.wt.commit(
 
295
            message='Revision 2',
 
296
            timestamp=1143936000, # 2006-04-02 00:00:00 UTC
 
297
            timezone=28800,
 
298
            rev_id='rev-2')
 
299
        self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
 
300
        self.wt.commit(
 
301
            message='Revision 3',
 
302
            timestamp=1144022400, # 2006-04-03 00:00:00 UTC
 
303
            timezone=-3600,
 
304
            rev_id='rev-3')
 
305
        self.wt.remove(['file2'])
 
306
        self.wt.commit(
 
307
            message='Revision 4',
 
308
            timestamp=1144108800, # 2006-04-04 00:00:00 UTC
 
309
            timezone=0,
 
310
            rev_id='rev-4')
 
311
        self.build_tree_contents([
 
312
            ('file1', 'file1 contents in working tree\n')
 
313
            ])
 
314
        # set the date stamps for files in the working tree to known values
 
315
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
 
316
 
 
317
    def test_diff_rev_tree_working_tree(self):
 
318
        output = self.get_diff(self.wt.basis_tree(), self.wt)
 
319
        # note that the date for old/file1 is from rev 2 rather than from
 
320
        # the basis revision (rev 4)
 
321
        self.assertEqualDiff(output, '''\
 
322
=== modified file 'file1'
 
323
--- old/file1\t2006-04-02 00:00:00 +0000
 
324
+++ new/file1\t2006-04-05 00:00:00 +0000
 
325
@@ -1,1 +1,1 @@
 
326
-file1 contents at rev 2
 
327
+file1 contents in working tree
 
328
 
 
329
''')
 
330
 
 
331
    def test_diff_rev_tree_rev_tree(self):
 
332
        tree1 = self.b.repository.revision_tree('rev-2')
 
333
        tree2 = self.b.repository.revision_tree('rev-3')
 
334
        output = self.get_diff(tree1, tree2)
 
335
        self.assertEqualDiff(output, '''\
 
336
=== modified file 'file2'
 
337
--- old/file2\t2006-04-01 00:00:00 +0000
 
338
+++ new/file2\t2006-04-03 00:00:00 +0000
 
339
@@ -1,1 +1,1 @@
 
340
-file2 contents at rev 1
 
341
+file2 contents at rev 3
 
342
 
 
343
''')
 
344
        
 
345
    def test_diff_add_files(self):
 
346
        tree1 = self.b.repository.revision_tree(None)
 
347
        tree2 = self.b.repository.revision_tree('rev-1')
 
348
        output = self.get_diff(tree1, tree2)
 
349
        # the files have the epoch time stamp for the tree in which
 
350
        # they don't exist.
 
351
        self.assertEqualDiff(output, '''\
 
352
=== added file 'file1'
 
353
--- old/file1\t1970-01-01 00:00:00 +0000
 
354
+++ new/file1\t2006-04-01 00:00:00 +0000
 
355
@@ -0,0 +1,1 @@
 
356
+file1 contents at rev 1
 
357
 
 
358
=== added file 'file2'
 
359
--- old/file2\t1970-01-01 00:00:00 +0000
 
360
+++ new/file2\t2006-04-01 00:00:00 +0000
 
361
@@ -0,0 +1,1 @@
 
362
+file2 contents at rev 1
 
363
 
 
364
''')
 
365
 
 
366
    def test_diff_remove_files(self):
 
367
        tree1 = self.b.repository.revision_tree('rev-3')
 
368
        tree2 = self.b.repository.revision_tree('rev-4')
 
369
        output = self.get_diff(tree1, tree2)
 
370
        # the file has the epoch time stamp for the tree in which
 
371
        # it doesn't exist.
 
372
        self.assertEqualDiff(output, '''\
 
373
=== removed file 'file2'
 
374
--- old/file2\t2006-04-03 00:00:00 +0000
 
375
+++ new/file2\t1970-01-01 00:00:00 +0000
 
376
@@ -1,1 +0,0 @@
 
377
-file2 contents at rev 3
 
378
 
 
379
''')
 
380
 
 
381
    def test_show_diff_specified(self):
 
382
        """A working tree filename can be used to identify a file"""
 
383
        self.wt.rename_one('file1', 'file1b')
 
384
        old_tree = self.b.repository.revision_tree('rev-1')
 
385
        new_tree = self.b.repository.revision_tree('rev-4')
 
386
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'], 
 
387
                            working_tree=self.wt)
 
388
        self.assertContainsRe(out, 'file1\t')
 
389
 
 
390
    def test_recursive_diff(self):
 
391
        """Children of directories are matched"""
 
392
        os.mkdir('dir1')
 
393
        os.mkdir('dir2')
 
394
        self.wt.add(['dir1', 'dir2'])
 
395
        self.wt.rename_one('file1', 'dir1/file1')
 
396
        old_tree = self.b.repository.revision_tree('rev-1')
 
397
        new_tree = self.b.repository.revision_tree('rev-4')
 
398
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'], 
 
399
                            working_tree=self.wt)
 
400
        self.assertContainsRe(out, 'file1\t')
 
401
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'], 
 
402
                            working_tree=self.wt)
 
403
        self.assertNotContainsRe(out, 'file1\t')
 
404
 
 
405
 
 
406
 
 
407
class TestShowDiffTrees(TestShowDiffTreesHelper):
 
408
    """Direct tests for show_diff_trees"""
 
409
 
 
410
    def test_modified_file(self):
 
411
        """Test when a file is modified."""
 
412
        tree = self.make_branch_and_tree('tree')
 
413
        self.build_tree_contents([('tree/file', 'contents\n')])
 
414
        tree.add(['file'], ['file-id'])
 
415
        tree.commit('one', rev_id='rev-1')
 
416
 
 
417
        self.build_tree_contents([('tree/file', 'new contents\n')])
 
418
        diff = self.get_diff(tree.basis_tree(), tree)
 
419
        self.assertContainsRe(diff, "=== modified file 'file'\n")
 
420
        self.assertContainsRe(diff, '--- old/file\t')
 
421
        self.assertContainsRe(diff, '\\+\\+\\+ new/file\t')
 
422
        self.assertContainsRe(diff, '-contents\n'
 
423
                                    '\\+new contents\n')
 
424
 
 
425
    def test_modified_file_in_renamed_dir(self):
 
426
        """Test when a file is modified in a renamed directory."""
 
427
        tree = self.make_branch_and_tree('tree')
 
428
        self.build_tree(['tree/dir/'])
 
429
        self.build_tree_contents([('tree/dir/file', 'contents\n')])
 
430
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
 
431
        tree.commit('one', rev_id='rev-1')
 
432
 
 
433
        tree.rename_one('dir', 'other')
 
434
        self.build_tree_contents([('tree/other/file', 'new contents\n')])
 
435
        diff = self.get_diff(tree.basis_tree(), tree)
 
436
        self.assertContainsRe(diff, "=== renamed directory 'dir' => 'other'\n")
 
437
        self.assertContainsRe(diff, "=== modified file 'other/file'\n")
 
438
        # XXX: This is technically incorrect, because it used to be at another
 
439
        # location. What to do?
 
440
        self.assertContainsRe(diff, '--- old/dir/file\t')
 
441
        self.assertContainsRe(diff, '\\+\\+\\+ new/other/file\t')
 
442
        self.assertContainsRe(diff, '-contents\n'
 
443
                                    '\\+new contents\n')
 
444
 
 
445
    def test_renamed_directory(self):
 
446
        """Test when only a directory is only renamed."""
 
447
        tree = self.make_branch_and_tree('tree')
 
448
        self.build_tree(['tree/dir/'])
 
449
        self.build_tree_contents([('tree/dir/file', 'contents\n')])
 
450
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
 
451
        tree.commit('one', rev_id='rev-1')
 
452
 
 
453
        tree.rename_one('dir', 'newdir')
 
454
        diff = self.get_diff(tree.basis_tree(), tree)
 
455
        # Renaming a directory should be a single "you renamed this dir" even
 
456
        # when there are files inside.
 
457
        self.assertEqual("=== renamed directory 'dir' => 'newdir'\n", diff)
 
458
 
 
459
    def test_renamed_file(self):
 
460
        """Test when a file is only renamed."""
 
461
        tree = self.make_branch_and_tree('tree')
 
462
        self.build_tree_contents([('tree/file', 'contents\n')])
 
463
        tree.add(['file'], ['file-id'])
 
464
        tree.commit('one', rev_id='rev-1')
 
465
 
 
466
        tree.rename_one('file', 'newname')
 
467
        diff = self.get_diff(tree.basis_tree(), tree)
 
468
        self.assertContainsRe(diff, "=== renamed file 'file' => 'newname'\n")
 
469
        # We shouldn't have a --- or +++ line, because there is no content
 
470
        # change
 
471
        self.assertNotContainsRe(diff, '---')
 
472
 
 
473
    def test_renamed_and_modified_file(self):
 
474
        """Test when a file is only renamed."""
 
475
        tree = self.make_branch_and_tree('tree')
 
476
        self.build_tree_contents([('tree/file', 'contents\n')])
 
477
        tree.add(['file'], ['file-id'])
 
478
        tree.commit('one', rev_id='rev-1')
 
479
 
 
480
        tree.rename_one('file', 'newname')
 
481
        self.build_tree_contents([('tree/newname', 'new contents\n')])
 
482
        diff = self.get_diff(tree.basis_tree(), tree)
 
483
        self.assertContainsRe(diff, "=== renamed file 'file' => 'newname'\n")
 
484
        self.assertContainsRe(diff, '--- old/file\t')
 
485
        self.assertContainsRe(diff, '\\+\\+\\+ new/newname\t')
 
486
        self.assertContainsRe(diff, '-contents\n'
 
487
                                    '\\+new contents\n')
 
488
 
 
489
    def test_binary_unicode_filenames(self):
 
490
        """Test that contents of files are *not* encoded in UTF-8 when there
 
491
        is a binary file in the diff.
 
492
        """
 
493
        # See https://bugs.launchpad.net/bugs/110092.
 
494
        self.requireFeature(UnicodeFilename)
 
495
 
 
496
        # This bug isn't triggered with cStringIO.
 
497
        from StringIO import StringIO
 
498
        tree = self.make_branch_and_tree('tree')
 
499
        alpha, omega = u'\u03b1', u'\u03c9'
 
500
        alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
 
501
        self.build_tree_contents(
 
502
            [('tree/' + alpha, chr(0)),
 
503
             ('tree/' + omega,
 
504
              ('The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
 
505
        tree.add([alpha], ['file-id'])
 
506
        tree.add([omega], ['file-id-2'])
 
507
        diff_content = StringIO()
 
508
        show_diff_trees(tree.basis_tree(), tree, diff_content)
 
509
        diff = diff_content.getvalue()
 
510
        self.assertContainsRe(diff, r"=== added file '%s'" % alpha_utf8)
 
511
        self.assertContainsRe(
 
512
            diff, "Binary files a/%s.*and b/%s.* differ\n" % (alpha_utf8, alpha_utf8))
 
513
        self.assertContainsRe(diff, r"=== added file '%s'" % omega_utf8)
 
514
        self.assertContainsRe(diff, r"--- a/%s" % (omega_utf8,))
 
515
        self.assertContainsRe(diff, r"\+\+\+ b/%s" % (omega_utf8,))
 
516
 
 
517
    def test_unicode_filename(self):
 
518
        """Test when the filename are unicode."""
 
519
        self.requireFeature(UnicodeFilename)
 
520
 
 
521
        alpha, omega = u'\u03b1', u'\u03c9'
 
522
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
 
523
 
 
524
        tree = self.make_branch_and_tree('tree')
 
525
        self.build_tree_contents([('tree/ren_'+alpha, 'contents\n')])
 
526
        tree.add(['ren_'+alpha], ['file-id-2'])
 
527
        self.build_tree_contents([('tree/del_'+alpha, 'contents\n')])
 
528
        tree.add(['del_'+alpha], ['file-id-3'])
 
529
        self.build_tree_contents([('tree/mod_'+alpha, 'contents\n')])
 
530
        tree.add(['mod_'+alpha], ['file-id-4'])
 
531
 
 
532
        tree.commit('one', rev_id='rev-1')
 
533
 
 
534
        tree.rename_one('ren_'+alpha, 'ren_'+omega)
 
535
        tree.remove('del_'+alpha)
 
536
        self.build_tree_contents([('tree/add_'+alpha, 'contents\n')])
 
537
        tree.add(['add_'+alpha], ['file-id'])
 
538
        self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
 
539
 
 
540
        diff = self.get_diff(tree.basis_tree(), tree)
 
541
        self.assertContainsRe(diff,
 
542
                "=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
 
543
        self.assertContainsRe(diff, "=== added file 'add_%s'"%autf8)
 
544
        self.assertContainsRe(diff, "=== modified file 'mod_%s'"%autf8)
 
545
        self.assertContainsRe(diff, "=== removed file 'del_%s'"%autf8)
 
546
 
 
547
class TestPatienceDiffLib(TestCase):
 
548
 
 
549
    def setUp(self):
 
550
        super(TestPatienceDiffLib, self).setUp()
 
551
        self._unique_lcs = bzrlib._patiencediff_py.unique_lcs_py
 
552
        self._recurse_matches = bzrlib._patiencediff_py.recurse_matches_py
 
553
        self._PatienceSequenceMatcher = \
 
554
            bzrlib._patiencediff_py.PatienceSequenceMatcher_py
 
555
 
 
556
    def test_unique_lcs(self):
 
557
        unique_lcs = self._unique_lcs
 
558
        self.assertEquals(unique_lcs('', ''), [])
 
559
        self.assertEquals(unique_lcs('', 'a'), [])
 
560
        self.assertEquals(unique_lcs('a', ''), [])
 
561
        self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
 
562
        self.assertEquals(unique_lcs('a', 'b'), [])
 
563
        self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
 
564
        self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
 
565
        self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
 
566
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1), 
 
567
                                                         (3,3), (4,4)])
 
568
        self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
 
569
 
 
570
    def test_recurse_matches(self):
 
571
        def test_one(a, b, matches):
 
572
            test_matches = []
 
573
            self._recurse_matches(
 
574
                a, b, 0, 0, len(a), len(b), test_matches, 10)
 
575
            self.assertEquals(test_matches, matches)
 
576
 
 
577
        test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
 
578
                 [(0, 0), (2, 2), (4, 4)])
 
579
        test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
 
580
                 [(0, 0), (2, 1), (4, 2)])
 
581
        # Even though 'bc' is not unique globally, and is surrounded by
 
582
        # non-matching lines, we should still match, because they are locally
 
583
        # unique
 
584
        test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
 
585
                                          (4, 6), (5, 7), (6, 8)])
 
586
 
 
587
        # recurse_matches doesn't match non-unique 
 
588
        # lines surrounded by bogus text.
 
589
        # The update has been done in patiencediff.SequenceMatcher instead
 
590
 
 
591
        # This is what it could be
 
592
        #test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
 
593
 
 
594
        # This is what it currently gives:
 
595
        test_one('aBccDe', 'abccde', [(0,0), (5,5)])
 
596
 
 
597
    def test_matching_blocks(self):
 
598
        def chk_blocks(a, b, expected_blocks):
 
599
            # difflib always adds a signature of the total
 
600
            # length, with no matching entries at the end
 
601
            s = self._PatienceSequenceMatcher(None, a, b)
 
602
            blocks = s.get_matching_blocks()
 
603
            self.assertEquals((len(a), len(b), 0), blocks[-1])
 
604
            self.assertEquals(expected_blocks, blocks[:-1])
 
605
 
 
606
        # Some basic matching tests
 
607
        chk_blocks('', '', [])
 
608
        chk_blocks([], [], [])
 
609
        chk_blocks('abc', '', [])
 
610
        chk_blocks('', 'abc', [])
 
611
        chk_blocks('abcd', 'abcd', [(0, 0, 4)])
 
612
        chk_blocks('abcd', 'abce', [(0, 0, 3)])
 
613
        chk_blocks('eabc', 'abce', [(1, 0, 3)])
 
614
        chk_blocks('eabce', 'abce', [(1, 0, 4)])
 
615
        chk_blocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
 
616
        chk_blocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
 
617
        chk_blocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
 
618
        # This may check too much, but it checks to see that 
 
619
        # a copied block stays attached to the previous section,
 
620
        # not the later one.
 
621
        # difflib would tend to grab the trailing longest match
 
622
        # which would make the diff not look right
 
623
        chk_blocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
624
                   [(0, 0, 6), (6, 11, 10)])
 
625
 
 
626
        # make sure it supports passing in lists
 
627
        chk_blocks(
 
628
                   ['hello there\n',
 
629
                    'world\n',
 
630
                    'how are you today?\n'],
 
631
                   ['hello there\n',
 
632
                    'how are you today?\n'],
 
633
                [(0, 0, 1), (2, 1, 1)])
 
634
 
 
635
        # non unique lines surrounded by non-matching lines
 
636
        # won't be found
 
637
        chk_blocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
 
638
 
 
639
        # But they only need to be locally unique
 
640
        chk_blocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
 
641
 
 
642
        # non unique blocks won't be matched
 
643
        chk_blocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
 
644
 
 
645
        # but locally unique ones will
 
646
        chk_blocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
 
647
                                              (5,4,1), (7,5,2), (10,8,1)])
 
648
 
 
649
        chk_blocks('abbabbXd', 'cabbabxd', [(7,7,1)])
 
650
        chk_blocks('abbabbbb', 'cabbabbc', [])
 
651
        chk_blocks('bbbbbbbb', 'cbbbbbbc', [])
 
652
 
 
653
    def test_opcodes(self):
 
654
        def chk_ops(a, b, expected_codes):
 
655
            s = self._PatienceSequenceMatcher(None, a, b)
 
656
            self.assertEquals(expected_codes, s.get_opcodes())
 
657
 
 
658
        chk_ops('', '', [])
 
659
        chk_ops([], [], [])
 
660
        chk_ops('abc', '', [('delete', 0,3, 0,0)])
 
661
        chk_ops('', 'abc', [('insert', 0,0, 0,3)])
 
662
        chk_ops('abcd', 'abcd', [('equal',    0,4, 0,4)])
 
663
        chk_ops('abcd', 'abce', [('equal',   0,3, 0,3),
 
664
                                 ('replace', 3,4, 3,4)
 
665
                                ])
 
666
        chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
 
667
                                 ('equal',  1,4, 0,3),
 
668
                                 ('insert', 4,4, 3,4)
 
669
                                ])
 
670
        chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
 
671
                                  ('equal',  1,5, 0,4)
 
672
                                 ])
 
673
        chk_ops('abcde', 'abXde', [('equal',   0,2, 0,2),
 
674
                                   ('replace', 2,3, 2,3),
 
675
                                   ('equal',   3,5, 3,5)
 
676
                                  ])
 
677
        chk_ops('abcde', 'abXYZde', [('equal',   0,2, 0,2),
 
678
                                     ('replace', 2,3, 2,5),
 
679
                                     ('equal',   3,5, 5,7)
 
680
                                    ])
 
681
        chk_ops('abde', 'abXYZde', [('equal',  0,2, 0,2),
 
682
                                    ('insert', 2,2, 2,5),
 
683
                                    ('equal',  2,4, 5,7)
 
684
                                   ])
 
685
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
686
                [('equal',  0,6,  0,6),
 
687
                 ('insert', 6,6,  6,11),
 
688
                 ('equal',  6,16, 11,21)
 
689
                ])
 
690
        chk_ops(
 
691
                [ 'hello there\n'
 
692
                , 'world\n'
 
693
                , 'how are you today?\n'],
 
694
                [ 'hello there\n'
 
695
                , 'how are you today?\n'],
 
696
                [('equal',  0,1, 0,1),
 
697
                 ('delete', 1,2, 1,1),
 
698
                 ('equal',  2,3, 1,2),
 
699
                ])
 
700
        chk_ops('aBccDe', 'abccde', 
 
701
                [('equal',   0,1, 0,1),
 
702
                 ('replace', 1,5, 1,5),
 
703
                 ('equal',   5,6, 5,6),
 
704
                ])
 
705
        chk_ops('aBcDec', 'abcdec', 
 
706
                [('equal',   0,1, 0,1),
 
707
                 ('replace', 1,2, 1,2),
 
708
                 ('equal',   2,3, 2,3),
 
709
                 ('replace', 3,4, 3,4),
 
710
                 ('equal',   4,6, 4,6),
 
711
                ])
 
712
        chk_ops('aBcdEcdFg', 'abcdecdfg', 
 
713
                [('equal',   0,1, 0,1),
 
714
                 ('replace', 1,8, 1,8),
 
715
                 ('equal',   8,9, 8,9)
 
716
                ])
 
717
        chk_ops('aBcdEeXcdFg', 'abcdecdfg', 
 
718
                [('equal',   0,1, 0,1),
 
719
                 ('replace', 1,2, 1,2),
 
720
                 ('equal',   2,4, 2,4),
 
721
                 ('delete', 4,5, 4,4),
 
722
                 ('equal',   5,6, 4,5),
 
723
                 ('delete', 6,7, 5,5),
 
724
                 ('equal',   7,9, 5,7),
 
725
                 ('replace', 9,10, 7,8),
 
726
                 ('equal',   10,11, 8,9)
 
727
                ])
 
728
 
 
729
    def test_grouped_opcodes(self):
 
730
        def chk_ops(a, b, expected_codes, n=3):
 
731
            s = self._PatienceSequenceMatcher(None, a, b)
 
732
            self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
 
733
 
 
734
        chk_ops('', '', [])
 
735
        chk_ops([], [], [])
 
736
        chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
 
737
        chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
 
738
        chk_ops('abcd', 'abcd', [])
 
739
        chk_ops('abcd', 'abce', [[('equal',   0,3, 0,3),
 
740
                                  ('replace', 3,4, 3,4)
 
741
                                 ]])
 
742
        chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
 
743
                                 ('equal',  1,4, 0,3),
 
744
                                 ('insert', 4,4, 3,4)
 
745
                                ]])
 
746
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
747
                [[('equal',  3,6, 3,6),
 
748
                  ('insert', 6,6, 6,11),
 
749
                  ('equal',  6,9, 11,14)
 
750
                  ]])
 
751
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
752
                [[('equal',  2,6, 2,6),
 
753
                  ('insert', 6,6, 6,11),
 
754
                  ('equal',  6,10, 11,15)
 
755
                  ]], 4)
 
756
        chk_ops('Xabcdef', 'abcdef',
 
757
                [[('delete', 0,1, 0,0),
 
758
                  ('equal',  1,4, 0,3)
 
759
                  ]])
 
760
        chk_ops('abcdef', 'abcdefX',
 
761
                [[('equal',  3,6, 3,6),
 
762
                  ('insert', 6,6, 6,7)
 
763
                  ]])
 
764
 
 
765
 
 
766
    def test_multiple_ranges(self):
 
767
        # There was an earlier bug where we used a bad set of ranges,
 
768
        # this triggers that specific bug, to make sure it doesn't regress
 
769
        def chk_blocks(a, b, expected_blocks):
 
770
            # difflib always adds a signature of the total
 
771
            # length, with no matching entries at the end
 
772
            s = self._PatienceSequenceMatcher(None, a, b)
 
773
            blocks = s.get_matching_blocks()
 
774
            x = blocks.pop()
 
775
            self.assertEquals(x, (len(a), len(b), 0))
 
776
            self.assertEquals(expected_blocks, blocks)
 
777
 
 
778
        chk_blocks('abcdefghijklmnop'
 
779
                 , 'abcXghiYZQRSTUVWXYZijklmnop'
 
780
                 , [(0, 0, 3), (6, 4, 3), (9, 20, 7)])
 
781
 
 
782
        chk_blocks('ABCd efghIjk  L'
 
783
                 , 'AxyzBCn mo pqrstuvwI1 2  L'
 
784
                 , [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
785
 
 
786
        # These are rot13 code snippets.
 
787
        chk_blocks('''\
 
788
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
 
789
    """
 
790
    gnxrf_netf = ['svyr*']
 
791
    gnxrf_bcgvbaf = ['ab-erphefr']
 
792
  
 
793
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
 
794
        sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
 
795
        vs vf_dhvrg():
 
796
            ercbegre = nqq_ercbegre_ahyy
 
797
        ryfr:
 
798
            ercbegre = nqq_ercbegre_cevag
 
799
        fzneg_nqq(svyr_yvfg, abg ab_erphefr, ercbegre)
 
800
 
 
801
 
 
802
pynff pzq_zxqve(Pbzznaq):
 
803
'''.splitlines(True), '''\
 
804
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
 
805
 
 
806
    --qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl 
 
807
    nqq gurz.
 
808
    """
 
809
    gnxrf_netf = ['svyr*']
 
810
    gnxrf_bcgvbaf = ['ab-erphefr', 'qel-eha']
 
811
 
 
812
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr, qel_eha=Snyfr):
 
813
        vzcbeg omeyvo.nqq
 
814
 
 
815
        vs qel_eha:
 
816
            vs vf_dhvrg():
 
817
                # Guvf vf cbvagyrff, ohg V'q engure abg envfr na reebe
 
818
                npgvba = omeyvo.nqq.nqq_npgvba_ahyy
 
819
            ryfr:
 
820
  npgvba = omeyvo.nqq.nqq_npgvba_cevag
 
821
        ryvs vf_dhvrg():
 
822
            npgvba = omeyvo.nqq.nqq_npgvba_nqq
 
823
        ryfr:
 
824
       npgvba = omeyvo.nqq.nqq_npgvba_nqq_naq_cevag
 
825
 
 
826
        omeyvo.nqq.fzneg_nqq(svyr_yvfg, abg ab_erphefr, npgvba)
 
827
 
 
828
 
 
829
pynff pzq_zxqve(Pbzznaq):
 
830
'''.splitlines(True)
 
831
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
832
 
 
833
    def test_patience_unified_diff(self):
 
834
        txt_a = ['hello there\n',
 
835
                 'world\n',
 
836
                 'how are you today?\n']
 
837
        txt_b = ['hello there\n',
 
838
                 'how are you today?\n']
 
839
        unified_diff = bzrlib.patiencediff.unified_diff
 
840
        psm = self._PatienceSequenceMatcher
 
841
        self.assertEquals([ '---  \n',
 
842
                           '+++  \n',
 
843
                           '@@ -1,3 +1,2 @@\n',
 
844
                           ' hello there\n',
 
845
                           '-world\n',
 
846
                           ' how are you today?\n'
 
847
                          ]
 
848
                          , list(unified_diff(txt_a, txt_b,
 
849
                                 sequencematcher=psm)))
 
850
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
 
851
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
 
852
        # This is the result with LongestCommonSubstring matching
 
853
        self.assertEquals(['---  \n',
 
854
                           '+++  \n',
 
855
                           '@@ -1,6 +1,11 @@\n',
 
856
                           ' a\n',
 
857
                           ' b\n',
 
858
                           ' c\n',
 
859
                           '+d\n',
 
860
                           '+e\n',
 
861
                           '+f\n',
 
862
                           '+x\n',
 
863
                           '+y\n',
 
864
                           ' d\n',
 
865
                           ' e\n',
 
866
                           ' f\n']
 
867
                          , list(unified_diff(txt_a, txt_b)))
 
868
        # And the patience diff
 
869
        self.assertEquals(['---  \n',
 
870
                           '+++  \n',
 
871
                           '@@ -4,6 +4,11 @@\n',
 
872
                           ' d\n',
 
873
                           ' e\n',
 
874
                           ' f\n',
 
875
                           '+x\n',
 
876
                           '+y\n',
 
877
                           '+d\n',
 
878
                           '+e\n',
 
879
                           '+f\n',
 
880
                           ' g\n',
 
881
                           ' h\n',
 
882
                           ' i\n',
 
883
                          ]
 
884
                          , list(unified_diff(txt_a, txt_b,
 
885
                                 sequencematcher=psm)))
 
886
 
 
887
 
 
888
class TestPatienceDiffLib_c(TestPatienceDiffLib):
 
889
 
 
890
    _test_needs_features = [CompiledPatienceDiffFeature]
 
891
 
 
892
    def setUp(self):
 
893
        super(TestPatienceDiffLib_c, self).setUp()
 
894
        import bzrlib._patiencediff_c
 
895
        self._unique_lcs = bzrlib._patiencediff_c.unique_lcs_c
 
896
        self._recurse_matches = bzrlib._patiencediff_c.recurse_matches_c
 
897
        self._PatienceSequenceMatcher = \
 
898
            bzrlib._patiencediff_c.PatienceSequenceMatcher_c
 
899
 
 
900
 
 
901
class TestPatienceDiffLibFiles(TestCaseInTempDir):
 
902
 
 
903
    def setUp(self):
 
904
        super(TestPatienceDiffLibFiles, self).setUp()
 
905
        self._PatienceSequenceMatcher = \
 
906
            bzrlib._patiencediff_py.PatienceSequenceMatcher_py
 
907
 
 
908
    def test_patience_unified_diff_files(self):
 
909
        txt_a = ['hello there\n',
 
910
                 'world\n',
 
911
                 'how are you today?\n']
 
912
        txt_b = ['hello there\n',
 
913
                 'how are you today?\n']
 
914
        open('a1', 'wb').writelines(txt_a)
 
915
        open('b1', 'wb').writelines(txt_b)
 
916
 
 
917
        unified_diff_files = bzrlib.patiencediff.unified_diff_files
 
918
        psm = self._PatienceSequenceMatcher
 
919
        self.assertEquals(['--- a1 \n',
 
920
                           '+++ b1 \n',
 
921
                           '@@ -1,3 +1,2 @@\n',
 
922
                           ' hello there\n',
 
923
                           '-world\n',
 
924
                           ' how are you today?\n',
 
925
                          ]
 
926
                          , list(unified_diff_files('a1', 'b1',
 
927
                                 sequencematcher=psm)))
 
928
 
 
929
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
 
930
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
 
931
        open('a2', 'wb').writelines(txt_a)
 
932
        open('b2', 'wb').writelines(txt_b)
 
933
 
 
934
        # This is the result with LongestCommonSubstring matching
 
935
        self.assertEquals(['--- a2 \n',
 
936
                           '+++ b2 \n',
 
937
                           '@@ -1,6 +1,11 @@\n',
 
938
                           ' a\n',
 
939
                           ' b\n',
 
940
                           ' c\n',
 
941
                           '+d\n',
 
942
                           '+e\n',
 
943
                           '+f\n',
 
944
                           '+x\n',
 
945
                           '+y\n',
 
946
                           ' d\n',
 
947
                           ' e\n',
 
948
                           ' f\n']
 
949
                          , list(unified_diff_files('a2', 'b2')))
 
950
 
 
951
        # And the patience diff
 
952
        self.assertEquals(['--- a2 \n',
 
953
                           '+++ b2 \n',
 
954
                           '@@ -4,6 +4,11 @@\n',
 
955
                           ' d\n',
 
956
                           ' e\n',
 
957
                           ' f\n',
 
958
                           '+x\n',
 
959
                           '+y\n',
 
960
                           '+d\n',
 
961
                           '+e\n',
 
962
                           '+f\n',
 
963
                           ' g\n',
 
964
                           ' h\n',
 
965
                           ' i\n',
 
966
                          ]
 
967
                          , list(unified_diff_files('a2', 'b2',
 
968
                                 sequencematcher=psm)))
 
969
 
 
970
 
 
971
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
 
972
 
 
973
    _test_needs_features = [CompiledPatienceDiffFeature]
 
974
 
 
975
    def setUp(self):
 
976
        super(TestPatienceDiffLibFiles_c, self).setUp()
 
977
        import bzrlib._patiencediff_c
 
978
        self._PatienceSequenceMatcher = \
 
979
            bzrlib._patiencediff_c.PatienceSequenceMatcher_c
 
980
 
 
981
 
 
982
class TestUsingCompiledIfAvailable(TestCase):
 
983
 
 
984
    def test_PatienceSequenceMatcher(self):
 
985
        if CompiledPatienceDiffFeature.available():
 
986
            from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
 
987
            self.assertIs(PatienceSequenceMatcher_c,
 
988
                          bzrlib.patiencediff.PatienceSequenceMatcher)
 
989
        else:
 
990
            from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
 
991
            self.assertIs(PatienceSequenceMatcher_py,
 
992
                          bzrlib.patiencediff.PatienceSequenceMatcher)
 
993
 
 
994
    def test_unique_lcs(self):
 
995
        if CompiledPatienceDiffFeature.available():
 
996
            from bzrlib._patiencediff_c import unique_lcs_c
 
997
            self.assertIs(unique_lcs_c,
 
998
                          bzrlib.patiencediff.unique_lcs)
 
999
        else:
 
1000
            from bzrlib._patiencediff_py import unique_lcs_py
 
1001
            self.assertIs(unique_lcs_py,
 
1002
                          bzrlib.patiencediff.unique_lcs)
 
1003
 
 
1004
    def test_recurse_matches(self):
 
1005
        if CompiledPatienceDiffFeature.available():
 
1006
            from bzrlib._patiencediff_c import recurse_matches_c
 
1007
            self.assertIs(recurse_matches_c,
 
1008
                          bzrlib.patiencediff.recurse_matches)
 
1009
        else:
 
1010
            from bzrlib._patiencediff_py import recurse_matches_py
 
1011
            self.assertIs(recurse_matches_py,
 
1012
                          bzrlib.patiencediff.recurse_matches)