~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_pack.py

Review comments.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Tests for bzrlib.pack."""
 
18
 
 
19
 
 
20
from cStringIO import StringIO
 
21
 
 
22
from bzrlib import pack, errors, tests
 
23
 
 
24
 
 
25
class TestContainerWriter(tests.TestCase):
 
26
 
 
27
    def test_construct(self):
 
28
        """Test constructing a ContainerWriter.
 
29
        
 
30
        This uses None as the output stream to show that the constructor doesn't
 
31
        try to use the output stream.
 
32
        """
 
33
        writer = pack.ContainerWriter(None)
 
34
 
 
35
    def test_begin(self):
 
36
        """The begin() method writes the container format marker line."""
 
37
        output = StringIO()
 
38
        writer = pack.ContainerWriter(output.write)
 
39
        writer.begin()
 
40
        self.assertEqual('Bazaar pack format 1 (introduced in 0.18)\n',
 
41
                         output.getvalue())
 
42
 
 
43
    def test_end(self):
 
44
        """The end() method writes an End Marker record."""
 
45
        output = StringIO()
 
46
        writer = pack.ContainerWriter(output.write)
 
47
        writer.begin()
 
48
        writer.end()
 
49
        self.assertEqual('Bazaar pack format 1 (introduced in 0.18)\nE',
 
50
                         output.getvalue())
 
51
 
 
52
    def test_add_bytes_record_no_name(self):
 
53
        """Add a bytes record with no name."""
 
54
        output = StringIO()
 
55
        writer = pack.ContainerWriter(output.write)
 
56
        writer.begin()
 
57
        writer.add_bytes_record('abc', names=[])
 
58
        self.assertEqual('Bazaar pack format 1 (introduced in 0.18)\nB3\n\nabc',
 
59
                         output.getvalue())
 
60
 
 
61
    def test_add_bytes_record_one_name(self):
 
62
        """Add a bytes record with one name."""
 
63
        output = StringIO()
 
64
        writer = pack.ContainerWriter(output.write)
 
65
        writer.begin()
 
66
        writer.add_bytes_record('abc', names=['name1'])
 
67
        self.assertEqual(
 
68
            'Bazaar pack format 1 (introduced in 0.18)\n'
 
69
            'B3\nname1\n\nabc',
 
70
            output.getvalue())
 
71
 
 
72
    def test_add_bytes_record_two_names(self):
 
73
        """Add a bytes record with two names."""
 
74
        output = StringIO()
 
75
        writer = pack.ContainerWriter(output.write)
 
76
        writer.begin()
 
77
        writer.add_bytes_record('abc', names=['name1', 'name2'])
 
78
        self.assertEqual(
 
79
            'Bazaar pack format 1 (introduced in 0.18)\n'
 
80
            'B3\nname1\nname2\n\nabc',
 
81
            output.getvalue())
 
82
 
 
83
    def test_add_bytes_record_invalid_name(self):
 
84
        """Adding a Bytes record with a name with whitespace in it raises
 
85
        InvalidRecordError.
 
86
        """
 
87
        output = StringIO()
 
88
        writer = pack.ContainerWriter(output.write)
 
89
        writer.begin()
 
90
        self.assertRaises(
 
91
            errors.InvalidRecordError,
 
92
            writer.add_bytes_record, 'abc', names=['bad name'])
 
93
 
 
94
 
 
95
class TestContainerReader(tests.TestCase):
 
96
 
 
97
    def get_reader_for(self, bytes):
 
98
        stream = StringIO(bytes)
 
99
        reader = pack.ContainerReader(stream)
 
100
        return reader
 
101
 
 
102
    def test_construct(self):
 
103
        """Test constructing a ContainerReader.
 
104
        
 
105
        This uses None as the output stream to show that the constructor doesn't
 
106
        try to use the input stream.
 
107
        """
 
108
        reader = pack.ContainerReader(None)
 
109
 
 
110
    def test_empty_container(self):
 
111
        """Read an empty container."""
 
112
        reader = self.get_reader_for(
 
113
            "Bazaar pack format 1 (introduced in 0.18)\nE")
 
114
        self.assertEqual([], list(reader.iter_records()))
 
115
 
 
116
    def test_unknown_format(self):
 
117
        """Unrecognised container formats raise UnknownContainerFormatError."""
 
118
        reader = self.get_reader_for("unknown format\n")
 
119
        self.assertRaises(
 
120
            errors.UnknownContainerFormatError, reader.iter_records)
 
121
 
 
122
    def test_unexpected_end_of_container(self):
 
123
        """Containers that don't end with an End Marker record should cause
 
124
        UnexpectedEndOfContainerError to be raised.
 
125
        """
 
126
        reader = self.get_reader_for(
 
127
            "Bazaar pack format 1 (introduced in 0.18)\n")
 
128
        iterator = reader.iter_records()
 
129
        self.assertRaises(
 
130
            errors.UnexpectedEndOfContainerError, iterator.next)
 
131
 
 
132
    def test_unknown_record_type(self):
 
133
        """Unknown record types cause UnknownRecordTypeError to be raised."""
 
134
        reader = self.get_reader_for(
 
135
            "Bazaar pack format 1 (introduced in 0.18)\nX")
 
136
        iterator = reader.iter_records()
 
137
        self.assertRaises(
 
138
            errors.UnknownRecordTypeError, iterator.next)
 
139
 
 
140
    def test_container_with_one_unnamed_record(self):
 
141
        """Read a container with one Bytes record.
 
142
        
 
143
        Parsing Bytes records is more thoroughly exercised by
 
144
        TestBytesRecordReader.  This test is here to ensure that
 
145
        ContainerReader's integration with BytesRecordReader is working.
 
146
        """
 
147
        reader = self.get_reader_for(
 
148
            "Bazaar pack format 1 (introduced in 0.18)\nB5\n\naaaaaE")
 
149
        expected_records = [([], 'aaaaa')]
 
150
        self.assertEqual(
 
151
            expected_records,
 
152
            [(names, read_bytes(None))
 
153
             for (names, read_bytes) in reader.iter_records()])
 
154
 
 
155
    def test_validate_empty_container(self):
 
156
        """validate does not raise an error for a container with no records."""
 
157
        reader = self.get_reader_for("Bazaar pack format 1 (introduced in 0.18)\nE")
 
158
        # No exception raised
 
159
        reader.validate()
 
160
 
 
161
    def test_validate_non_empty_valid_container(self):
 
162
        """validate does not raise an error for a container with a valid record.
 
163
        """
 
164
        reader = self.get_reader_for(
 
165
            "Bazaar pack format 1 (introduced in 0.18)\nB3\nname\n\nabcE")
 
166
        # No exception raised
 
167
        reader.validate()
 
168
 
 
169
    def test_validate_bad_format(self):
 
170
        """validate raises an error for unrecognised format strings.
 
171
 
 
172
        It may raise either UnexpectedEndOfContainerError or
 
173
        UnknownContainerFormatError, depending on exactly what the string is.
 
174
        """
 
175
        inputs = ["", "x", "Bazaar pack format 1 (introduced in 0.18)", "bad\n"]
 
176
        for input in inputs:
 
177
            reader = self.get_reader_for(input)
 
178
            self.assertRaises(
 
179
                (errors.UnexpectedEndOfContainerError,
 
180
                 errors.UnknownContainerFormatError),
 
181
                reader.validate)
 
182
 
 
183
    def test_validate_bad_record_marker(self):
 
184
        """validate raises UnknownRecordTypeError for unrecognised record
 
185
        types.
 
186
        """
 
187
        reader = self.get_reader_for(
 
188
            "Bazaar pack format 1 (introduced in 0.18)\nX")
 
189
        self.assertRaises(errors.UnknownRecordTypeError, reader.validate)
 
190
 
 
191
    def test_validate_data_after_end_marker(self):
 
192
        """validate raises ContainerHasExcessDataError if there are any bytes
 
193
        after the end of the container.
 
194
        """
 
195
        reader = self.get_reader_for(
 
196
            "Bazaar pack format 1 (introduced in 0.18)\nEcrud")
 
197
        self.assertRaises(
 
198
            errors.ContainerHasExcessDataError, reader.validate)
 
199
 
 
200
    def test_validate_no_end_marker(self):
 
201
        """validate raises UnexpectedEndOfContainerError if there's no end of
 
202
        container marker, even if the container up to this point has been valid.
 
203
        """
 
204
        reader = self.get_reader_for(
 
205
            "Bazaar pack format 1 (introduced in 0.18)\n")
 
206
        self.assertRaises(
 
207
            errors.UnexpectedEndOfContainerError, reader.validate)
 
208
 
 
209
    def test_validate_duplicate_name(self):
 
210
        """validate raises DuplicateRecordNameError if the same name occurs
 
211
        multiple times in the container.
 
212
        """
 
213
        reader = self.get_reader_for(
 
214
            "Bazaar pack format 1 (introduced in 0.18)\n"
 
215
            "B0\nname\n\n"
 
216
            "B0\nname\n\n"
 
217
            "E")
 
218
        self.assertRaises(errors.DuplicateRecordNameError, reader.validate)
 
219
 
 
220
    def test_validate_undecodeable_name(self):
 
221
        """Names that aren't valid UTF-8 cause validate to fail."""
 
222
        reader = self.get_reader_for(
 
223
            "Bazaar pack format 1 (introduced in 0.18)\nB0\n\xcc\n\nE")
 
224
        self.assertRaises(errors.InvalidRecordError, reader.validate)
 
225
        
 
226
 
 
227
class TestBytesRecordReader(tests.TestCase):
 
228
    """Tests for reading and validating Bytes records with BytesRecordReader."""
 
229
 
 
230
    def get_reader_for(self, bytes):
 
231
        stream = StringIO(bytes)
 
232
        reader = pack.BytesRecordReader(stream)
 
233
        return reader
 
234
 
 
235
    def test_record_with_no_name(self):
 
236
        """Reading a Bytes record with no name returns an empty list of
 
237
        names.
 
238
        """
 
239
        reader = self.get_reader_for("5\n\naaaaa")
 
240
        names, get_bytes = reader.read()
 
241
        self.assertEqual([], names)
 
242
        self.assertEqual('aaaaa', get_bytes(None))
 
243
 
 
244
    def test_record_with_one_name(self):
 
245
        """Reading a Bytes record with one name returns a list of just that
 
246
        name.
 
247
        """
 
248
        reader = self.get_reader_for("5\nname1\n\naaaaa")
 
249
        names, get_bytes = reader.read()
 
250
        self.assertEqual(['name1'], names)
 
251
        self.assertEqual('aaaaa', get_bytes(None))
 
252
 
 
253
    def test_record_with_two_names(self):
 
254
        """Reading a Bytes record with two names returns a list of both names.
 
255
        """
 
256
        reader = self.get_reader_for("5\nname1\nname2\n\naaaaa")
 
257
        names, get_bytes = reader.read()
 
258
        self.assertEqual(['name1', 'name2'], names)
 
259
        self.assertEqual('aaaaa', get_bytes(None))
 
260
 
 
261
    def test_invalid_length(self):
 
262
        """If the length-prefix is not a number, parsing raises
 
263
        InvalidRecordError.
 
264
        """
 
265
        reader = self.get_reader_for("not a number\n")
 
266
        self.assertRaises(errors.InvalidRecordError, reader.read)
 
267
 
 
268
    def test_early_eof(self):
 
269
        """Tests for premature EOF occuring during parsing Bytes records with
 
270
        BytesRecordReader.
 
271
        
 
272
        A incomplete container might be interrupted at any point.  The
 
273
        BytesRecordReader needs to cope with the input stream running out no
 
274
        matter where it is in the parsing process.
 
275
 
 
276
        In all cases, UnexpectedEndOfContainerError should be raised.
 
277
        """
 
278
        complete_record = "6\nname\n\nabcdef"
 
279
        for count in range(0, len(complete_record)):
 
280
            incomplete_record = complete_record[:count]
 
281
            reader = self.get_reader_for(incomplete_record)
 
282
            # We don't use assertRaises to make diagnosing failures easier
 
283
            # (assertRaises doesn't allow a custom failure message).
 
284
            try:
 
285
                names, read_bytes = reader.read()
 
286
                read_bytes(None)
 
287
            except errors.UnexpectedEndOfContainerError:
 
288
                pass
 
289
            else:
 
290
                self.fail(
 
291
                    "UnexpectedEndOfContainerError not raised when parsing %r"
 
292
                    % (incomplete_record,))
 
293
 
 
294
    def test_initial_eof(self):
 
295
        """EOF before any bytes read at all."""
 
296
        reader = self.get_reader_for("")
 
297
        self.assertRaises(errors.UnexpectedEndOfContainerError, reader.read)
 
298
 
 
299
    def test_eof_after_length(self):
 
300
        """EOF after reading the length and before reading name(s)."""
 
301
        reader = self.get_reader_for("123\n")
 
302
        self.assertRaises(errors.UnexpectedEndOfContainerError, reader.read)
 
303
 
 
304
    def test_eof_during_name(self):
 
305
        """EOF during reading a name."""
 
306
        reader = self.get_reader_for("123\nname")
 
307
        self.assertRaises(errors.UnexpectedEndOfContainerError, reader.read)
 
308
 
 
309
    def test_read_invalid_name_whitespace(self):
 
310
        """Names must have no whitespace."""
 
311
        # A name with a space.
 
312
        reader = self.get_reader_for("0\nbad name\n\n")
 
313
        self.assertRaises(errors.InvalidRecordError, reader.read)
 
314
 
 
315
        # A name with a tab.
 
316
        reader = self.get_reader_for("0\nbad\tname\n\n")
 
317
        self.assertRaises(errors.InvalidRecordError, reader.read)
 
318
 
 
319
        # A name with a vertical tab.
 
320
        reader = self.get_reader_for("0\nbad\vname\n\n")
 
321
        self.assertRaises(errors.InvalidRecordError, reader.read)
 
322
 
 
323
    def test_validate_whitespace_in_name(self):
 
324
        """Names must have no whitespace."""
 
325
        reader = self.get_reader_for("0\nbad name\n\n")
 
326
        self.assertRaises(errors.InvalidRecordError, reader.validate)
 
327
 
 
328
    def test_validate_interrupted_prelude(self):
 
329
        """EOF during reading a record's prelude causes validate to fail."""
 
330
        reader = self.get_reader_for("")
 
331
        self.assertRaises(
 
332
            errors.UnexpectedEndOfContainerError, reader.validate)
 
333
 
 
334
    def test_validate_interrupted_body(self):
 
335
        """EOF during reading a record's body causes validate to fail."""
 
336
        reader = self.get_reader_for("1\n\n")
 
337
        self.assertRaises(
 
338
            errors.UnexpectedEndOfContainerError, reader.validate)
 
339
 
 
340
    def test_validate_unparseable_length(self):
 
341
        """An unparseable record length causes validate to fail."""
 
342
        reader = self.get_reader_for("\n\n")
 
343
        self.assertRaises(
 
344
            errors.InvalidRecordError, reader.validate)
 
345
 
 
346
    def test_validate_undecodeable_name(self):
 
347
        """Names that aren't valid UTF-8 cause validate to fail."""
 
348
        reader = self.get_reader_for("0\n\xcc\n\n")
 
349
        self.assertRaises(errors.InvalidRecordError, reader.validate)
 
350
 
 
351
    def test_read_max_length(self):
 
352
        """If the max_length passed to the callable returned by read is not
 
353
        None, then no more than that many bytes will be read.
 
354
        """
 
355
        reader = self.get_reader_for("6\n\nabcdef")
 
356
        names, get_bytes = reader.read()
 
357
        self.assertEqual('abc', get_bytes(3))
 
358
 
 
359
    def test_read_no_max_length(self):
 
360
        """If the max_length passed to the callable returned by read is None,
 
361
        then all the bytes in the record will be read.
 
362
        """
 
363
        reader = self.get_reader_for("6\n\nabcdef")
 
364
        names, get_bytes = reader.read()
 
365
        self.assertEqual('abcdef', get_bytes(None))
 
366
 
 
367
    def test_repeated_read_calls(self):
 
368
        """Repeated calls to the callable returned from BytesRecordReader.read
 
369
        will not read beyond the end of the record.
 
370
        """
 
371
        reader = self.get_reader_for("6\n\nabcdefB3\nnext-record\nXXX")
 
372
        names, get_bytes = reader.read()
 
373
        self.assertEqual('abcdef', get_bytes(None))
 
374
        self.assertEqual('', get_bytes(None))
 
375
        self.assertEqual('', get_bytes(99))
 
376
 
 
377