~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

  • Committer: mbp at sourcefrog
  • Date: 2005-04-06 14:06:32 UTC
  • Revision ID: mbp@sourcefrog.net-20050406140632-b44f572e110216f266a43b71
pychecker fixups

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
from cStringIO import StringIO
2
 
 
3
 
from bzrlib.diff import internal_diff
4
 
from bzrlib.errors import BinaryFile
5
 
from bzrlib.patiencediff import (recurse_matches, SequenceMatcher, unique_lcs,
6
 
                                 unified_diff, unified_diff_files)
7
 
from bzrlib.tests import TestCase, TestCaseInTempDir
8
 
 
9
 
 
10
 
def udiff_lines(old, new, allow_binary=False):
11
 
    output = StringIO()
12
 
    internal_diff('old', old, 'new', new, output, allow_binary)
13
 
    output.seek(0, 0)
14
 
    return output.readlines()
15
 
 
16
 
 
17
 
class TestDiff(TestCase):
18
 
 
19
 
    def test_add_nl(self):
20
 
        """diff generates a valid diff for patches that add a newline"""
21
 
        lines = udiff_lines(['boo'], ['boo\n'])
22
 
        self.check_patch(lines)
23
 
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
24
 
            ## "expected no-nl, got %r" % lines[4]
25
 
 
26
 
    def test_add_nl_2(self):
27
 
        """diff generates a valid diff for patches that change last line and
28
 
        add a newline.
29
 
        """
30
 
        lines = udiff_lines(['boo'], ['goo\n'])
31
 
        self.check_patch(lines)
32
 
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
33
 
            ## "expected no-nl, got %r" % lines[4]
34
 
 
35
 
    def test_remove_nl(self):
36
 
        """diff generates a valid diff for patches that change last line and
37
 
        add a newline.
38
 
        """
39
 
        lines = udiff_lines(['boo\n'], ['boo'])
40
 
        self.check_patch(lines)
41
 
        self.assertEquals(lines[5], '\\ No newline at end of file\n')
42
 
            ## "expected no-nl, got %r" % lines[5]
43
 
 
44
 
    def check_patch(self, lines):
45
 
        self.assert_(len(lines) > 1)
46
 
            ## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
47
 
        self.assert_(lines[0].startswith ('---'))
48
 
            ## 'No orig line for patch:\n%s' % "".join(lines)
49
 
        self.assert_(lines[1].startswith ('+++'))
50
 
            ## 'No mod line for patch:\n%s' % "".join(lines)
51
 
        self.assert_(len(lines) > 2)
52
 
            ## "No hunks for patch:\n%s" % "".join(lines)
53
 
        self.assert_(lines[2].startswith('@@'))
54
 
            ## "No hunk header for patch:\n%s" % "".join(lines)
55
 
        self.assert_('@@' in lines[2][2:])
56
 
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
57
 
 
58
 
    def test_binary_lines(self):
59
 
        self.assertRaises(BinaryFile, udiff_lines, [1023 * 'a' + '\x00'], [])
60
 
        self.assertRaises(BinaryFile, udiff_lines, [], [1023 * 'a' + '\x00'])
61
 
        udiff_lines([1023 * 'a' + '\x00'], [], allow_binary=True)
62
 
        udiff_lines([], [1023 * 'a' + '\x00'], allow_binary=True)
63
 
 
64
 
 
65
 
class TestCDVDiffLib(TestCase):
66
 
 
67
 
    def test_unique_lcs(self):
68
 
        self.assertEquals(unique_lcs('', ''), [])
69
 
        self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
70
 
        self.assertEquals(unique_lcs('a', 'b'), [])
71
 
        self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
72
 
        self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
73
 
        self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
74
 
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1), 
75
 
                                                         (3,3), (4,4)])
76
 
        self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
77
 
 
78
 
    def test_recurse_matches(self):
79
 
        def test_one(a, b, matches):
80
 
            test_matches = []
81
 
            recurse_matches(a, b, len(a), len(b), test_matches, 10)
82
 
            self.assertEquals(test_matches, matches)
83
 
 
84
 
        test_one(['a', None, 'b', None, 'c'], ['a', 'a', 'b', 'c', 'c'],
85
 
                 [(0, 0), (2, 2), (4, 4)])
86
 
        test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
87
 
                 [(0, 0), (2, 1), (4, 2)])
88
 
 
89
 
        # recurse_matches doesn't match non-unique 
90
 
        # lines surrounded by bogus text.
91
 
        # The update has been done in patiencediff.SequenceMatcher instead
92
 
 
93
 
        # This is what it could be
94
 
        #test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
95
 
 
96
 
        # This is what it currently gives:
97
 
        test_one('aBccDe', 'abccde', [(0,0), (5,5)])
98
 
 
99
 
    def test_matching_blocks(self):
100
 
        def chk_blocks(a, b, matching):
101
 
            # difflib always adds a signature of the total
102
 
            # length, with no matching entries at the end
103
 
            s = SequenceMatcher(None, a, b)
104
 
            blocks = s.get_matching_blocks()
105
 
            x = blocks.pop()
106
 
            self.assertEquals(x, (len(a), len(b), 0))
107
 
            self.assertEquals(matching, blocks)
108
 
 
109
 
        # Some basic matching tests
110
 
        chk_blocks('', '', [])
111
 
        chk_blocks([], [], [])
112
 
        chk_blocks('abcd', 'abcd', [(0, 0, 4)])
113
 
        chk_blocks('abcd', 'abce', [(0, 0, 3)])
114
 
        chk_blocks('eabc', 'abce', [(1, 0, 3)])
115
 
        chk_blocks('eabce', 'abce', [(1, 0, 4)])
116
 
        chk_blocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
117
 
        chk_blocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
118
 
        chk_blocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
119
 
        # This may check too much, but it checks to see that 
120
 
        # a copied block stays attached to the previous section,
121
 
        # not the later one.
122
 
        # difflib would tend to grab the trailing longest match
123
 
        # which would make the diff not look right
124
 
        chk_blocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
125
 
                   [(0, 0, 6), (6, 11, 10)])
126
 
 
127
 
        # make sure it supports passing in lists
128
 
        chk_blocks(
129
 
                   ['hello there\n',
130
 
                    'world\n',
131
 
                    'how are you today?\n'],
132
 
                   ['hello there\n',
133
 
                    'how are you today?\n'],
134
 
                [(0, 0, 1), (2, 1, 1)])
135
 
 
136
 
        chk_blocks('aBccDe', 'abccde', [(0,0,1), (2,2,2), (5,5,1)])
137
 
 
138
 
        chk_blocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
139
 
                                              (5,5,2), (8,8,1)])
140
 
 
141
 
        chk_blocks('abbabbXd', 'cabbabxd', [(0,1,5), (7,7,1)])
142
 
        chk_blocks('abbabbbb', 'cabbabbc', [(0,1,6)])
143
 
        chk_blocks('bbbbbbbb', 'cbbbbbbc', [(0,1,6)])
144
 
 
145
 
    def test_opcodes(self):
146
 
        def chk_ops(a, b, codes):
147
 
            s = SequenceMatcher(None, a, b)
148
 
            self.assertEquals(codes, s.get_opcodes())
149
 
 
150
 
        chk_ops('', '', [])
151
 
        chk_ops([], [], [])
152
 
        chk_ops('abcd', 'abcd', [('equal',    0,4, 0,4)])
153
 
        chk_ops('abcd', 'abce', [('equal',   0,3, 0,3),
154
 
                                 ('replace', 3,4, 3,4)
155
 
                                ])
156
 
        chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
157
 
                                 ('equal',  1,4, 0,3),
158
 
                                 ('insert', 4,4, 3,4)
159
 
                                ])
160
 
        chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
161
 
                                  ('equal',  1,5, 0,4)
162
 
                                 ])
163
 
        chk_ops('abcde', 'abXde', [('equal',   0,2, 0,2),
164
 
                                   ('replace', 2,3, 2,3),
165
 
                                   ('equal',   3,5, 3,5)
166
 
                                  ])
167
 
        chk_ops('abcde', 'abXYZde', [('equal',   0,2, 0,2),
168
 
                                     ('replace', 2,3, 2,5),
169
 
                                     ('equal',   3,5, 5,7)
170
 
                                    ])
171
 
        chk_ops('abde', 'abXYZde', [('equal',  0,2, 0,2),
172
 
                                    ('insert', 2,2, 2,5),
173
 
                                    ('equal',  2,4, 5,7)
174
 
                                   ])
175
 
        chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
176
 
                [('equal',  0,6,  0,6),
177
 
                 ('insert', 6,6,  6,11),
178
 
                 ('equal',  6,16, 11,21)
179
 
                ])
180
 
        chk_ops(
181
 
                [ 'hello there\n'
182
 
                , 'world\n'
183
 
                , 'how are you today?\n'],
184
 
                [ 'hello there\n'
185
 
                , 'how are you today?\n'],
186
 
                [('equal',  0,1, 0,1),
187
 
                 ('delete', 1,2, 1,1),
188
 
                 ('equal',  2,3, 1,2)
189
 
                ])
190
 
        chk_ops('aBccDe', 'abccde', 
191
 
                [('equal',   0,1, 0,1),
192
 
                 ('replace', 1,2, 1,2),
193
 
                 ('equal',   2,4, 2,4),
194
 
                 ('replace', 4,5, 4,5),
195
 
                 ('equal',   5,6, 5,6)
196
 
                ])
197
 
        chk_ops('aBcdEcdFg', 'abcdecdfg', 
198
 
                [('equal',   0,1, 0,1),
199
 
                 ('replace', 1,2, 1,2),
200
 
                 ('equal',   2,4, 2,4),
201
 
                 ('replace', 4,5, 4,5),
202
 
                 ('equal',   5,7, 5,7),
203
 
                 ('replace', 7,8, 7,8),
204
 
                 ('equal',   8,9, 8,9)
205
 
                ])
206
 
 
207
 
    def test_multiple_ranges(self):
208
 
        # There was an earlier bug where we used a bad set of ranges,
209
 
        # this triggers that specific bug, to make sure it doesn't regress
210
 
        def chk_blocks(a, b, matching):
211
 
            # difflib always adds a signature of the total
212
 
            # length, with no matching entries at the end
213
 
            s = SequenceMatcher(None, a, b)
214
 
            blocks = s.get_matching_blocks()
215
 
            x = blocks.pop()
216
 
            self.assertEquals(x, (len(a), len(b), 0))
217
 
            self.assertEquals(matching, blocks)
218
 
 
219
 
        chk_blocks('abcdefghijklmnop'
220
 
                 , 'abcXghiYZQRSTUVWXYZijklmnop'
221
 
                 , [(0, 0, 3), (6, 4, 3), (9, 20, 7)])
222
 
 
223
 
        chk_blocks('ABCd efghIjk  L'
224
 
                 , 'AxyzBCn mo pqrstuvwI1 2  L'
225
 
                 , [(0,0,1), (1, 4, 2), (4, 7, 1), (9, 19, 1), (12, 23, 3)])
226
 
 
227
 
        chk_blocks('''\
228
 
    get added when you add a file in the directory.
229
 
    """
230
 
    takes_args = ['file*']
231
 
    takes_options = ['no-recurse']
232
 
    
233
 
    def run(self, file_list, no_recurse=False):
234
 
        from bzrlib.add import smart_add, add_reporter_print, add_reporter_null
235
 
        if is_quiet():
236
 
            reporter = add_reporter_null
237
 
        else:
238
 
            reporter = add_reporter_print
239
 
        smart_add(file_list, not no_recurse, reporter)
240
 
 
241
 
 
242
 
class cmd_mkdir(Command):
243
 
'''.splitlines(True)
244
 
, '''\
245
 
    get added when you add a file in the directory.
246
 
 
247
 
    --dry-run will show which files would be added, but not actually 
248
 
    add them.
249
 
    """
250
 
    takes_args = ['file*']
251
 
    takes_options = ['no-recurse', 'dry-run']
252
 
 
253
 
    def run(self, file_list, no_recurse=False, dry_run=False):
254
 
        import bzrlib.add
255
 
 
256
 
        if dry_run:
257
 
            if is_quiet():
258
 
                # This is pointless, but I'd rather not raise an error
259
 
                action = bzrlib.add.add_action_null
260
 
            else:
261
 
                action = bzrlib.add.add_action_print
262
 
        elif is_quiet():
263
 
            action = bzrlib.add.add_action_add
264
 
        else:
265
 
            action = bzrlib.add.add_action_add_and_print
266
 
 
267
 
        bzrlib.add.smart_add(file_list, not no_recurse, action)
268
 
 
269
 
 
270
 
class cmd_mkdir(Command):
271
 
'''.splitlines(True)
272
 
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
273
 
 
274
 
    def test_cdv_unified_diff(self):
275
 
        txt_a = ['hello there\n',
276
 
                 'world\n',
277
 
                 'how are you today?\n']
278
 
        txt_b = ['hello there\n',
279
 
                 'how are you today?\n']
280
 
        self.assertEquals([ '---  \n',
281
 
                           '+++  \n',
282
 
                           '@@ -1,3 +1,2 @@\n',
283
 
                           ' hello there\n',
284
 
                           '-world\n',
285
 
                           ' how are you today?\n'
286
 
                          ]
287
 
                          , list(unified_diff(txt_a, txt_b
288
 
                                        , sequencematcher=SequenceMatcher)))
289
 
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
290
 
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
291
 
        # This is the result with LongestCommonSubstring matching
292
 
        self.assertEquals(['---  \n',
293
 
                           '+++  \n',
294
 
                           '@@ -1,6 +1,11 @@\n',
295
 
                           ' a\n',
296
 
                           ' b\n',
297
 
                           ' c\n',
298
 
                           '+d\n',
299
 
                           '+e\n',
300
 
                           '+f\n',
301
 
                           '+x\n',
302
 
                           '+y\n',
303
 
                           ' d\n',
304
 
                           ' e\n',
305
 
                           ' f\n']
306
 
                          , list(unified_diff(txt_a, txt_b)))
307
 
        # And the cdv diff
308
 
        self.assertEquals(['---  \n',
309
 
                           '+++  \n',
310
 
                           '@@ -4,6 +4,11 @@\n',
311
 
                           ' d\n',
312
 
                           ' e\n',
313
 
                           ' f\n',
314
 
                           '+x\n',
315
 
                           '+y\n',
316
 
                           '+d\n',
317
 
                           '+e\n',
318
 
                           '+f\n',
319
 
                           ' g\n',
320
 
                           ' h\n',
321
 
                           ' i\n',
322
 
                          ]
323
 
                          , list(unified_diff(txt_a, txt_b,
324
 
                                 sequencematcher=SequenceMatcher)))
325
 
 
326
 
 
327
 
class TestCDVDiffLibFiles(TestCaseInTempDir):
328
 
 
329
 
    def test_cdv_unified_diff_files(self):
330
 
        txt_a = ['hello there\n',
331
 
                 'world\n',
332
 
                 'how are you today?\n']
333
 
        txt_b = ['hello there\n',
334
 
                 'how are you today?\n']
335
 
        open('a1', 'wb').writelines(txt_a)
336
 
        open('b1', 'wb').writelines(txt_b)
337
 
 
338
 
        self.assertEquals(['--- a1 \n',
339
 
                           '+++ b1 \n',
340
 
                           '@@ -1,3 +1,2 @@\n',
341
 
                           ' hello there\n',
342
 
                           '-world\n',
343
 
                           ' how are you today?\n',
344
 
                          ]
345
 
                          , list(unified_diff_files('a1', 'b1',
346
 
                                 sequencematcher=SequenceMatcher)))
347
 
 
348
 
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
349
 
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
350
 
        open('a2', 'wb').writelines(txt_a)
351
 
        open('b2', 'wb').writelines(txt_b)
352
 
 
353
 
        # This is the result with LongestCommonSubstring matching
354
 
        self.assertEquals(['--- a2 \n',
355
 
                           '+++ b2 \n',
356
 
                           '@@ -1,6 +1,11 @@\n',
357
 
                           ' a\n',
358
 
                           ' b\n',
359
 
                           ' c\n',
360
 
                           '+d\n',
361
 
                           '+e\n',
362
 
                           '+f\n',
363
 
                           '+x\n',
364
 
                           '+y\n',
365
 
                           ' d\n',
366
 
                           ' e\n',
367
 
                           ' f\n']
368
 
                          , list(unified_diff_files('a2', 'b2')))
369
 
 
370
 
        # And the cdv diff
371
 
        self.assertEquals(['--- a2 \n',
372
 
                           '+++ b2 \n',
373
 
                           '@@ -4,6 +4,11 @@\n',
374
 
                           ' d\n',
375
 
                           ' e\n',
376
 
                           ' f\n',
377
 
                           '+x\n',
378
 
                           '+y\n',
379
 
                           '+d\n',
380
 
                           '+e\n',
381
 
                           '+f\n',
382
 
                           ' g\n',
383
 
                           ' h\n',
384
 
                           ' i\n',
385
 
                          ]
386
 
                          , list(unified_diff_files('a2', 'b2',
387
 
                                 sequencematcher=SequenceMatcher)))