~bzr-pqm/bzr/bzr.dev

1731.1.5 by Aaron Bentley
Restore test_patches_data
1
--- orig-6	2005-09-23 16:27:16.000000000 -0500
2
+++ mod-6	2005-09-23 16:27:32.000000000 -0500
3
@@ -1,558 +1 @@
4
-# Copyright (C) 2004, 2005 Aaron Bentley
5
-# <aaron.bentley@utoronto.ca>
6
-#
7
-#    This program is free software; you can redistribute it and/or modify
8
-#    it under the terms of the GNU General Public License as published by
9
-#    the Free Software Foundation; either version 2 of the License, or
10
-#    (at your option) any later version.
11
-#
12
-#    This program is distributed in the hope that it will be useful,
13
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
-#    GNU General Public License for more details.
16
-#
17
-#    You should have received a copy of the GNU General Public License
18
-#    along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
19
-#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1731.1.5 by Aaron Bentley
Restore test_patches_data
20
-
21
-class PatchSyntax(Exception):
22
-    def __init__(self, msg):
23
-        Exception.__init__(self, msg)
24
-
25
-
26
-class MalformedPatchHeader(PatchSyntax):
27
-    def __init__(self, desc, line):
28
-        self.desc = desc
29
-        self.line = line
30
-        msg = "Malformed patch header.  %s\n%r" % (self.desc, self.line)
31
-        PatchSyntax.__init__(self, msg)
32
-
33
-class MalformedHunkHeader(PatchSyntax):
34
-    def __init__(self, desc, line):
35
-        self.desc = desc
36
-        self.line = line
37
-        msg = "Malformed hunk header.  %s\n%r" % (self.desc, self.line)
38
-        PatchSyntax.__init__(self, msg)
39
-
40
-class MalformedLine(PatchSyntax):
41
-    def __init__(self, desc, line):
42
-        self.desc = desc
43
-        self.line = line
44
-        msg = "Malformed line.  %s\n%s" % (self.desc, self.line)
45
-        PatchSyntax.__init__(self, msg)
46
-
47
-def get_patch_names(iter_lines):
48
-    try:
49
-        line = iter_lines.next()
50
-        if not line.startswith("--- "):
51
-            raise MalformedPatchHeader("No orig name", line)
52
-        else:
53
-            orig_name = line[4:].rstrip("\n")
54
-    except StopIteration:
55
-        raise MalformedPatchHeader("No orig line", "")
56
-    try:
57
-        line = iter_lines.next()
58
-        if not line.startswith("+++ "):
59
-            raise PatchSyntax("No mod name")
60
-        else:
61
-            mod_name = line[4:].rstrip("\n")
62
-    except StopIteration:
63
-        raise MalformedPatchHeader("No mod line", "")
64
-    return (orig_name, mod_name)
65
-
66
-def parse_range(textrange):
67
-    """Parse a patch range, handling the "1" special-case
68
-
69
-    :param textrange: The text to parse
70
-    :type textrange: str
71
-    :return: the position and range, as a tuple
72
-    :rtype: (int, int)
73
-    """
74
-    tmp = textrange.split(',')
75
-    if len(tmp) == 1:
76
-        pos = tmp[0]
77
-        range = "1"
78
-    else:
79
-        (pos, range) = tmp
80
-    pos = int(pos)
81
-    range = int(range)
82
-    return (pos, range)
83
-
84
- 
85
-def hunk_from_header(line):
86
-    if not line.startswith("@@") or not line.endswith("@@\n") \
87
-        or not len(line) > 4:
88
-        raise MalformedHunkHeader("Does not start and end with @@.", line)
89
-    try:
90
-        (orig, mod) = line[3:-4].split(" ")
91
-    except Exception, e:
92
-        raise MalformedHunkHeader(str(e), line)
93
-    if not orig.startswith('-') or not mod.startswith('+'):
94
-        raise MalformedHunkHeader("Positions don't start with + or -.", line)
95
-    try:
96
-        (orig_pos, orig_range) = parse_range(orig[1:])
97
-        (mod_pos, mod_range) = parse_range(mod[1:])
98
-    except Exception, e:
99
-        raise MalformedHunkHeader(str(e), line)
100
-    if mod_range < 0 or orig_range < 0:
101
-        raise MalformedHunkHeader("Hunk range is negative", line)
102
-    return Hunk(orig_pos, orig_range, mod_pos, mod_range)
103
-
104
-
105
-class HunkLine:
106
-    def __init__(self, contents):
107
-        self.contents = contents
108
-
109
-    def get_str(self, leadchar):
110
-        if self.contents == "\n" and leadchar == " " and False:
111
-            return "\n"
112
-        if not self.contents.endswith('\n'):
113
-            terminator = '\n' + NO_NL
114
-        else:
115
-            terminator = ''
116
-        return leadchar + self.contents + terminator
117
-
118
-
119
-class ContextLine(HunkLine):
120
-    def __init__(self, contents):
121
-        HunkLine.__init__(self, contents)
122
-
123
-    def __str__(self):
124
-        return self.get_str(" ")
125
-
126
-
127
-class InsertLine(HunkLine):
128
-    def __init__(self, contents):
129
-        HunkLine.__init__(self, contents)
130
-
131
-    def __str__(self):
132
-        return self.get_str("+")
133
-
134
-
135
-class RemoveLine(HunkLine):
136
-    def __init__(self, contents):
137
-        HunkLine.__init__(self, contents)
138
-
139
-    def __str__(self):
140
-        return self.get_str("-")
141
-
142
-NO_NL = '\\ No newline at end of file\n'
143
-__pychecker__="no-returnvalues"
144
-
145
-def parse_line(line):
146
-    if line.startswith("\n"):
147
-        return ContextLine(line)
148
-    elif line.startswith(" "):
149
-        return ContextLine(line[1:])
150
-    elif line.startswith("+"):
151
-        return InsertLine(line[1:])
152
-    elif line.startswith("-"):
153
-        return RemoveLine(line[1:])
154
-    elif line == NO_NL:
155
-        return NO_NL
156
-    else:
157
-        raise MalformedLine("Unknown line type", line)
158
-__pychecker__=""
159
-
160
-
161
-class Hunk:
162
-    def __init__(self, orig_pos, orig_range, mod_pos, mod_range):
163
-        self.orig_pos = orig_pos
164
-        self.orig_range = orig_range
165
-        self.mod_pos = mod_pos
166
-        self.mod_range = mod_range
167
-        self.lines = []
168
-
169
-    def get_header(self):
170
-        return "@@ -%s +%s @@\n" % (self.range_str(self.orig_pos, 
171
-                                                   self.orig_range),
172
-                                    self.range_str(self.mod_pos, 
173
-                                                   self.mod_range))
174
-
175
-    def range_str(self, pos, range):
176
-        """Return a file range, special-casing for 1-line files.
177
-
178
-        :param pos: The position in the file
179
-        :type pos: int
180
-        :range: The range in the file
181
-        :type range: int
182
-        :return: a string in the format 1,4 except when range == pos == 1
183
-        """
184
-        if range == 1:
185
-            return "%i" % pos
186
-        else:
187
-            return "%i,%i" % (pos, range)
188
-
189
-    def __str__(self):
190
-        lines = [self.get_header()]
191
-        for line in self.lines:
192
-            lines.append(str(line))
193
-        return "".join(lines)
194
-
195
-    def shift_to_mod(self, pos):
196
-        if pos < self.orig_pos-1:
197
-            return 0
198
-        elif pos > self.orig_pos+self.orig_range:
199
-            return self.mod_range - self.orig_range
200
-        else:
201
-            return self.shift_to_mod_lines(pos)
202
-
203
-    def shift_to_mod_lines(self, pos):
204
-        assert (pos >= self.orig_pos-1 and pos <= self.orig_pos+self.orig_range)
205
-        position = self.orig_pos-1
206
-        shift = 0
207
-        for line in self.lines:
208
-            if isinstance(line, InsertLine):
209
-                shift += 1
210
-            elif isinstance(line, RemoveLine):
211
-                if position == pos:
212
-                    return None
213
-                shift -= 1
214
-                position += 1
215
-            elif isinstance(line, ContextLine):
216
-                position += 1
217
-            if position > pos:
218
-                break
219
-        return shift
220
-
221
-def iter_hunks(iter_lines):
222
-    hunk = None
223
-    for line in iter_lines:
224
-        if line == "\n":
225
-            if hunk is not None:
226
-                yield hunk
227
-                hunk = None
228
-            continue
229
-        if hunk is not None:
230
-            yield hunk
231
-        hunk = hunk_from_header(line)
232
-        orig_size = 0
233
-        mod_size = 0
234
-        while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
235
-            hunk_line = parse_line(iter_lines.next())
236
-            hunk.lines.append(hunk_line)
237
-            if isinstance(hunk_line, (RemoveLine, ContextLine)):
238
-                orig_size += 1
239
-            if isinstance(hunk_line, (InsertLine, ContextLine)):
240
-                mod_size += 1
241
-    if hunk is not None:
242
-        yield hunk
243
-
244
-class Patch:
245
-    def __init__(self, oldname, newname):
246
-        self.oldname = oldname
247
-        self.newname = newname
248
-        self.hunks = []
249
-
250
-    def __str__(self):
251
-        ret = self.get_header() 
252
-        ret += "".join([str(h) for h in self.hunks])
253
-        return ret
254
-
255
-    def get_header(self):
256
-        return "--- %s\n+++ %s\n" % (self.oldname, self.newname)
257
-
258
-    def stats_str(self):
259
-        """Return a string of patch statistics"""
260
-        removes = 0
261
-        inserts = 0
262
-        for hunk in self.hunks:
263
-            for line in hunk.lines:
264
-                if isinstance(line, InsertLine):
265
-                     inserts+=1;
266
-                elif isinstance(line, RemoveLine):
267
-                     removes+=1;
268
-        return "%i inserts, %i removes in %i hunks" % \
269
-            (inserts, removes, len(self.hunks))
270
-
271
-    def pos_in_mod(self, position):
272
-        newpos = position
273
-        for hunk in self.hunks:
274
-            shift = hunk.shift_to_mod(position)
275
-            if shift is None:
276
-                return None
277
-            newpos += shift
278
-        return newpos
279
-            
280
-    def iter_inserted(self):
281
-        """Iteraties through inserted lines
282
-        
283
-        :return: Pair of line number, line
284
-        :rtype: iterator of (int, InsertLine)
285
-        """
286
-        for hunk in self.hunks:
287
-            pos = hunk.mod_pos - 1;
288
-            for line in hunk.lines:
289
-                if isinstance(line, InsertLine):
290
-                    yield (pos, line)
291
-                    pos += 1
292
-                if isinstance(line, ContextLine):
293
-                    pos += 1
294
-
295
-def parse_patch(iter_lines):
296
-    (orig_name, mod_name) = get_patch_names(iter_lines)
297
-    patch = Patch(orig_name, mod_name)
298
-    for hunk in iter_hunks(iter_lines):
299
-        patch.hunks.append(hunk)
300
-    return patch
301
-
302
-
303
-def iter_file_patch(iter_lines):
304
-    saved_lines = []
305
-    for line in iter_lines:
306
-        if line.startswith('=== '):
307
-            continue
308
-        elif line.startswith('--- '):
309
-            if len(saved_lines) > 0:
310
-                yield saved_lines
311
-            saved_lines = []
312
-        saved_lines.append(line)
313
-    if len(saved_lines) > 0:
314
-        yield saved_lines
315
-
316
-
317
-def iter_lines_handle_nl(iter_lines):
318
-    """
319
-    Iterates through lines, ensuring that lines that originally had no
320
-    terminating \n are produced without one.  This transformation may be
321
-    applied at any point up until hunk line parsing, and is safe to apply
322
-    repeatedly.
323
-    """
324
-    last_line = None
325
-    for line in iter_lines:
326
-        if line == NO_NL:
327
-            assert last_line.endswith('\n')
328
-            last_line = last_line[:-1]
329
-            line = None
330
-        if last_line is not None:
331
-            yield last_line
332
-        last_line = line
333
-    if last_line is not None:
334
-        yield last_line
335
-
336
-
337
-def parse_patches(iter_lines):
338
-    iter_lines = iter_lines_handle_nl(iter_lines)
339
-    return [parse_patch(f.__iter__()) for f in iter_file_patch(iter_lines)]
340
-
341
-
342
-def difference_index(atext, btext):
343
-    """Find the indext of the first character that differs betweeen two texts
344
-
345
-    :param atext: The first text
346
-    :type atext: str
347
-    :param btext: The second text
348
-    :type str: str
349
-    :return: The index, or None if there are no differences within the range
350
-    :rtype: int or NoneType
351
-    """
352
-    length = len(atext)
353
-    if len(btext) < length:
354
-        length = len(btext)
355
-    for i in range(length):
356
-        if atext[i] != btext[i]:
357
-            return i;
358
-    return None
359
-
360
-class PatchConflict(Exception):
361
-    def __init__(self, line_no, orig_line, patch_line):
362
-        orig = orig_line.rstrip('\n')
363
-        patch = str(patch_line).rstrip('\n')
364
-        msg = 'Text contents mismatch at line %d.  Original has "%s",'\
365
-            ' but patch says it should be "%s"' % (line_no, orig, patch)
366
-        Exception.__init__(self, msg)
367
-
368
-
369
-def iter_patched(orig_lines, patch_lines):
370
-    """Iterate through a series of lines with a patch applied.
371
-    This handles a single file, and does exact, not fuzzy patching.
372
-    """
373
-    if orig_lines is not None:
374
-        orig_lines = orig_lines.__iter__()
375
-    seen_patch = []
376
-    patch_lines = iter_lines_handle_nl(patch_lines.__iter__())
377
-    get_patch_names(patch_lines)
378
-    line_no = 1
379
-    for hunk in iter_hunks(patch_lines):
380
-        while line_no < hunk.orig_pos:
381
-            orig_line = orig_lines.next()
382
-            yield orig_line
383
-            line_no += 1
384
-        for hunk_line in hunk.lines:
385
-            seen_patch.append(str(hunk_line))
386
-            if isinstance(hunk_line, InsertLine):
387
-                yield hunk_line.contents
388
-            elif isinstance(hunk_line, (ContextLine, RemoveLine)):
389
-                orig_line = orig_lines.next()
390
-                if orig_line != hunk_line.contents:
391
-                    raise PatchConflict(line_no, orig_line, "".join(seen_patch))
392
-                if isinstance(hunk_line, ContextLine):
393
-                    yield orig_line
394
-                else:
395
-                    assert isinstance(hunk_line, RemoveLine)
396
-                line_no += 1
397
-                    
398
-import unittest
399
-import os.path
400
-class PatchesTester(unittest.TestCase):
401
-    def datafile(self, filename):
402
-        data_path = os.path.join(os.path.dirname(__file__), "testdata", 
403
-                                 filename)
404
-        return file(data_path, "rb")
405
-
406
-    def testValidPatchHeader(self):
407
-        """Parse a valid patch header"""
408
-        lines = "--- orig/commands.py\n+++ mod/dommands.py\n".split('\n')
409
-        (orig, mod) = get_patch_names(lines.__iter__())
410
-        assert(orig == "orig/commands.py")
411
-        assert(mod == "mod/dommands.py")
412
-
413
-    def testInvalidPatchHeader(self):
414
-        """Parse an invalid patch header"""
415
-        lines = "-- orig/commands.py\n+++ mod/dommands.py".split('\n')
416
-        self.assertRaises(MalformedPatchHeader, get_patch_names,
417
-                          lines.__iter__())
418
-
419
-    def testValidHunkHeader(self):
420
-        """Parse a valid hunk header"""
421
-        header = "@@ -34,11 +50,6 @@\n"
422
-        hunk = hunk_from_header(header);
423
-        assert (hunk.orig_pos == 34)
424
-        assert (hunk.orig_range == 11)
425
-        assert (hunk.mod_pos == 50)
426
-        assert (hunk.mod_range == 6)
427
-        assert (str(hunk) == header)
428
-
429
-    def testValidHunkHeader2(self):
430
-        """Parse a tricky, valid hunk header"""
431
-        header = "@@ -1 +0,0 @@\n"
432
-        hunk = hunk_from_header(header);
433
-        assert (hunk.orig_pos == 1)
434
-        assert (hunk.orig_range == 1)
435
-        assert (hunk.mod_pos == 0)
436
-        assert (hunk.mod_range == 0)
437
-        assert (str(hunk) == header)
438
-
439
-    def makeMalformed(self, header):
440
-        self.assertRaises(MalformedHunkHeader, hunk_from_header, header)
441
-
442
-    def testInvalidHeader(self):
443
-        """Parse an invalid hunk header"""
444
-        self.makeMalformed(" -34,11 +50,6 \n")
445
-        self.makeMalformed("@@ +50,6 -34,11 @@\n")
446
-        self.makeMalformed("@@ -34,11 +50,6 @@")
447
-        self.makeMalformed("@@ -34.5,11 +50,6 @@\n")
448
-        self.makeMalformed("@@-34,11 +50,6@@\n")
449
-        self.makeMalformed("@@ 34,11 50,6 @@\n")
450
-        self.makeMalformed("@@ -34,11 @@\n")
451
-        self.makeMalformed("@@ -34,11 +50,6.5 @@\n")
452
-        self.makeMalformed("@@ -34,11 +50,-6 @@\n")
453
-
454
-    def lineThing(self,text, type):
455
-        line = parse_line(text)
456
-        assert(isinstance(line, type))
457
-        assert(str(line)==text)
458
-
459
-    def makeMalformedLine(self, text):
460
-        self.assertRaises(MalformedLine, parse_line, text)
461
-
462
-    def testValidLine(self):
463
-        """Parse a valid hunk line"""
464
-        self.lineThing(" hello\n", ContextLine)
465
-        self.lineThing("+hello\n", InsertLine)
466
-        self.lineThing("-hello\n", RemoveLine)
467
-    
468
-    def testMalformedLine(self):
469
-        """Parse invalid valid hunk lines"""
470
-        self.makeMalformedLine("hello\n")
471
-    
472
-    def compare_parsed(self, patchtext):
473
-        lines = patchtext.splitlines(True)
474
-        patch = parse_patch(lines.__iter__())
475
-        pstr = str(patch)
476
-        i = difference_index(patchtext, pstr)
477
-        if i is not None:
478
-            print "%i: \"%s\" != \"%s\"" % (i, patchtext[i], pstr[i])
479
-        self.assertEqual (patchtext, str(patch))
480
-
481
-    def testAll(self):
482
-        """Test parsing a whole patch"""
483
-        patchtext = """--- orig/commands.py
484
-+++ mod/commands.py
485
-@@ -1337,7 +1337,8 @@
486
- 
487
-     def set_title(self, command=None):
488
-         try:
489
--            version = self.tree.tree_version.nonarch
490
-+            version = pylon.alias_or_version(self.tree.tree_version, self.tree,
491
-+                                             full=False)
492
-         except:
493
-             version = "[no version]"
494
-         if command is None:
495
-@@ -1983,7 +1984,11 @@
496
-                                          version)
497
-         if len(new_merges) > 0:
498
-             if cmdutil.prompt("Log for merge"):
499
--                mergestuff = cmdutil.log_for_merge(tree, comp_version)
500
-+                if cmdutil.prompt("changelog for merge"):
501
-+                    mergestuff = "Patches applied:\\n"
502
-+                    mergestuff += pylon.changelog_for_merge(new_merges)
503
-+                else:
504
-+                    mergestuff = cmdutil.log_for_merge(tree, comp_version)
505
-                 log.description += mergestuff
506
-         log.save()
507
-     try:
508
-"""
509
-        self.compare_parsed(patchtext)
510
-
511
-    def testInit(self):
512
-        """Handle patches missing half the position, range tuple"""
513
-        patchtext = \
514
-"""--- orig/__init__.py
515
-+++ mod/__init__.py
516
-@@ -1 +1,2 @@
517
- __docformat__ = "restructuredtext en"
518
-+__doc__ = An alternate Arch commandline interface
519
-"""
520
-        self.compare_parsed(patchtext)
521
-        
522
-
523
-
524
-    def testLineLookup(self):
525
-        import sys
526
-        """Make sure we can accurately look up mod line from orig"""
527
-        patch = parse_patch(self.datafile("diff"))
528
-        orig = list(self.datafile("orig"))
529
-        mod = list(self.datafile("mod"))
530
-        removals = []
531
-        for i in range(len(orig)):
532
-            mod_pos = patch.pos_in_mod(i)
533
-            if mod_pos is None:
534
-                removals.append(orig[i])
535
-                continue
536
-            assert(mod[mod_pos]==orig[i])
537
-        rem_iter = removals.__iter__()
538
-        for hunk in patch.hunks:
539
-            for line in hunk.lines:
540
-                if isinstance(line, RemoveLine):
541
-                    next = rem_iter.next()
542
-                    if line.contents != next:
543
-                        sys.stdout.write(" orig:%spatch:%s" % (next,
544
-                                         line.contents))
545
-                    assert(line.contents == next)
546
-        self.assertRaises(StopIteration, rem_iter.next)
547
-
548
-    def testFirstLineRenumber(self):
549
-        """Make sure we handle lines at the beginning of the hunk"""
550
-        patch = parse_patch(self.datafile("insert_top.patch"))
551
-        assert (patch.pos_in_mod(0)==1)
552
-
553
-def test():
554
-    patchesTestSuite = unittest.makeSuite(PatchesTester,'test')
555
-    runner = unittest.TextTestRunner(verbosity=0)
556
-    return runner.run(patchesTestSuite)
557
-    
558
-
559
-if __name__ == "__main__":
560
-    test()
561
-# arch-tag: d1541a25-eac5-4de9-a476-08a7cecd5683
562
+Total contents change