~greatmay12/+junk/test1

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_patches_data/mod-5

  • Committer: thitipong at ndrsolution
  • Date: 2011-11-14 06:31:02 UTC
  • Revision ID: thitipong@ndrsolution.com-20111114063102-9obte3yfi2azku7d
ndr redirect version

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2004, 2005 Aaron Bentley
 
2
# <aaron.bentley@utoronto.ca>
 
3
#
 
4
#    This program is free software; you can redistribute it and/or modify
 
5
#    it under the terms of the GNU General Public License as published by
 
6
#    the Free Software Foundation; either version 2 of the License, or
 
7
#    (at your option) any later version.
 
8
#
 
9
#    This program is distributed in the hope that it will be useful,
 
10
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
#    GNU General Public License for more details.
 
13
#
 
14
#    You should have received a copy of the GNU General Public License
 
15
#    along with this program; if not, write to the Free Software
 
16
#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
17
 
 
18
class PatchSyntax(Exception):
 
19
    def __init__(self, msg):
 
20
        Exception.__init__(self, msg)
 
21
 
 
22
 
 
23
class MalformedPatchHeader(PatchSyntax):
 
24
    def __init__(self, desc, line):
 
25
        self.desc = desc
 
26
        self.line = line
 
27
        msg = "Malformed patch header.  %s\n%r" % (self.desc, self.line)
 
28
        PatchSyntax.__init__(self, msg)
 
29
 
 
30
class MalformedHunkHeader(PatchSyntax):
 
31
    def __init__(self, desc, line):
 
32
        self.desc = desc
 
33
        self.line = line
 
34
        msg = "Malformed hunk header.  %s\n%r" % (self.desc, self.line)
 
35
        PatchSyntax.__init__(self, msg)
 
36
 
 
37
class MalformedLine(PatchSyntax):
 
38
    def __init__(self, desc, line):
 
39
        self.desc = desc
 
40
        self.line = line
 
41
        msg = "Malformed line.  %s\n%s" % (self.desc, self.line)
 
42
        PatchSyntax.__init__(self, msg)
 
43
 
 
44
def get_patch_names(iter_lines):
 
45
    try:
 
46
        line = iter_lines.next()
 
47
        if not line.startswith("--- "):
 
48
            raise MalformedPatchHeader("No orig name", line)
 
49
        else:
 
50
            orig_name = line[4:].rstrip("\n")
 
51
    except StopIteration:
 
52
        raise MalformedPatchHeader("No orig line", "")
 
53
    try:
 
54
        line = iter_lines.next()
 
55
        if not line.startswith("+++ "):
 
56
            raise PatchSyntax("No mod name")
 
57
        else:
 
58
            mod_name = line[4:].rstrip("\n")
 
59
    except StopIteration:
 
60
        raise MalformedPatchHeader("No mod line", "")
 
61
    return (orig_name, mod_name)
 
62
 
 
63
def iter_hunks(iter_lines):
 
64
    hunk = None
 
65
    for line in iter_lines:
 
66
        if line == "\n":
 
67
            if hunk is not None:
 
68
                yield hunk
 
69
                hunk = None
 
70
            continue
 
71
        if hunk is not None:
 
72
            yield hunk
 
73
        hunk = hunk_from_header(line)
 
74
        orig_size = 0
 
75
        mod_size = 0
 
76
        while orig_size < hunk.orig_range or mod_size < hunk.mod_range:
 
77
            hunk_line = parse_line(iter_lines.next())
 
78
            hunk.lines.append(hunk_line)
 
79
            if isinstance(hunk_line, (RemoveLine, ContextLine)):
 
80
                orig_size += 1
 
81
            if isinstance(hunk_line, (InsertLine, ContextLine)):
 
82
                mod_size += 1
 
83
    if hunk is not None:
 
84
        yield hunk
 
85
 
 
86
class Patch:
 
87
    def __init__(self, oldname, newname):
 
88
        self.oldname = oldname
 
89
        self.newname = newname
 
90
        self.hunks = []
 
91
 
 
92
    def __str__(self):
 
93
        ret = self.get_header() 
 
94
        ret += "".join([str(h) for h in self.hunks])
 
95
        return ret
 
96
 
 
97
    def get_header(self):
 
98
        return "--- %s\n+++ %s\n" % (self.oldname, self.newname)
 
99
 
 
100
    def stats_str(self):
 
101
        """Return a string of patch statistics"""
 
102
        removes = 0
 
103
        inserts = 0
 
104
        for hunk in self.hunks:
 
105
            for line in hunk.lines:
 
106
                if isinstance(line, InsertLine):
 
107
                     inserts+=1;
 
108
                elif isinstance(line, RemoveLine):
 
109
                     removes+=1;
 
110
        return "%i inserts, %i removes in %i hunks" % \
 
111
            (inserts, removes, len(self.hunks))
 
112
 
 
113
    def pos_in_mod(self, position):
 
114
        newpos = position
 
115
        for hunk in self.hunks:
 
116
            shift = hunk.shift_to_mod(position)
 
117
            if shift is None:
 
118
                return None
 
119
            newpos += shift
 
120
        return newpos
 
121
            
 
122
    def iter_inserted(self):
 
123
        """Iteraties through inserted lines
 
124
        
 
125
        :return: Pair of line number, line
 
126
        :rtype: iterator of (int, InsertLine)
 
127
        """
 
128
        for hunk in self.hunks:
 
129
            pos = hunk.mod_pos - 1;
 
130
            for line in hunk.lines:
 
131
                if isinstance(line, InsertLine):
 
132
                    yield (pos, line)
 
133
                    pos += 1
 
134
                if isinstance(line, ContextLine):
 
135
                    pos += 1
 
136
 
 
137
def parse_patch(iter_lines):
 
138
    (orig_name, mod_name) = get_patch_names(iter_lines)
 
139
    patch = Patch(orig_name, mod_name)
 
140
    for hunk in iter_hunks(iter_lines):
 
141
        patch.hunks.append(hunk)
 
142
    return patch
 
143
 
 
144
 
 
145
def iter_file_patch(iter_lines):
 
146
    saved_lines = []
 
147
    for line in iter_lines:
 
148
        if line.startswith('=== '):
 
149
            continue
 
150
        elif line.startswith('--- '):
 
151
            if len(saved_lines) > 0:
 
152
                yield saved_lines
 
153
            saved_lines = []
 
154
        saved_lines.append(line)
 
155
    if len(saved_lines) > 0:
 
156
        yield saved_lines
 
157
 
 
158
 
 
159
def iter_lines_handle_nl(iter_lines):
 
160
    """
 
161
    Iterates through lines, ensuring that lines that originally had no
 
162
    terminating \n are produced without one.  This transformation may be
 
163
    applied at any point up until hunk line parsing, and is safe to apply
 
164
    repeatedly.
 
165
    """
 
166
    last_line = None
 
167
    for line in iter_lines:
 
168
        if line == NO_NL:
 
169
            assert last_line.endswith('\n')
 
170
            last_line = last_line[:-1]
 
171
            line = None
 
172
        if last_line is not None:
 
173
            yield last_line
 
174
        last_line = line
 
175
    if last_line is not None:
 
176
        yield last_line
 
177
 
 
178
 
 
179
def parse_patches(iter_lines):
 
180
    iter_lines = iter_lines_handle_nl(iter_lines)
 
181
    return [parse_patch(f.__iter__()) for f in iter_file_patch(iter_lines)]
 
182
 
 
183
 
 
184
def difference_index(atext, btext):
 
185
    """Find the indext of the first character that differs betweeen two texts
 
186
 
 
187
    :param atext: The first text
 
188
    :type atext: str
 
189
    :param btext: The second text
 
190
    :type str: str
 
191
    :return: The index, or None if there are no differences within the range
 
192
    :rtype: int or NoneType
 
193
    """
 
194
    length = len(atext)
 
195
    if len(btext) < length:
 
196
        length = len(btext)
 
197
    for i in range(length):
 
198
        if atext[i] != btext[i]:
 
199
            return i;
 
200
    return None
 
201
 
 
202
class PatchConflict(Exception):
 
203
    def __init__(self, line_no, orig_line, patch_line):
 
204
        orig = orig_line.rstrip('\n')
 
205
        patch = str(patch_line).rstrip('\n')
 
206
        msg = 'Text contents mismatch at line %d.  Original has "%s",'\
 
207
            ' but patch says it should be "%s"' % (line_no, orig, patch)
 
208
        Exception.__init__(self, msg)
 
209
 
 
210
 
 
211
def iter_patched(orig_lines, patch_lines):
 
212
    """Iterate through a series of lines with a patch applied.
 
213
    This handles a single file, and does exact, not fuzzy patching.
 
214
    """
 
215
    if orig_lines is not None:
 
216
        orig_lines = orig_lines.__iter__()
 
217
    seen_patch = []
 
218
    patch_lines = iter_lines_handle_nl(patch_lines.__iter__())
 
219
    get_patch_names(patch_lines)
 
220
    line_no = 1
 
221
    for hunk in iter_hunks(patch_lines):
 
222
        while line_no < hunk.orig_pos:
 
223
            orig_line = orig_lines.next()
 
224
            yield orig_line
 
225
            line_no += 1
 
226
        for hunk_line in hunk.lines:
 
227
            seen_patch.append(str(hunk_line))
 
228
            if isinstance(hunk_line, InsertLine):
 
229
                yield hunk_line.contents
 
230
            elif isinstance(hunk_line, (ContextLine, RemoveLine)):
 
231
                orig_line = orig_lines.next()
 
232
                if orig_line != hunk_line.contents:
 
233
                    raise PatchConflict(line_no, orig_line, "".join(seen_patch))
 
234
                if isinstance(hunk_line, ContextLine):
 
235
                    yield orig_line
 
236
                else:
 
237
                    assert isinstance(hunk_line, RemoveLine)
 
238
                line_no += 1
 
239
                    
 
240
import unittest
 
241
import os.path
 
242
class PatchesTester(unittest.TestCase):
 
243
    def datafile(self, filename):
 
244
        data_path = os.path.join(os.path.dirname(__file__), "testdata", 
 
245
                                 filename)
 
246
        return file(data_path, "rb")
 
247
 
 
248
    def testValidPatchHeader(self):
 
249
        """Parse a valid patch header"""
 
250
        lines = "--- orig/commands.py\n+++ mod/dommands.py\n".split('\n')
 
251
        (orig, mod) = get_patch_names(lines.__iter__())
 
252
        assert(orig == "orig/commands.py")
 
253
        assert(mod == "mod/dommands.py")
 
254
 
 
255
    def testInvalidPatchHeader(self):
 
256
        """Parse an invalid patch header"""
 
257
        lines = "-- orig/commands.py\n+++ mod/dommands.py".split('\n')
 
258
        self.assertRaises(MalformedPatchHeader, get_patch_names,
 
259
                          lines.__iter__())
 
260
 
 
261
    def testValidHunkHeader(self):
 
262
        """Parse a valid hunk header"""
 
263
        header = "@@ -34,11 +50,6 @@\n"
 
264
        hunk = hunk_from_header(header);
 
265
        assert (hunk.orig_pos == 34)
 
266
        assert (hunk.orig_range == 11)
 
267
        assert (hunk.mod_pos == 50)
 
268
        assert (hunk.mod_range == 6)
 
269
        assert (str(hunk) == header)
 
270
 
 
271
    def testValidHunkHeader2(self):
 
272
        """Parse a tricky, valid hunk header"""
 
273
        header = "@@ -1 +0,0 @@\n"
 
274
        hunk = hunk_from_header(header);
 
275
        assert (hunk.orig_pos == 1)
 
276
        assert (hunk.orig_range == 1)
 
277
        assert (hunk.mod_pos == 0)
 
278
        assert (hunk.mod_range == 0)
 
279
        assert (str(hunk) == header)
 
280
 
 
281
    def makeMalformed(self, header):
 
282
        self.assertRaises(MalformedHunkHeader, hunk_from_header, header)
 
283
 
 
284
    def testInvalidHeader(self):
 
285
        """Parse an invalid hunk header"""
 
286
        self.makeMalformed(" -34,11 +50,6 \n")
 
287
        self.makeMalformed("@@ +50,6 -34,11 @@\n")
 
288
        self.makeMalformed("@@ -34,11 +50,6 @@")
 
289
        self.makeMalformed("@@ -34.5,11 +50,6 @@\n")
 
290
        self.makeMalformed("@@-34,11 +50,6@@\n")
 
291
        self.makeMalformed("@@ 34,11 50,6 @@\n")
 
292
        self.makeMalformed("@@ -34,11 @@\n")
 
293
        self.makeMalformed("@@ -34,11 +50,6.5 @@\n")
 
294
        self.makeMalformed("@@ -34,11 +50,-6 @@\n")
 
295
 
 
296
    def lineThing(self,text, type):
 
297
        line = parse_line(text)
 
298
        assert(isinstance(line, type))
 
299
        assert(str(line)==text)
 
300
 
 
301
    def makeMalformedLine(self, text):
 
302
        self.assertRaises(MalformedLine, parse_line, text)
 
303
 
 
304
    def testValidLine(self):
 
305
        """Parse a valid hunk line"""
 
306
        self.lineThing(" hello\n", ContextLine)
 
307
        self.lineThing("+hello\n", InsertLine)
 
308
        self.lineThing("-hello\n", RemoveLine)
 
309
    
 
310
    def testMalformedLine(self):
 
311
        """Parse invalid valid hunk lines"""
 
312
        self.makeMalformedLine("hello\n")
 
313
    
 
314
    def compare_parsed(self, patchtext):
 
315
        lines = patchtext.splitlines(True)
 
316
        patch = parse_patch(lines.__iter__())
 
317
        pstr = str(patch)
 
318
        i = difference_index(patchtext, pstr)
 
319
        if i is not None:
 
320
            print "%i: \"%s\" != \"%s\"" % (i, patchtext[i], pstr[i])
 
321
        self.assertEqual (patchtext, str(patch))
 
322
 
 
323
    def testAll(self):
 
324
        """Test parsing a whole patch"""
 
325
        patchtext = """--- orig/commands.py
 
326
+++ mod/commands.py
 
327
@@ -1337,7 +1337,8 @@
 
328
 
 
329
     def set_title(self, command=None):
 
330
         try:
 
331
-            version = self.tree.tree_version.nonarch
 
332
+            version = pylon.alias_or_version(self.tree.tree_version, self.tree,
 
333
+                                             full=False)
 
334
         except:
 
335
             version = "[no version]"
 
336
         if command is None:
 
337
@@ -1983,7 +1984,11 @@
 
338
                                          version)
 
339
         if len(new_merges) > 0:
 
340
             if cmdutil.prompt("Log for merge"):
 
341
-                mergestuff = cmdutil.log_for_merge(tree, comp_version)
 
342
+                if cmdutil.prompt("changelog for merge"):
 
343
+                    mergestuff = "Patches applied:\\n"
 
344
+                    mergestuff += pylon.changelog_for_merge(new_merges)
 
345
+                else:
 
346
+                    mergestuff = cmdutil.log_for_merge(tree, comp_version)
 
347
                 log.description += mergestuff
 
348
         log.save()
 
349
     try:
 
350
"""
 
351
        self.compare_parsed(patchtext)
 
352
 
 
353
    def testInit(self):
 
354
        """Handle patches missing half the position, range tuple"""
 
355
        patchtext = \
 
356
"""--- orig/__init__.py
 
357
+++ mod/__init__.py
 
358
@@ -1 +1,2 @@
 
359
 __docformat__ = "restructuredtext en"
 
360
+__doc__ = An alternate Arch commandline interface
 
361
"""
 
362
        self.compare_parsed(patchtext)
 
363
        
 
364
 
 
365
 
 
366
    def testLineLookup(self):
 
367
        import sys
 
368
        """Make sure we can accurately look up mod line from orig"""
 
369
        patch = parse_patch(self.datafile("diff"))
 
370
        orig = list(self.datafile("orig"))
 
371
        mod = list(self.datafile("mod"))
 
372
        removals = []
 
373
        for i in range(len(orig)):
 
374
            mod_pos = patch.pos_in_mod(i)
 
375
            if mod_pos is None:
 
376
                removals.append(orig[i])
 
377
                continue
 
378
            assert(mod[mod_pos]==orig[i])
 
379
        rem_iter = removals.__iter__()
 
380
        for hunk in patch.hunks:
 
381
            for line in hunk.lines:
 
382
                if isinstance(line, RemoveLine):
 
383
                    next = rem_iter.next()
 
384
                    if line.contents != next:
 
385
                        sys.stdout.write(" orig:%spatch:%s" % (next,
 
386
                                         line.contents))
 
387
                    assert(line.contents == next)
 
388
        self.assertRaises(StopIteration, rem_iter.next)
 
389
 
 
390
    def testFirstLineRenumber(self):
 
391
        """Make sure we handle lines at the beginning of the hunk"""
 
392
        patch = parse_patch(self.datafile("insert_top.patch"))
 
393
        assert (patch.pos_in_mod(0)==1)
 
394
 
 
395
def test():
 
396
    patchesTestSuite = unittest.makeSuite(PatchesTester,'test')
 
397
    runner = unittest.TextTestRunner(verbosity=0)
 
398
    return runner.run(patchesTestSuite)
 
399
    
 
400
 
 
401
if __name__ == "__main__":
 
402
    test()
 
403
# arch-tag: d1541a25-eac5-4de9-a476-08a7cecd5683