~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_patches.py

  • Committer: Martin Pool
  • Date: 2005-05-25 00:48:22 UTC
  • Revision ID: mbp@sourcefrog.net-20050525004822-7665484961d59734
- Refactor diff code into one that works purely on 
  Tree objects

Show diffs side-by-side

added added

removed removed

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