~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_patches.py

(vila) Fix test failures blocking package builds. (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2010 Aaron Bentley, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
 
 
18
import os.path
 
19
 
 
20
from bzrlib.tests import TestCase
 
21
 
 
22
from bzrlib.iterablefile import IterableFile
 
23
from bzrlib.patches import (MalformedLine,
 
24
                            MalformedHunkHeader,
 
25
                            MalformedPatchHeader,
 
26
                            BinaryPatch,
 
27
                            BinaryFiles,
 
28
                            Patch,
 
29
                            ContextLine,
 
30
                            InsertLine,
 
31
                            RemoveLine,
 
32
                            difference_index,
 
33
                            get_patch_names,
 
34
                            hunk_from_header,
 
35
                            iter_patched,
 
36
                            iter_patched_from_hunks,
 
37
                            parse_line,
 
38
                            parse_patch,
 
39
                            parse_patches,
 
40
                            NO_NL)
 
41
 
 
42
 
 
43
class PatchesTester(TestCase):
 
44
 
 
45
    def datafile(self, filename):
 
46
        data_path = os.path.join(os.path.dirname(__file__),
 
47
                                 "test_patches_data", filename)
 
48
        return file(data_path, "rb")
 
49
 
 
50
    def data_lines(self, filename):
 
51
        datafile = self.datafile(filename)
 
52
        try:
 
53
            return datafile.readlines()
 
54
        finally:
 
55
            datafile.close()
 
56
 
 
57
    def test_parse_patches_leading_noise(self):
 
58
        # https://bugs.launchpad.net/bzr/+bug/502076
 
59
        # https://code.launchpad.net/~toshio/bzr/allow-dirty-patches/+merge/18854
 
60
        lines = ["diff -pruN commands.py",
 
61
                 "--- orig/commands.py",
 
62
                 "+++ mod/dommands.py"]
 
63
        bits = parse_patches(iter(lines), allow_dirty=True)
 
64
 
 
65
    def test_preserve_dirty_head(self):
 
66
        """Parse a patch containing a dirty header, and preserve lines"""
 
67
        lines = ["=== added directory 'foo/bar'\n",
 
68
                 "=== modified file 'orig/commands.py'\n",
 
69
                 "--- orig/commands.py\n",
 
70
                 "+++ mod/dommands.py\n"]
 
71
        patches = parse_patches(
 
72
            lines.__iter__(), allow_dirty=True, keep_dirty=True)
 
73
        self.assertEqual(patches[0]['dirty_head'],
 
74
                         ["=== added directory 'foo/bar'\n",
 
75
                          "=== modified file 'orig/commands.py'\n"])
 
76
        self.assertEqual(patches[0]['patch'].get_header().splitlines(True),
 
77
                         ["--- orig/commands.py\n", "+++ mod/dommands.py\n"])
 
78
 
 
79
    def testValidPatchHeader(self):
 
80
        """Parse a valid patch header"""
 
81
        lines = "--- orig/commands.py\n+++ mod/dommands.py\n".split('\n')
 
82
        (orig, mod) = get_patch_names(lines.__iter__())
 
83
        self.assertEqual(orig, "orig/commands.py")
 
84
        self.assertEqual(mod, "mod/dommands.py")
 
85
 
 
86
    def testInvalidPatchHeader(self):
 
87
        """Parse an invalid patch header"""
 
88
        lines = "-- orig/commands.py\n+++ mod/dommands.py".split('\n')
 
89
        self.assertRaises(MalformedPatchHeader, get_patch_names,
 
90
                          lines.__iter__())
 
91
 
 
92
    def testValidHunkHeader(self):
 
93
        """Parse a valid hunk header"""
 
94
        header = "@@ -34,11 +50,6 @@\n"
 
95
        hunk = hunk_from_header(header)
 
96
        self.assertEqual(hunk.orig_pos, 34)
 
97
        self.assertEqual(hunk.orig_range, 11)
 
98
        self.assertEqual(hunk.mod_pos, 50)
 
99
        self.assertEqual(hunk.mod_range, 6)
 
100
        self.assertEqual(str(hunk), header)
 
101
 
 
102
    def testValidHunkHeader2(self):
 
103
        """Parse a tricky, valid hunk header"""
 
104
        header = "@@ -1 +0,0 @@\n"
 
105
        hunk = hunk_from_header(header)
 
106
        self.assertEqual(hunk.orig_pos, 1)
 
107
        self.assertEqual(hunk.orig_range, 1)
 
108
        self.assertEqual(hunk.mod_pos, 0)
 
109
        self.assertEqual(hunk.mod_range, 0)
 
110
        self.assertEqual(str(hunk), header)
 
111
 
 
112
    def testPDiff(self):
 
113
        """Parse a hunk header produced by diff -p"""
 
114
        header = "@@ -407,7 +292,7 @@ bzr 0.18rc1  2007-07-10\n"
 
115
        hunk = hunk_from_header(header)
 
116
        self.assertEqual('bzr 0.18rc1  2007-07-10', hunk.tail)
 
117
        self.assertEqual(header, str(hunk))
 
118
 
 
119
    def makeMalformed(self, header):
 
120
        self.assertRaises(MalformedHunkHeader, hunk_from_header, header)
 
121
 
 
122
    def testInvalidHeader(self):
 
123
        """Parse an invalid hunk header"""
 
124
        self.makeMalformed(" -34,11 +50,6 \n")
 
125
        self.makeMalformed("@@ +50,6 -34,11 @@\n")
 
126
        self.makeMalformed("@@ -34,11 +50,6 @@")
 
127
        self.makeMalformed("@@ -34.5,11 +50,6 @@\n")
 
128
        self.makeMalformed("@@-34,11 +50,6@@\n")
 
129
        self.makeMalformed("@@ 34,11 50,6 @@\n")
 
130
        self.makeMalformed("@@ -34,11 @@\n")
 
131
        self.makeMalformed("@@ -34,11 +50,6.5 @@\n")
 
132
        self.makeMalformed("@@ -34,11 +50,-6 @@\n")
 
133
 
 
134
    def lineThing(self, text, type):
 
135
        line = parse_line(text)
 
136
        self.assertIsInstance(line, type)
 
137
        self.assertEqual(str(line), text)
 
138
 
 
139
    def makeMalformedLine(self, text):
 
140
        self.assertRaises(MalformedLine, parse_line, text)
 
141
 
 
142
    def testValidLine(self):
 
143
        """Parse a valid hunk line"""
 
144
        self.lineThing(" hello\n", ContextLine)
 
145
        self.lineThing("+hello\n", InsertLine)
 
146
        self.lineThing("-hello\n", RemoveLine)
 
147
 
 
148
    def testMalformedLine(self):
 
149
        """Parse invalid valid hunk lines"""
 
150
        self.makeMalformedLine("hello\n")
 
151
 
 
152
    def testMalformedLineNO_NL(self):
 
153
        """Parse invalid '\ No newline at end of file' in hunk lines"""
 
154
        self.makeMalformedLine(NO_NL)
 
155
 
 
156
    def compare_parsed(self, patchtext):
 
157
        lines = patchtext.splitlines(True)
 
158
        patch = parse_patch(lines.__iter__())
 
159
        pstr = str(patch)
 
160
        i = difference_index(patchtext, pstr)
 
161
        if i is not None:
 
162
            print "%i: \"%s\" != \"%s\"" % (i, patchtext[i], pstr[i])
 
163
        self.assertEqual(patchtext, str(patch))
 
164
 
 
165
    def testAll(self):
 
166
        """Test parsing a whole patch"""
 
167
        patchtext = self.datafile("patchtext.patch").read()
 
168
        self.compare_parsed(patchtext)
 
169
 
 
170
    def test_parse_binary(self):
 
171
        """Test parsing a whole patch"""
 
172
        patches = parse_patches(self.data_lines("binary.patch"))
 
173
        self.assertIs(BinaryPatch, patches[0].__class__)
 
174
        self.assertIs(Patch, patches[1].__class__)
 
175
        self.assertContainsRe(patches[0].oldname, '^bar\t')
 
176
        self.assertContainsRe(patches[0].newname, '^qux\t')
 
177
        self.assertContainsRe(str(patches[0]),
 
178
                              'Binary files bar\t.* and qux\t.* differ\n')
 
179
 
 
180
    def test_parse_binary_after_normal(self):
 
181
        patches = parse_patches(self.data_lines("binary-after-normal.patch"))
 
182
        self.assertIs(BinaryPatch, patches[1].__class__)
 
183
        self.assertIs(Patch, patches[0].__class__)
 
184
        self.assertContainsRe(patches[1].oldname, '^bar\t')
 
185
        self.assertContainsRe(patches[1].newname, '^qux\t')
 
186
        self.assertContainsRe(str(patches[1]),
 
187
                              'Binary files bar\t.* and qux\t.* differ\n')
 
188
 
 
189
    def test_roundtrip_binary(self):
 
190
        patchtext = ''.join(self.data_lines("binary.patch"))
 
191
        patches = parse_patches(patchtext.splitlines(True))
 
192
        self.assertEqual(patchtext, ''.join(str(p) for p in patches))
 
193
 
 
194
    def testInit(self):
 
195
        """Handle patches missing half the position, range tuple"""
 
196
        patchtext = \
 
197
"""--- orig/__vavg__.cl
 
198
+++ mod/__vavg__.cl
 
199
@@ -1 +1,2 @@
 
200
 __qbpsbezng__ = "erfgehpgherqgrkg ra"
 
201
+__qbp__ = Na nygreangr Nepu pbzznaqyvar vagresnpr
 
202
"""
 
203
        self.compare_parsed(patchtext)
 
204
 
 
205
    def testLineLookup(self):
 
206
        import sys
 
207
        """Make sure we can accurately look up mod line from orig"""
 
208
        patch = parse_patch(self.datafile("diff"))
 
209
        orig = list(self.datafile("orig"))
 
210
        mod = list(self.datafile("mod"))
 
211
        removals = []
 
212
        for i in range(len(orig)):
 
213
            mod_pos = patch.pos_in_mod(i)
 
214
            if mod_pos is None:
 
215
                removals.append(orig[i])
 
216
                continue
 
217
            self.assertEqual(mod[mod_pos], orig[i])
 
218
        rem_iter = removals.__iter__()
 
219
        for hunk in patch.hunks:
 
220
            for line in hunk.lines:
 
221
                if isinstance(line, RemoveLine):
 
222
                    next = rem_iter.next()
 
223
                    if line.contents != next:
 
224
                        sys.stdout.write(" orig:%spatch:%s" % (next,
 
225
                                         line.contents))
 
226
                    self.assertEqual(line.contents, next)
 
227
        self.assertRaises(StopIteration, rem_iter.next)
 
228
 
 
229
    def testPatching(self):
 
230
        """Test a few patch files, and make sure they work."""
 
231
        files = [
 
232
            ('diff-2', 'orig-2', 'mod-2'),
 
233
            ('diff-3', 'orig-3', 'mod-3'),
 
234
            ('diff-4', 'orig-4', 'mod-4'),
 
235
            ('diff-5', 'orig-5', 'mod-5'),
 
236
            ('diff-6', 'orig-6', 'mod-6'),
 
237
            ('diff-7', 'orig-7', 'mod-7'),
 
238
        ]
 
239
        for diff, orig, mod in files:
 
240
            patch = self.datafile(diff)
 
241
            orig_lines = list(self.datafile(orig))
 
242
            mod_lines = list(self.datafile(mod))
 
243
 
 
244
            patched_file = IterableFile(iter_patched(orig_lines, patch))
 
245
            count = 0
 
246
            for patch_line in patched_file:
 
247
                self.assertEqual(patch_line, mod_lines[count])
 
248
                count += 1
 
249
            self.assertEqual(count, len(mod_lines))
 
250
 
 
251
    def test_iter_patched_binary(self):
 
252
        binary_lines = self.data_lines('binary.patch')
 
253
        e = self.assertRaises(BinaryFiles, iter_patched, [], binary_lines)
 
254
 
 
255
    def test_iter_patched_from_hunks(self):
 
256
        """Test a few patch files, and make sure they work."""
 
257
        files = [
 
258
            ('diff-2', 'orig-2', 'mod-2'),
 
259
            ('diff-3', 'orig-3', 'mod-3'),
 
260
            ('diff-4', 'orig-4', 'mod-4'),
 
261
            ('diff-5', 'orig-5', 'mod-5'),
 
262
            ('diff-6', 'orig-6', 'mod-6'),
 
263
            ('diff-7', 'orig-7', 'mod-7'),
 
264
        ]
 
265
        for diff, orig, mod in files:
 
266
            parsed = parse_patch(self.datafile(diff))
 
267
            orig_lines = list(self.datafile(orig))
 
268
            mod_lines = list(self.datafile(mod))
 
269
            iter_patched = iter_patched_from_hunks(orig_lines, parsed.hunks)
 
270
            patched_file = IterableFile(iter_patched)
 
271
            count = 0
 
272
            for patch_line in patched_file:
 
273
                self.assertEqual(patch_line, mod_lines[count])
 
274
                count += 1
 
275
            self.assertEqual(count, len(mod_lines))
 
276
 
 
277
    def testFirstLineRenumber(self):
 
278
        """Make sure we handle lines at the beginning of the hunk"""
 
279
        patch = parse_patch(self.datafile("insert_top.patch"))
 
280
        self.assertEqual(patch.pos_in_mod(0), 1)
 
281
 
 
282
    def testParsePatches(self):
 
283
        """Make sure file names can be extracted from tricky unified diffs"""
 
284
        patchtext = \
 
285
"""--- orig-7
 
286
+++ mod-7
 
287
@@ -1,10 +1,10 @@
 
288
 -- a
 
289
--- b
 
290
+++ c
 
291
 xx d
 
292
 xx e
 
293
 ++ f
 
294
-++ g
 
295
+-- h
 
296
 xx i
 
297
 xx j
 
298
 -- k
 
299
--- l
 
300
+++ m
 
301
--- orig-8
 
302
+++ mod-8
 
303
@@ -1 +1 @@
 
304
--- A
 
305
+++ B
 
306
@@ -1 +1 @@
 
307
--- C
 
308
+++ D
 
309
"""
 
310
        filenames = [('orig-7', 'mod-7'),
 
311
                     ('orig-8', 'mod-8')]
 
312
        patches = parse_patches(patchtext.splitlines(True))
 
313
        patch_files = []
 
314
        for patch in patches:
 
315
            patch_files.append((patch.oldname, patch.newname))
 
316
        self.assertEqual(patch_files, filenames)
 
317
 
 
318
    def testStatsValues(self):
 
319
        """Test the added, removed and hunks values for stats_values."""
 
320
        patch = parse_patch(self.datafile("diff"))
 
321
        self.assertEqual((299, 407, 48), patch.stats_values())