~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: 2006-08-01 20:00:18 UTC
  • mto: (1946.2.6 reduce-knit-churn)
  • mto: This revision was merged to the branch mainline in revision 1906.
  • Revision ID: john@arbash-meinel.com-20060801200018-cafa6272d9b8cac4
Fix broken test

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 Canonical Development 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
 
18
from cStringIO import StringIO
 
19
import errno
 
20
from tempfile import TemporaryFile
 
21
 
 
22
from bzrlib.diff import internal_diff, external_diff, show_diff_trees
 
23
from bzrlib.errors import BinaryFile, NoDiff
 
24
import bzrlib.patiencediff
 
25
from bzrlib.tests import (TestCase, TestCaseWithTransport,
 
26
                          TestCaseInTempDir, TestSkipped)
 
27
 
 
28
 
 
29
def udiff_lines(old, new, allow_binary=False):
 
30
    output = StringIO()
 
31
    internal_diff('old', old, 'new', new, output, allow_binary)
 
32
    output.seek(0, 0)
 
33
    return output.readlines()
 
34
 
 
35
 
 
36
def external_udiff_lines(old, new, use_stringio=False):
 
37
    if use_stringio:
 
38
        # StringIO has no fileno, so it tests a different codepath
 
39
        output = StringIO()
 
40
    else:
 
41
        output = TemporaryFile()
 
42
    try:
 
43
        external_diff('old', old, 'new', new, output, diff_opts=['-u'])
 
44
    except NoDiff:
 
45
        raise TestSkipped('external "diff" not present to test')
 
46
    output.seek(0, 0)
 
47
    lines = output.readlines()
 
48
    output.close()
 
49
    return lines
 
50
 
 
51
 
 
52
class TestDiff(TestCase):
 
53
 
 
54
    def test_add_nl(self):
 
55
        """diff generates a valid diff for patches that add a newline"""
 
56
        lines = udiff_lines(['boo'], ['boo\n'])
 
57
        self.check_patch(lines)
 
58
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
 
59
            ## "expected no-nl, got %r" % lines[4]
 
60
 
 
61
    def test_add_nl_2(self):
 
62
        """diff generates a valid diff for patches that change last line and
 
63
        add a newline.
 
64
        """
 
65
        lines = udiff_lines(['boo'], ['goo\n'])
 
66
        self.check_patch(lines)
 
67
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
 
68
            ## "expected no-nl, got %r" % lines[4]
 
69
 
 
70
    def test_remove_nl(self):
 
71
        """diff generates a valid diff for patches that change last line and
 
72
        add a newline.
 
73
        """
 
74
        lines = udiff_lines(['boo\n'], ['boo'])
 
75
        self.check_patch(lines)
 
76
        self.assertEquals(lines[5], '\\ No newline at end of file\n')
 
77
            ## "expected no-nl, got %r" % lines[5]
 
78
 
 
79
    def check_patch(self, lines):
 
80
        self.assert_(len(lines) > 1)
 
81
            ## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
 
82
        self.assert_(lines[0].startswith ('---'))
 
83
            ## 'No orig line for patch:\n%s' % "".join(lines)
 
84
        self.assert_(lines[1].startswith ('+++'))
 
85
            ## 'No mod line for patch:\n%s' % "".join(lines)
 
86
        self.assert_(len(lines) > 2)
 
87
            ## "No hunks for patch:\n%s" % "".join(lines)
 
88
        self.assert_(lines[2].startswith('@@'))
 
89
            ## "No hunk header for patch:\n%s" % "".join(lines)
 
90
        self.assert_('@@' in lines[2][2:])
 
91
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
 
92
 
 
93
    def test_binary_lines(self):
 
94
        self.assertRaises(BinaryFile, udiff_lines, [1023 * 'a' + '\x00'], [])
 
95
        self.assertRaises(BinaryFile, udiff_lines, [], [1023 * 'a' + '\x00'])
 
96
        udiff_lines([1023 * 'a' + '\x00'], [], allow_binary=True)
 
97
        udiff_lines([], [1023 * 'a' + '\x00'], allow_binary=True)
 
98
 
 
99
    def test_external_diff(self):
 
100
        lines = external_udiff_lines(['boo\n'], ['goo\n'])
 
101
        self.check_patch(lines)
 
102
        self.assertEqual('\n', lines[-1])
 
103
 
 
104
    def test_external_diff_no_fileno(self):
 
105
        # Make sure that we can handle not having a fileno, even
 
106
        # if the diff is large
 
107
        lines = external_udiff_lines(['boo\n']*10000,
 
108
                                     ['goo\n']*10000,
 
109
                                     use_stringio=True)
 
110
        self.check_patch(lines)
 
111
 
 
112
    def test_external_diff_binary(self):
 
113
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
114
        self.assertEqual(['Binary files old and new differ\n', '\n'], lines)
 
115
 
 
116
    def test_no_external_diff(self):
 
117
        """Check that NoDiff is raised when diff is not available"""
 
118
        # Use os.environ['PATH'] to make sure no 'diff' command is available
 
119
        orig_path = os.environ['PATH']
 
120
        try:
 
121
            os.environ['PATH'] = ''
 
122
            self.assertRaises(NoDiff, external_diff,
 
123
                              'old', ['boo\n'], 'new', ['goo\n'],
 
124
                              StringIO(), diff_opts=['-u'])
 
125
        finally:
 
126
            os.environ['PATH'] = orig_path
 
127
        
 
128
    def test_internal_diff_default(self):
 
129
        # Default internal diff encoding is utf8
 
130
        output = StringIO()
 
131
        internal_diff(u'old_\xb5', ['old_text\n'],
 
132
                    u'new_\xe5', ['new_text\n'], output)
 
133
        lines = output.getvalue().splitlines(True)
 
134
        self.check_patch(lines)
 
135
        self.assertEquals(['--- old_\xc2\xb5\n',
 
136
                           '+++ new_\xc3\xa5\n',
 
137
                           '@@ -1,1 +1,1 @@\n',
 
138
                           '-old_text\n',
 
139
                           '+new_text\n',
 
140
                           '\n',
 
141
                          ]
 
142
                          , lines)
 
143
 
 
144
    def test_internal_diff_utf8(self):
 
145
        output = StringIO()
 
146
        internal_diff(u'old_\xb5', ['old_text\n'],
 
147
                    u'new_\xe5', ['new_text\n'], output,
 
148
                    path_encoding='utf8')
 
149
        lines = output.getvalue().splitlines(True)
 
150
        self.check_patch(lines)
 
151
        self.assertEquals(['--- old_\xc2\xb5\n',
 
152
                           '+++ new_\xc3\xa5\n',
 
153
                           '@@ -1,1 +1,1 @@\n',
 
154
                           '-old_text\n',
 
155
                           '+new_text\n',
 
156
                           '\n',
 
157
                          ]
 
158
                          , lines)
 
159
 
 
160
    def test_internal_diff_iso_8859_1(self):
 
161
        output = StringIO()
 
162
        internal_diff(u'old_\xb5', ['old_text\n'],
 
163
                    u'new_\xe5', ['new_text\n'], output,
 
164
                    path_encoding='iso-8859-1')
 
165
        lines = output.getvalue().splitlines(True)
 
166
        self.check_patch(lines)
 
167
        self.assertEquals(['--- old_\xb5\n',
 
168
                           '+++ new_\xe5\n',
 
169
                           '@@ -1,1 +1,1 @@\n',
 
170
                           '-old_text\n',
 
171
                           '+new_text\n',
 
172
                           '\n',
 
173
                          ]
 
174
                          , lines)
 
175
 
 
176
    def test_internal_diff_returns_bytes(self):
 
177
        import StringIO
 
178
        output = StringIO.StringIO()
 
179
        internal_diff(u'old_\xb5', ['old_text\n'],
 
180
                    u'new_\xe5', ['new_text\n'], output)
 
181
        self.failUnless(isinstance(output.getvalue(), str),
 
182
            'internal_diff should return bytestrings')
 
183
 
 
184
 
 
185
class TestDiffDates(TestCaseWithTransport):
 
186
 
 
187
    def setUp(self):
 
188
        super(TestDiffDates, self).setUp()
 
189
        self.wt = self.make_branch_and_tree('.')
 
190
        self.b = self.wt.branch
 
191
        self.build_tree_contents([
 
192
            ('file1', 'file1 contents at rev 1\n'),
 
193
            ('file2', 'file2 contents at rev 1\n')
 
194
            ])
 
195
        self.wt.add(['file1', 'file2'])
 
196
        self.wt.commit(
 
197
            message='Revision 1',
 
198
            timestamp=1143849600, # 2006-04-01 00:00:00 UTC
 
199
            timezone=0,
 
200
            rev_id='rev-1')
 
201
        self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
 
202
        self.wt.commit(
 
203
            message='Revision 2',
 
204
            timestamp=1143936000, # 2006-04-02 00:00:00 UTC
 
205
            timezone=28800,
 
206
            rev_id='rev-2')
 
207
        self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
 
208
        self.wt.commit(
 
209
            message='Revision 3',
 
210
            timestamp=1144022400, # 2006-04-03 00:00:00 UTC
 
211
            timezone=-3600,
 
212
            rev_id='rev-3')
 
213
        self.wt.remove(['file2'])
 
214
        self.wt.commit(
 
215
            message='Revision 4',
 
216
            timestamp=1144108800, # 2006-04-04 00:00:00 UTC
 
217
            timezone=0,
 
218
            rev_id='rev-4')
 
219
        self.build_tree_contents([
 
220
            ('file1', 'file1 contents in working tree\n')
 
221
            ])
 
222
        # set the date stamps for files in the working tree to known values
 
223
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
 
224
 
 
225
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
 
226
        output = StringIO()
 
227
        if working_tree is not None:
 
228
            extra_trees = (working_tree,)
 
229
        else:
 
230
            extra_trees = ()
 
231
        show_diff_trees(tree1, tree2, output, specific_files=specific_files,
 
232
                        extra_trees=extra_trees, old_label='old/', 
 
233
                        new_label='new/')
 
234
        return output.getvalue()
 
235
 
 
236
    def test_diff_rev_tree_working_tree(self):
 
237
        output = self.get_diff(self.wt.basis_tree(), self.wt)
 
238
        # note that the date for old/file1 is from rev 2 rather than from
 
239
        # the basis revision (rev 4)
 
240
        self.assertEqualDiff(output, '''\
 
241
=== modified file 'file1'
 
242
--- old/file1\t2006-04-02 00:00:00 +0000
 
243
+++ new/file1\t2006-04-05 00:00:00 +0000
 
244
@@ -1,1 +1,1 @@
 
245
-file1 contents at rev 2
 
246
+file1 contents in working tree
 
247
 
 
248
''')
 
249
 
 
250
    def test_diff_rev_tree_rev_tree(self):
 
251
        tree1 = self.b.repository.revision_tree('rev-2')
 
252
        tree2 = self.b.repository.revision_tree('rev-3')
 
253
        output = self.get_diff(tree1, tree2)
 
254
        self.assertEqualDiff(output, '''\
 
255
=== modified file 'file2'
 
256
--- old/file2\t2006-04-01 00:00:00 +0000
 
257
+++ new/file2\t2006-04-03 00:00:00 +0000
 
258
@@ -1,1 +1,1 @@
 
259
-file2 contents at rev 1
 
260
+file2 contents at rev 3
 
261
 
 
262
''')
 
263
        
 
264
    def test_diff_add_files(self):
 
265
        tree1 = self.b.repository.revision_tree(None)
 
266
        tree2 = self.b.repository.revision_tree('rev-1')
 
267
        output = self.get_diff(tree1, tree2)
 
268
        # the files have the epoch time stamp for the tree in which
 
269
        # they don't exist.
 
270
        self.assertEqualDiff(output, '''\
 
271
=== added file 'file1'
 
272
--- old/file1\t1970-01-01 00:00:00 +0000
 
273
+++ new/file1\t2006-04-01 00:00:00 +0000
 
274
@@ -0,0 +1,1 @@
 
275
+file1 contents at rev 1
 
276
 
 
277
=== added file 'file2'
 
278
--- old/file2\t1970-01-01 00:00:00 +0000
 
279
+++ new/file2\t2006-04-01 00:00:00 +0000
 
280
@@ -0,0 +1,1 @@
 
281
+file2 contents at rev 1
 
282
 
 
283
''')
 
284
 
 
285
    def test_diff_remove_files(self):
 
286
        tree1 = self.b.repository.revision_tree('rev-3')
 
287
        tree2 = self.b.repository.revision_tree('rev-4')
 
288
        output = self.get_diff(tree1, tree2)
 
289
        # the file has the epoch time stamp for the tree in which
 
290
        # it doesn't exist.
 
291
        self.assertEqualDiff(output, '''\
 
292
=== removed file 'file2'
 
293
--- old/file2\t2006-04-03 00:00:00 +0000
 
294
+++ new/file2\t1970-01-01 00:00:00 +0000
 
295
@@ -1,1 +0,0 @@
 
296
-file2 contents at rev 3
 
297
 
 
298
''')
 
299
 
 
300
    def test_show_diff_specified(self):
 
301
        """A working tree filename can be used to identify a file"""
 
302
        self.wt.rename_one('file1', 'file1b')
 
303
        old_tree = self.b.repository.revision_tree('rev-1')
 
304
        new_tree = self.b.repository.revision_tree('rev-4')
 
305
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'], 
 
306
                            working_tree=self.wt)
 
307
        self.assertContainsRe(out, 'file1\t')
 
308
 
 
309
    def test_recursive_diff(self):
 
310
        """Children of directories are matched"""
 
311
        os.mkdir('dir1')
 
312
        os.mkdir('dir2')
 
313
        self.wt.add(['dir1', 'dir2'])
 
314
        self.wt.rename_one('file1', 'dir1/file1')
 
315
        old_tree = self.b.repository.revision_tree('rev-1')
 
316
        new_tree = self.b.repository.revision_tree('rev-4')
 
317
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'], 
 
318
                            working_tree=self.wt)
 
319
        self.assertContainsRe(out, 'file1\t')
 
320
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'], 
 
321
                            working_tree=self.wt)
 
322
        self.assertNotContainsRe(out, 'file1\t')
 
323
 
 
324
 
 
325
class TestPatienceDiffLib(TestCase):
 
326
 
 
327
    def test_unique_lcs(self):
 
328
        unique_lcs = bzrlib.patiencediff.unique_lcs
 
329
        self.assertEquals(unique_lcs('', ''), [])
 
330
        self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
 
331
        self.assertEquals(unique_lcs('a', 'b'), [])
 
332
        self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
 
333
        self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
 
334
        self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
 
335
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1), 
 
336
                                                         (3,3), (4,4)])
 
337
        self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
 
338
 
 
339
    def test_recurse_matches(self):
 
340
        def test_one(a, b, matches):
 
341
            test_matches = []
 
342
            bzrlib.patiencediff.recurse_matches(a, b, 0, 0, len(a), len(b),
 
343
                test_matches, 10)
 
344
            self.assertEquals(test_matches, matches)
 
345
 
 
346
        test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
 
347
                 [(0, 0), (2, 2), (4, 4)])
 
348
        test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
 
349
                 [(0, 0), (2, 1), (4, 2)])
 
350
 
 
351
        # recurse_matches doesn't match non-unique 
 
352
        # lines surrounded by bogus text.
 
353
        # The update has been done in patiencediff.SequenceMatcher instead
 
354
 
 
355
        # This is what it could be
 
356
        #test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
 
357
 
 
358
        # This is what it currently gives:
 
359
        test_one('aBccDe', 'abccde', [(0,0), (5,5)])
 
360
 
 
361
    def test_matching_blocks(self):
 
362
        def chk_blocks(a, b, expected_blocks):
 
363
            # difflib always adds a signature of the total
 
364
            # length, with no matching entries at the end
 
365
            s = bzrlib.patiencediff.PatienceSequenceMatcher(None, a, b)
 
366
            blocks = s.get_matching_blocks()
 
367
            self.assertEquals((len(a), len(b), 0), blocks[-1])
 
368
            self.assertEquals(expected_blocks, blocks[:-1])
 
369
 
 
370
        # Some basic matching tests
 
371
        chk_blocks('', '', [])
 
372
        chk_blocks([], [], [])
 
373
        chk_blocks('abcd', 'abcd', [(0, 0, 4)])
 
374
        chk_blocks('abcd', 'abce', [(0, 0, 3)])
 
375
        chk_blocks('eabc', 'abce', [(1, 0, 3)])
 
376
        chk_blocks('eabce', 'abce', [(1, 0, 4)])
 
377
        chk_blocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
 
378
        chk_blocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
 
379
        chk_blocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
 
380
        # This may check too much, but it checks to see that 
 
381
        # a copied block stays attached to the previous section,
 
382
        # not the later one.
 
383
        # difflib would tend to grab the trailing longest match
 
384
        # which would make the diff not look right
 
385
        chk_blocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
386
                   [(0, 0, 6), (6, 11, 10)])
 
387
 
 
388
        # make sure it supports passing in lists
 
389
        chk_blocks(
 
390
                   ['hello there\n',
 
391
                    'world\n',
 
392
                    'how are you today?\n'],
 
393
                   ['hello there\n',
 
394
                    'how are you today?\n'],
 
395
                [(0, 0, 1), (2, 1, 1)])
 
396
 
 
397
        # non unique lines surrounded by non-matching lines
 
398
        # won't be found
 
399
        chk_blocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
 
400
 
 
401
        # But they only need to be locally unique
 
402
        chk_blocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
 
403
 
 
404
        # non unique blocks won't be matched
 
405
        chk_blocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
 
406
 
 
407
        # but locally unique ones will
 
408
        chk_blocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
 
409
                                              (5,4,1), (7,5,2), (10,8,1)])
 
410
 
 
411
        chk_blocks('abbabbXd', 'cabbabxd', [(7,7,1)])
 
412
        chk_blocks('abbabbbb', 'cabbabbc', [])
 
413
        chk_blocks('bbbbbbbb', 'cbbbbbbc', [])
 
414
 
 
415
    def test_opcodes(self):
 
416
        def chk_ops(a, b, expected_codes):
 
417
            s = bzrlib.patiencediff.PatienceSequenceMatcher(None, a, b)
 
418
            self.assertEquals(expected_codes, s.get_opcodes())
 
419
 
 
420
        chk_ops('', '', [])
 
421
        chk_ops([], [], [])
 
422
        chk_ops('abcd', 'abcd', [('equal',    0,4, 0,4)])
 
423
        chk_ops('abcd', 'abce', [('equal',   0,3, 0,3),
 
424
                                 ('replace', 3,4, 3,4)
 
425
                                ])
 
426
        chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
 
427
                                 ('equal',  1,4, 0,3),
 
428
                                 ('insert', 4,4, 3,4)
 
429
                                ])
 
430
        chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
 
431
                                  ('equal',  1,5, 0,4)
 
432
                                 ])
 
433
        chk_ops('abcde', 'abXde', [('equal',   0,2, 0,2),
 
434
                                   ('replace', 2,3, 2,3),
 
435
                                   ('equal',   3,5, 3,5)
 
436
                                  ])
 
437
        chk_ops('abcde', 'abXYZde', [('equal',   0,2, 0,2),
 
438
                                     ('replace', 2,3, 2,5),
 
439
                                     ('equal',   3,5, 5,7)
 
440
                                    ])
 
441
        chk_ops('abde', 'abXYZde', [('equal',  0,2, 0,2),
 
442
                                    ('insert', 2,2, 2,5),
 
443
                                    ('equal',  2,4, 5,7)
 
444
                                   ])
 
445
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
446
                [('equal',  0,6,  0,6),
 
447
                 ('insert', 6,6,  6,11),
 
448
                 ('equal',  6,16, 11,21)
 
449
                ])
 
450
        chk_ops(
 
451
                [ 'hello there\n'
 
452
                , 'world\n'
 
453
                , 'how are you today?\n'],
 
454
                [ 'hello there\n'
 
455
                , 'how are you today?\n'],
 
456
                [('equal',  0,1, 0,1),
 
457
                 ('delete', 1,2, 1,1),
 
458
                 ('equal',  2,3, 1,2),
 
459
                ])
 
460
        chk_ops('aBccDe', 'abccde', 
 
461
                [('equal',   0,1, 0,1),
 
462
                 ('replace', 1,5, 1,5),
 
463
                 ('equal',   5,6, 5,6),
 
464
                ])
 
465
        chk_ops('aBcDec', 'abcdec', 
 
466
                [('equal',   0,1, 0,1),
 
467
                 ('replace', 1,2, 1,2),
 
468
                 ('equal',   2,3, 2,3),
 
469
                 ('replace', 3,4, 3,4),
 
470
                 ('equal',   4,6, 4,6),
 
471
                ])
 
472
        chk_ops('aBcdEcdFg', 'abcdecdfg', 
 
473
                [('equal',   0,1, 0,1),
 
474
                 ('replace', 1,8, 1,8),
 
475
                 ('equal',   8,9, 8,9)
 
476
                ])
 
477
        chk_ops('aBcdEeXcdFg', 'abcdecdfg', 
 
478
                [('equal',   0,1, 0,1),
 
479
                 ('replace', 1,2, 1,2),
 
480
                 ('equal',   2,4, 2,4),
 
481
                 ('delete', 4,5, 4,4),
 
482
                 ('equal',   5,6, 4,5),
 
483
                 ('delete', 6,7, 5,5),
 
484
                 ('equal',   7,9, 5,7),
 
485
                 ('replace', 9,10, 7,8),
 
486
                 ('equal',   10,11, 8,9)
 
487
                ])
 
488
 
 
489
    def test_multiple_ranges(self):
 
490
        # There was an earlier bug where we used a bad set of ranges,
 
491
        # this triggers that specific bug, to make sure it doesn't regress
 
492
        def chk_blocks(a, b, expected_blocks):
 
493
            # difflib always adds a signature of the total
 
494
            # length, with no matching entries at the end
 
495
            s = bzrlib.patiencediff.PatienceSequenceMatcher(None, a, b)
 
496
            blocks = s.get_matching_blocks()
 
497
            x = blocks.pop()
 
498
            self.assertEquals(x, (len(a), len(b), 0))
 
499
            self.assertEquals(expected_blocks, blocks)
 
500
 
 
501
        chk_blocks('abcdefghijklmnop'
 
502
                 , 'abcXghiYZQRSTUVWXYZijklmnop'
 
503
                 , [(0, 0, 3), (6, 4, 3), (9, 20, 7)])
 
504
 
 
505
        chk_blocks('ABCd efghIjk  L'
 
506
                 , 'AxyzBCn mo pqrstuvwI1 2  L'
 
507
                 , [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
508
 
 
509
        # These are rot13 code snippets.
 
510
        chk_blocks('''\
 
511
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
 
512
    """
 
513
    gnxrf_netf = ['svyr*']
 
514
    gnxrf_bcgvbaf = ['ab-erphefr']
 
515
  
 
516
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
 
517
        sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
 
518
        vs vf_dhvrg():
 
519
            ercbegre = nqq_ercbegre_ahyy
 
520
        ryfr:
 
521
            ercbegre = nqq_ercbegre_cevag
 
522
        fzneg_nqq(svyr_yvfg, abg ab_erphefr, ercbegre)
 
523
 
 
524
 
 
525
pynff pzq_zxqve(Pbzznaq):
 
526
'''.splitlines(True), '''\
 
527
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
 
528
 
 
529
    --qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl 
 
530
    nqq gurz.
 
531
    """
 
532
    gnxrf_netf = ['svyr*']
 
533
    gnxrf_bcgvbaf = ['ab-erphefr', 'qel-eha']
 
534
 
 
535
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr, qel_eha=Snyfr):
 
536
        vzcbeg omeyvo.nqq
 
537
 
 
538
        vs qel_eha:
 
539
            vs vf_dhvrg():
 
540
                # Guvf vf cbvagyrff, ohg V'q engure abg envfr na reebe
 
541
                npgvba = omeyvo.nqq.nqq_npgvba_ahyy
 
542
            ryfr:
 
543
  npgvba = omeyvo.nqq.nqq_npgvba_cevag
 
544
        ryvs vf_dhvrg():
 
545
            npgvba = omeyvo.nqq.nqq_npgvba_nqq
 
546
        ryfr:
 
547
       npgvba = omeyvo.nqq.nqq_npgvba_nqq_naq_cevag
 
548
 
 
549
        omeyvo.nqq.fzneg_nqq(svyr_yvfg, abg ab_erphefr, npgvba)
 
550
 
 
551
 
 
552
pynff pzq_zxqve(Pbzznaq):
 
553
'''.splitlines(True)
 
554
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
555
 
 
556
    def test_patience_unified_diff(self):
 
557
        txt_a = ['hello there\n',
 
558
                 'world\n',
 
559
                 'how are you today?\n']
 
560
        txt_b = ['hello there\n',
 
561
                 'how are you today?\n']
 
562
        unified_diff = bzrlib.patiencediff.unified_diff
 
563
        psm = bzrlib.patiencediff.PatienceSequenceMatcher
 
564
        self.assertEquals([ '---  \n',
 
565
                           '+++  \n',
 
566
                           '@@ -1,3 +1,2 @@\n',
 
567
                           ' hello there\n',
 
568
                           '-world\n',
 
569
                           ' how are you today?\n'
 
570
                          ]
 
571
                          , list(unified_diff(txt_a, txt_b,
 
572
                                 sequencematcher=psm)))
 
573
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
 
574
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
 
575
        # This is the result with LongestCommonSubstring matching
 
576
        self.assertEquals(['---  \n',
 
577
                           '+++  \n',
 
578
                           '@@ -1,6 +1,11 @@\n',
 
579
                           ' a\n',
 
580
                           ' b\n',
 
581
                           ' c\n',
 
582
                           '+d\n',
 
583
                           '+e\n',
 
584
                           '+f\n',
 
585
                           '+x\n',
 
586
                           '+y\n',
 
587
                           ' d\n',
 
588
                           ' e\n',
 
589
                           ' f\n']
 
590
                          , list(unified_diff(txt_a, txt_b)))
 
591
        # And the patience diff
 
592
        self.assertEquals(['---  \n',
 
593
                           '+++  \n',
 
594
                           '@@ -4,6 +4,11 @@\n',
 
595
                           ' d\n',
 
596
                           ' e\n',
 
597
                           ' f\n',
 
598
                           '+x\n',
 
599
                           '+y\n',
 
600
                           '+d\n',
 
601
                           '+e\n',
 
602
                           '+f\n',
 
603
                           ' g\n',
 
604
                           ' h\n',
 
605
                           ' i\n',
 
606
                          ]
 
607
                          , list(unified_diff(txt_a, txt_b,
 
608
                                 sequencematcher=psm)))
 
609
 
 
610
 
 
611
class TestPatienceDiffLibFiles(TestCaseInTempDir):
 
612
 
 
613
    def test_patience_unified_diff_files(self):
 
614
        txt_a = ['hello there\n',
 
615
                 'world\n',
 
616
                 'how are you today?\n']
 
617
        txt_b = ['hello there\n',
 
618
                 'how are you today?\n']
 
619
        open('a1', 'wb').writelines(txt_a)
 
620
        open('b1', 'wb').writelines(txt_b)
 
621
 
 
622
        unified_diff_files = bzrlib.patiencediff.unified_diff_files
 
623
        psm = bzrlib.patiencediff.PatienceSequenceMatcher
 
624
        self.assertEquals(['--- a1 \n',
 
625
                           '+++ b1 \n',
 
626
                           '@@ -1,3 +1,2 @@\n',
 
627
                           ' hello there\n',
 
628
                           '-world\n',
 
629
                           ' how are you today?\n',
 
630
                          ]
 
631
                          , list(unified_diff_files('a1', 'b1',
 
632
                                 sequencematcher=psm)))
 
633
 
 
634
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
 
635
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
 
636
        open('a2', 'wb').writelines(txt_a)
 
637
        open('b2', 'wb').writelines(txt_b)
 
638
 
 
639
        # This is the result with LongestCommonSubstring matching
 
640
        self.assertEquals(['--- a2 \n',
 
641
                           '+++ b2 \n',
 
642
                           '@@ -1,6 +1,11 @@\n',
 
643
                           ' a\n',
 
644
                           ' b\n',
 
645
                           ' c\n',
 
646
                           '+d\n',
 
647
                           '+e\n',
 
648
                           '+f\n',
 
649
                           '+x\n',
 
650
                           '+y\n',
 
651
                           ' d\n',
 
652
                           ' e\n',
 
653
                           ' f\n']
 
654
                          , list(unified_diff_files('a2', 'b2')))
 
655
 
 
656
        # And the patience diff
 
657
        self.assertEquals(['--- a2 \n',
 
658
                           '+++ b2 \n',
 
659
                           '@@ -4,6 +4,11 @@\n',
 
660
                           ' d\n',
 
661
                           ' e\n',
 
662
                           ' f\n',
 
663
                           '+x\n',
 
664
                           '+y\n',
 
665
                           '+d\n',
 
666
                           '+e\n',
 
667
                           '+f\n',
 
668
                           ' g\n',
 
669
                           ' h\n',
 
670
                           ' i\n',
 
671
                          ]
 
672
                          , list(unified_diff_files('a2', 'b2',
 
673
                                 sequencematcher=psm)))