~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_pack.py

  • Committer: Martin Pool
  • Date: 2005-07-23 13:59:30 UTC
  • Revision ID: mbp@sourcefrog.net-20050723135930-d81530c82c925cb0
- less dodgy is_inside function

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
 
        offset, length = writer.add_bytes_record('abc', names=[])
58
 
        self.assertEqual((42, 7), (offset, length))
59
 
        self.assertEqual('Bazaar pack format 1 (introduced in 0.18)\nB3\n\nabc',
60
 
                         output.getvalue())
61
 
 
62
 
    def test_add_bytes_record_one_name(self):
63
 
        """Add a bytes record with one name."""
64
 
        output = StringIO()
65
 
        writer = pack.ContainerWriter(output.write)
66
 
        writer.begin()
67
 
        offset, length = writer.add_bytes_record('abc', names=[('name1', )])
68
 
        self.assertEqual((42, 13), (offset, length))
69
 
        self.assertEqual(
70
 
            'Bazaar pack format 1 (introduced in 0.18)\n'
71
 
            'B3\nname1\n\nabc',
72
 
            output.getvalue())
73
 
 
74
 
    def test_add_bytes_record_two_names(self):
75
 
        """Add a bytes record with two names."""
76
 
        output = StringIO()
77
 
        writer = pack.ContainerWriter(output.write)
78
 
        writer.begin()
79
 
        offset, length = writer.add_bytes_record('abc', names=[('name1', ), ('name2', )])
80
 
        self.assertEqual((42, 19), (offset, length))
81
 
        self.assertEqual(
82
 
            'Bazaar pack format 1 (introduced in 0.18)\n'
83
 
            'B3\nname1\nname2\n\nabc',
84
 
            output.getvalue())
85
 
 
86
 
    def test_add_bytes_record_two_names(self):
87
 
        """Add a bytes record with two names."""
88
 
        output = StringIO()
89
 
        writer = pack.ContainerWriter(output.write)
90
 
        writer.begin()
91
 
        offset, length = writer.add_bytes_record('abc', names=[('name1', ), ('name2', )])
92
 
        self.assertEqual((42, 19), (offset, length))
93
 
        self.assertEqual(
94
 
            'Bazaar pack format 1 (introduced in 0.18)\n'
95
 
            'B3\nname1\nname2\n\nabc',
96
 
            output.getvalue())
97
 
 
98
 
    def test_add_bytes_record_two_element_name(self):
99
 
        """Add a bytes record with a two-element name."""
100
 
        output = StringIO()
101
 
        writer = pack.ContainerWriter(output.write)
102
 
        writer.begin()
103
 
        offset, length = writer.add_bytes_record('abc', names=[('name1', 'name2')])
104
 
        self.assertEqual((42, 19), (offset, length))
105
 
        self.assertEqual(
106
 
            'Bazaar pack format 1 (introduced in 0.18)\n'
107
 
            'B3\nname1\x00name2\n\nabc',
108
 
            output.getvalue())
109
 
 
110
 
    def test_add_second_bytes_record_gets_higher_offset(self):
111
 
        output = StringIO()
112
 
        writer = pack.ContainerWriter(output.write)
113
 
        writer.begin()
114
 
        writer.add_bytes_record('abc', names=[])
115
 
        offset, length = writer.add_bytes_record('abc', names=[])
116
 
        self.assertEqual((49, 7), (offset, length))
117
 
        self.assertEqual(
118
 
            'Bazaar pack format 1 (introduced in 0.18)\n'
119
 
            'B3\n\nabc'
120
 
            'B3\n\nabc',
121
 
            output.getvalue())
122
 
 
123
 
    def test_add_bytes_record_invalid_name(self):
124
 
        """Adding a Bytes record with a name with whitespace in it raises
125
 
        InvalidRecordError.
126
 
        """
127
 
        output = StringIO()
128
 
        writer = pack.ContainerWriter(output.write)
129
 
        writer.begin()
130
 
        self.assertRaises(
131
 
            errors.InvalidRecordError,
132
 
            writer.add_bytes_record, 'abc', names=[('bad name', )])
133
 
 
134
 
 
135
 
class TestContainerReader(tests.TestCase):
136
 
 
137
 
    def get_reader_for(self, bytes):
138
 
        stream = StringIO(bytes)
139
 
        reader = pack.ContainerReader(stream)
140
 
        return reader
141
 
 
142
 
    def test_construct(self):
143
 
        """Test constructing a ContainerReader.
144
 
        
145
 
        This uses None as the output stream to show that the constructor doesn't
146
 
        try to use the input stream.
147
 
        """
148
 
        reader = pack.ContainerReader(None)
149
 
 
150
 
    def test_empty_container(self):
151
 
        """Read an empty container."""
152
 
        reader = self.get_reader_for(
153
 
            "Bazaar pack format 1 (introduced in 0.18)\nE")
154
 
        self.assertEqual([], list(reader.iter_records()))
155
 
 
156
 
    def test_unknown_format(self):
157
 
        """Unrecognised container formats raise UnknownContainerFormatError."""
158
 
        reader = self.get_reader_for("unknown format\n")
159
 
        self.assertRaises(
160
 
            errors.UnknownContainerFormatError, reader.iter_records)
161
 
 
162
 
    def test_unexpected_end_of_container(self):
163
 
        """Containers that don't end with an End Marker record should cause
164
 
        UnexpectedEndOfContainerError to be raised.
165
 
        """
166
 
        reader = self.get_reader_for(
167
 
            "Bazaar pack format 1 (introduced in 0.18)\n")
168
 
        iterator = reader.iter_records()
169
 
        self.assertRaises(
170
 
            errors.UnexpectedEndOfContainerError, iterator.next)
171
 
 
172
 
    def test_unknown_record_type(self):
173
 
        """Unknown record types cause UnknownRecordTypeError to be raised."""
174
 
        reader = self.get_reader_for(
175
 
            "Bazaar pack format 1 (introduced in 0.18)\nX")
176
 
        iterator = reader.iter_records()
177
 
        self.assertRaises(
178
 
            errors.UnknownRecordTypeError, iterator.next)
179
 
 
180
 
    def test_container_with_one_unnamed_record(self):
181
 
        """Read a container with one Bytes record.
182
 
        
183
 
        Parsing Bytes records is more thoroughly exercised by
184
 
        TestBytesRecordReader.  This test is here to ensure that
185
 
        ContainerReader's integration with BytesRecordReader is working.
186
 
        """
187
 
        reader = self.get_reader_for(
188
 
            "Bazaar pack format 1 (introduced in 0.18)\nB5\n\naaaaaE")
189
 
        expected_records = [([], 'aaaaa')]
190
 
        self.assertEqual(
191
 
            expected_records,
192
 
            [(names, read_bytes(None))
193
 
             for (names, read_bytes) in reader.iter_records()])
194
 
 
195
 
    def test_validate_empty_container(self):
196
 
        """validate does not raise an error for a container with no records."""
197
 
        reader = self.get_reader_for("Bazaar pack format 1 (introduced in 0.18)\nE")
198
 
        # No exception raised
199
 
        reader.validate()
200
 
 
201
 
    def test_validate_non_empty_valid_container(self):
202
 
        """validate does not raise an error for a container with a valid record.
203
 
        """
204
 
        reader = self.get_reader_for(
205
 
            "Bazaar pack format 1 (introduced in 0.18)\nB3\nname\n\nabcE")
206
 
        # No exception raised
207
 
        reader.validate()
208
 
 
209
 
    def test_validate_bad_format(self):
210
 
        """validate raises an error for unrecognised format strings.
211
 
 
212
 
        It may raise either UnexpectedEndOfContainerError or
213
 
        UnknownContainerFormatError, depending on exactly what the string is.
214
 
        """
215
 
        inputs = ["", "x", "Bazaar pack format 1 (introduced in 0.18)", "bad\n"]
216
 
        for input in inputs:
217
 
            reader = self.get_reader_for(input)
218
 
            self.assertRaises(
219
 
                (errors.UnexpectedEndOfContainerError,
220
 
                 errors.UnknownContainerFormatError),
221
 
                reader.validate)
222
 
 
223
 
    def test_validate_bad_record_marker(self):
224
 
        """validate raises UnknownRecordTypeError for unrecognised record
225
 
        types.
226
 
        """
227
 
        reader = self.get_reader_for(
228
 
            "Bazaar pack format 1 (introduced in 0.18)\nX")
229
 
        self.assertRaises(errors.UnknownRecordTypeError, reader.validate)
230
 
 
231
 
    def test_validate_data_after_end_marker(self):
232
 
        """validate raises ContainerHasExcessDataError if there are any bytes
233
 
        after the end of the container.
234
 
        """
235
 
        reader = self.get_reader_for(
236
 
            "Bazaar pack format 1 (introduced in 0.18)\nEcrud")
237
 
        self.assertRaises(
238
 
            errors.ContainerHasExcessDataError, reader.validate)
239
 
 
240
 
    def test_validate_no_end_marker(self):
241
 
        """validate raises UnexpectedEndOfContainerError if there's no end of
242
 
        container marker, even if the container up to this point has been valid.
243
 
        """
244
 
        reader = self.get_reader_for(
245
 
            "Bazaar pack format 1 (introduced in 0.18)\n")
246
 
        self.assertRaises(
247
 
            errors.UnexpectedEndOfContainerError, reader.validate)
248
 
 
249
 
    def test_validate_duplicate_name(self):
250
 
        """validate raises DuplicateRecordNameError if the same name occurs
251
 
        multiple times in the container.
252
 
        """
253
 
        reader = self.get_reader_for(
254
 
            "Bazaar pack format 1 (introduced in 0.18)\n"
255
 
            "B0\nname\n\n"
256
 
            "B0\nname\n\n"
257
 
            "E")
258
 
        self.assertRaises(errors.DuplicateRecordNameError, reader.validate)
259
 
 
260
 
    def test_validate_undecodeable_name(self):
261
 
        """Names that aren't valid UTF-8 cause validate to fail."""
262
 
        reader = self.get_reader_for(
263
 
            "Bazaar pack format 1 (introduced in 0.18)\nB0\n\xcc\n\nE")
264
 
        self.assertRaises(errors.InvalidRecordError, reader.validate)
265
 
        
266
 
 
267
 
class TestBytesRecordReader(tests.TestCase):
268
 
    """Tests for reading and validating Bytes records with BytesRecordReader."""
269
 
 
270
 
    def get_reader_for(self, bytes):
271
 
        stream = StringIO(bytes)
272
 
        reader = pack.BytesRecordReader(stream)
273
 
        return reader
274
 
 
275
 
    def test_record_with_no_name(self):
276
 
        """Reading a Bytes record with no name returns an empty list of
277
 
        names.
278
 
        """
279
 
        reader = self.get_reader_for("5\n\naaaaa")
280
 
        names, get_bytes = reader.read()
281
 
        self.assertEqual([], names)
282
 
        self.assertEqual('aaaaa', get_bytes(None))
283
 
 
284
 
    def test_record_with_one_name(self):
285
 
        """Reading a Bytes record with one name returns a list of just that
286
 
        name.
287
 
        """
288
 
        reader = self.get_reader_for("5\nname1\n\naaaaa")
289
 
        names, get_bytes = reader.read()
290
 
        self.assertEqual([('name1', )], names)
291
 
        self.assertEqual('aaaaa', get_bytes(None))
292
 
 
293
 
    def test_record_with_two_names(self):
294
 
        """Reading a Bytes record with two names returns a list of both names.
295
 
        """
296
 
        reader = self.get_reader_for("5\nname1\nname2\n\naaaaa")
297
 
        names, get_bytes = reader.read()
298
 
        self.assertEqual([('name1', ), ('name2', )], names)
299
 
        self.assertEqual('aaaaa', get_bytes(None))
300
 
 
301
 
    def test_record_with_two_part_names(self):
302
 
        """Reading a Bytes record with a two_part name reads both."""
303
 
        reader = self.get_reader_for("5\nname1\x00name2\n\naaaaa")
304
 
        names, get_bytes = reader.read()
305
 
        self.assertEqual([('name1', 'name2', )], names)
306
 
        self.assertEqual('aaaaa', get_bytes(None))
307
 
 
308
 
    def test_invalid_length(self):
309
 
        """If the length-prefix is not a number, parsing raises
310
 
        InvalidRecordError.
311
 
        """
312
 
        reader = self.get_reader_for("not a number\n")
313
 
        self.assertRaises(errors.InvalidRecordError, reader.read)
314
 
 
315
 
    def test_early_eof(self):
316
 
        """Tests for premature EOF occuring during parsing Bytes records with
317
 
        BytesRecordReader.
318
 
        
319
 
        A incomplete container might be interrupted at any point.  The
320
 
        BytesRecordReader needs to cope with the input stream running out no
321
 
        matter where it is in the parsing process.
322
 
 
323
 
        In all cases, UnexpectedEndOfContainerError should be raised.
324
 
        """
325
 
        complete_record = "6\nname\n\nabcdef"
326
 
        for count in range(0, len(complete_record)):
327
 
            incomplete_record = complete_record[:count]
328
 
            reader = self.get_reader_for(incomplete_record)
329
 
            # We don't use assertRaises to make diagnosing failures easier
330
 
            # (assertRaises doesn't allow a custom failure message).
331
 
            try:
332
 
                names, read_bytes = reader.read()
333
 
                read_bytes(None)
334
 
            except errors.UnexpectedEndOfContainerError:
335
 
                pass
336
 
            else:
337
 
                self.fail(
338
 
                    "UnexpectedEndOfContainerError not raised when parsing %r"
339
 
                    % (incomplete_record,))
340
 
 
341
 
    def test_initial_eof(self):
342
 
        """EOF before any bytes read at all."""
343
 
        reader = self.get_reader_for("")
344
 
        self.assertRaises(errors.UnexpectedEndOfContainerError, reader.read)
345
 
 
346
 
    def test_eof_after_length(self):
347
 
        """EOF after reading the length and before reading name(s)."""
348
 
        reader = self.get_reader_for("123\n")
349
 
        self.assertRaises(errors.UnexpectedEndOfContainerError, reader.read)
350
 
 
351
 
    def test_eof_during_name(self):
352
 
        """EOF during reading a name."""
353
 
        reader = self.get_reader_for("123\nname")
354
 
        self.assertRaises(errors.UnexpectedEndOfContainerError, reader.read)
355
 
 
356
 
    def test_read_invalid_name_whitespace(self):
357
 
        """Names must have no whitespace."""
358
 
        # A name with a space.
359
 
        reader = self.get_reader_for("0\nbad name\n\n")
360
 
        self.assertRaises(errors.InvalidRecordError, reader.read)
361
 
 
362
 
        # A name with a tab.
363
 
        reader = self.get_reader_for("0\nbad\tname\n\n")
364
 
        self.assertRaises(errors.InvalidRecordError, reader.read)
365
 
 
366
 
        # A name with a vertical tab.
367
 
        reader = self.get_reader_for("0\nbad\vname\n\n")
368
 
        self.assertRaises(errors.InvalidRecordError, reader.read)
369
 
 
370
 
    def test_validate_whitespace_in_name(self):
371
 
        """Names must have no whitespace."""
372
 
        reader = self.get_reader_for("0\nbad name\n\n")
373
 
        self.assertRaises(errors.InvalidRecordError, reader.validate)
374
 
 
375
 
    def test_validate_interrupted_prelude(self):
376
 
        """EOF during reading a record's prelude causes validate to fail."""
377
 
        reader = self.get_reader_for("")
378
 
        self.assertRaises(
379
 
            errors.UnexpectedEndOfContainerError, reader.validate)
380
 
 
381
 
    def test_validate_interrupted_body(self):
382
 
        """EOF during reading a record's body causes validate to fail."""
383
 
        reader = self.get_reader_for("1\n\n")
384
 
        self.assertRaises(
385
 
            errors.UnexpectedEndOfContainerError, reader.validate)
386
 
 
387
 
    def test_validate_unparseable_length(self):
388
 
        """An unparseable record length causes validate to fail."""
389
 
        reader = self.get_reader_for("\n\n")
390
 
        self.assertRaises(
391
 
            errors.InvalidRecordError, reader.validate)
392
 
 
393
 
    def test_validate_undecodeable_name(self):
394
 
        """Names that aren't valid UTF-8 cause validate to fail."""
395
 
        reader = self.get_reader_for("0\n\xcc\n\n")
396
 
        self.assertRaises(errors.InvalidRecordError, reader.validate)
397
 
 
398
 
    def test_read_max_length(self):
399
 
        """If the max_length passed to the callable returned by read is not
400
 
        None, then no more than that many bytes will be read.
401
 
        """
402
 
        reader = self.get_reader_for("6\n\nabcdef")
403
 
        names, get_bytes = reader.read()
404
 
        self.assertEqual('abc', get_bytes(3))
405
 
 
406
 
    def test_read_no_max_length(self):
407
 
        """If the max_length passed to the callable returned by read is None,
408
 
        then all the bytes in the record will be read.
409
 
        """
410
 
        reader = self.get_reader_for("6\n\nabcdef")
411
 
        names, get_bytes = reader.read()
412
 
        self.assertEqual('abcdef', get_bytes(None))
413
 
 
414
 
    def test_repeated_read_calls(self):
415
 
        """Repeated calls to the callable returned from BytesRecordReader.read
416
 
        will not read beyond the end of the record.
417
 
        """
418
 
        reader = self.get_reader_for("6\n\nabcdefB3\nnext-record\nXXX")
419
 
        names, get_bytes = reader.read()
420
 
        self.assertEqual('abcdef', get_bytes(None))
421
 
        self.assertEqual('', get_bytes(None))
422
 
        self.assertEqual('', get_bytes(99))
423
 
 
424
 
 
425
 
class TestMakeReadvReader(tests.TestCaseWithTransport):
426
 
 
427
 
    def test_read_skipping_records(self):
428
 
        pack_data = StringIO()
429
 
        writer = pack.ContainerWriter(pack_data.write)
430
 
        writer.begin()
431
 
        memos = []
432
 
        memos.append(writer.add_bytes_record('abc', names=[]))
433
 
        memos.append(writer.add_bytes_record('def', names=[('name1', )]))
434
 
        memos.append(writer.add_bytes_record('ghi', names=[('name2', )]))
435
 
        memos.append(writer.add_bytes_record('jkl', names=[]))
436
 
        writer.end()
437
 
        transport = self.get_transport()
438
 
        transport.put_bytes('mypack', pack_data.getvalue())
439
 
        requested_records = [memos[0], memos[2]]
440
 
        reader = pack.make_readv_reader(transport, 'mypack', requested_records)
441
 
        result = []
442
 
        for names, reader_func in reader.iter_records():
443
 
            result.append((names, reader_func(None)))
444
 
        self.assertEqual([([], 'abc'), ([('name2', )], 'ghi')], result)
445
 
 
446
 
 
447
 
class TestReadvFile(tests.TestCaseWithTransport):
448
 
    """Tests of the ReadVFile class.
449
 
 
450
 
    Error cases are deliberately undefined: this code adapts the underlying
451
 
    transport interface to a single 'streaming read' interface as 
452
 
    ContainerReader needs.
453
 
    """
454
 
 
455
 
    def test_read_bytes(self):
456
 
        """Test reading of both single bytes and all bytes in a hunk."""
457
 
        transport = self.get_transport()
458
 
        transport.put_bytes('sample', '0123456789')
459
 
        f = pack.ReadVFile(transport.readv('sample', [(0,1), (1,2), (4,1), (6,2)]))
460
 
        results = []
461
 
        results.append(f.read(1))
462
 
        results.append(f.read(2))
463
 
        results.append(f.read(1))
464
 
        results.append(f.read(1))
465
 
        results.append(f.read(1))
466
 
        self.assertEqual(['0', '12', '4', '6', '7'], results)
467
 
 
468
 
    def test_readline(self):
469
 
        """Test using readline() as ContainerReader does.
470
 
 
471
 
        This is always within a readv hunk, never across it.
472
 
        """
473
 
        transport = self.get_transport()
474
 
        transport.put_bytes('sample', '0\n2\n4\n')
475
 
        f = pack.ReadVFile(transport.readv('sample', [(0,2), (2,4)]))
476
 
        results = []
477
 
        results.append(f.readline())
478
 
        results.append(f.readline())
479
 
        results.append(f.readline())
480
 
        self.assertEqual(['0\n', '2\n', '4\n'], results)
481
 
 
482
 
    def test_readline_and_read(self):
483
 
        """Test exercising one byte reads, readline, and then read again."""
484
 
        transport = self.get_transport()
485
 
        transport.put_bytes('sample', '0\n2\n4\n')
486
 
        f = pack.ReadVFile(transport.readv('sample', [(0,6)]))
487
 
        results = []
488
 
        results.append(f.read(1))
489
 
        results.append(f.readline())
490
 
        results.append(f.read(4))
491
 
        self.assertEqual(['0', '\n', '2\n4\n'], results)