/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tests/test_patches.py

  • Committer: Jelmer Vernooij
  • Date: 2020-04-05 19:11:34 UTC
  • mto: (7490.7.16 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200405191134-0aebh8ikiwygxma5
Populate the .gitignore file.

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