~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_pack.py

Compare URLs in RemoteRepository.__eq__, rather than '_client' attributes.

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