/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_http_response.py

  • Committer: Breezy landing bot
  • Author(s): Colin Watson
  • Date: 2020-11-16 21:47:08 UTC
  • mfrom: (7521.1.1 remove-lp-workaround)
  • Revision ID: breezy.the.bot@gmail.com-20201116214708-jos209mgxi41oy15
Remove breezy.git workaround for bazaar.launchpad.net.

Merged from https://code.launchpad.net/~cjwatson/brz/remove-lp-workaround/+merge/393710

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006-2010, 2012, 2013, 2016 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
37
37
  InvalidHttpResponse.
38
38
"""
39
39
 
40
 
from cStringIO import StringIO
41
 
import httplib
42
 
 
43
 
from bzrlib import (
 
40
from io import BytesIO
 
41
 
 
42
import http.client as http_client
 
43
 
 
44
parse_headers = http_client.parse_headers
 
45
 
 
46
from .. import (
44
47
    errors,
45
48
    tests,
46
49
    )
47
 
from bzrlib.transport.http import (
 
50
from ..transport.http import (
48
51
    response,
49
 
    _urllib2_wrappers,
 
52
    HTTPConnection,
50
53
    )
51
 
from bzrlib.tests.file_utils import (
 
54
from .file_utils import (
52
55
    FakeReadFile,
53
56
    )
54
57
 
57
60
    """A socket-like object that can be given a predefined content."""
58
61
 
59
62
    def __init__(self, data):
60
 
        self.readfile = StringIO(data)
 
63
        self.readfile = BytesIO(data)
61
64
 
62
65
    def makefile(self, mode='r', bufsize=None):
63
66
        return self.readfile
64
67
 
65
68
 
66
 
class FakeHTTPConnection(_urllib2_wrappers.HTTPConnection):
 
69
class FakeHTTPConnection(HTTPConnection):
67
70
 
68
71
    def __init__(self, sock):
69
 
        _urllib2_wrappers.HTTPConnection.__init__(self, 'localhost')
 
72
        HTTPConnection.__init__(self, 'localhost')
70
73
        # Set the socket to bypass the connection
71
74
        self.sock = sock
72
75
 
78
81
class TestResponseFileIter(tests.TestCase):
79
82
 
80
83
    def test_iter_empty(self):
81
 
        f = response.ResponseFile('empty', StringIO())
 
84
        f = response.ResponseFile('empty', BytesIO())
82
85
        self.assertEqual([], list(f))
83
86
 
84
87
    def test_iter_many(self):
85
 
        f = response.ResponseFile('many', StringIO('0\n1\nboo!\n'))
86
 
        self.assertEqual(['0\n', '1\n', 'boo!\n'], list(f))
 
88
        f = response.ResponseFile('many', BytesIO(b'0\n1\nboo!\n'))
 
89
        self.assertEqual([b'0\n', b'1\n', b'boo!\n'], list(f))
 
90
 
 
91
    def test_readlines(self):
 
92
        f = response.ResponseFile('many', BytesIO(b'0\n1\nboo!\n'))
 
93
        self.assertEqual([b'0\n', b'1\n', b'boo!\n'], f.readlines())
87
94
 
88
95
 
89
96
class TestHTTPConnection(tests.TestCase):
90
97
 
91
98
    def test_cleanup_pipe(self):
92
 
        sock = ReadSocket("""HTTP/1.1 200 OK\r
 
99
        sock = ReadSocket(b"""HTTP/1.1 200 OK\r
93
100
Content-Type: text/plain; charset=UTF-8\r
94
101
Content-Length: 18
95
102
\r
103
110
        # Now, get the response
104
111
        resp = conn.getresponse()
105
112
        # Read part of the response
106
 
        self.assertEquals('0123456789\n', resp.read(11))
 
113
        self.assertEqual(b'0123456789\n', resp.read(11))
107
114
        # Override the thresold to force the warning emission
108
 
        conn._range_warning_thresold = 6 # There are 7 bytes pending
 
115
        conn._range_warning_thresold = 6  # There are 7 bytes pending
109
116
        conn.cleanup_pipe()
110
117
        self.assertContainsRe(self.get_log(), 'Got a 200 response when asking')
111
118
 
117
124
    # which offsets are easy to calculate for test writers. It's used as a
118
125
    # building block with slight variations but basically 'a' is the first char
119
126
    # of the range and 'z' is the last.
120
 
    alpha = 'abcdefghijklmnopqrstuvwxyz'
 
127
    alpha = b'abcdefghijklmnopqrstuvwxyz'
121
128
 
122
129
    def test_can_read_at_first_access(self):
123
130
        """Test that the just created file can be read."""
124
 
        self.assertEquals(self.alpha, self._file.read())
 
131
        self.assertEqual(self.alpha, self._file.read())
125
132
 
126
133
    def test_seek_read(self):
127
134
        """Test seek/read inside the range."""
128
135
        f = self._file
129
136
        start = self.first_range_start
130
137
        # Before any use, tell() should be at the range start
131
 
        self.assertEquals(start, f.tell())
132
 
        cur = start # For an overall offset assertion
 
138
        self.assertEqual(start, f.tell())
 
139
        cur = start  # For an overall offset assertion
133
140
        f.seek(start + 3)
134
141
        cur += 3
135
 
        self.assertEquals('def', f.read(3))
 
142
        self.assertEqual(b'def', f.read(3))
136
143
        cur += len('def')
137
144
        f.seek(4, 1)
138
145
        cur += 4
139
 
        self.assertEquals('klmn', f.read(4))
 
146
        self.assertEqual(b'klmn', f.read(4))
140
147
        cur += len('klmn')
141
148
        # read(0) in the middle of a range
142
 
        self.assertEquals('', f.read(0))
 
149
        self.assertEqual(b'', f.read(0))
143
150
        # seek in place
144
151
        here = f.tell()
145
152
        f.seek(0, 1)
146
 
        self.assertEquals(here, f.tell())
147
 
        self.assertEquals(cur, f.tell())
 
153
        self.assertEqual(here, f.tell())
 
154
        self.assertEqual(cur, f.tell())
148
155
 
149
156
    def test_read_zero(self):
150
157
        f = self._file
151
 
        self.assertEquals('', f.read(0))
 
158
        self.assertEqual(b'', f.read(0))
152
159
        f.seek(10, 1)
153
 
        self.assertEquals('', f.read(0))
 
160
        self.assertEqual(b'', f.read(0))
154
161
 
155
162
    def test_seek_at_range_end(self):
156
163
        f = self._file
159
166
    def test_read_at_range_end(self):
160
167
        """Test read behaviour at range end."""
161
168
        f = self._file
162
 
        self.assertEquals(self.alpha, f.read())
163
 
        self.assertEquals('', f.read(0))
 
169
        self.assertEqual(self.alpha, f.read())
 
170
        self.assertEqual(b'', f.read(0))
164
171
        self.assertRaises(errors.InvalidRange, f.read, 1)
165
172
 
166
173
    def test_unbounded_read_after_seek(self):
167
174
        f = self._file
168
175
        f.seek(24, 1)
169
176
        # Should not cross ranges
170
 
        self.assertEquals('yz', f.read())
 
177
        self.assertEqual(b'yz', f.read())
171
178
 
172
179
    def test_seek_backwards(self):
173
180
        f = self._file
193
200
        self.assertRaises(errors.InvalidRange, f.read, 10)
194
201
 
195
202
    def test_seek_from_end(self):
196
 
       """Test seeking from the end of the file.
197
 
 
198
 
       The semantic is unclear in case of multiple ranges. Seeking from end
199
 
       exists only for the http transports, cannot be used if the file size is
200
 
       unknown and is not used in bzrlib itself. This test must be (and is)
201
 
       overridden by daughter classes.
202
 
 
203
 
       Reading from end makes sense only when a range has been requested from
204
 
       the end of the file (see HttpTransportBase._get() when using the
205
 
       'tail_amount' parameter). The HTTP response can only be a whole file or
206
 
       a single range.
207
 
       """
208
 
       f = self._file
209
 
       f.seek(-2, 2)
210
 
       self.assertEquals('yz', f.read())
 
203
        """Test seeking from the end of the file.
 
204
 
 
205
        The semantic is unclear in case of multiple ranges. Seeking from end
 
206
        exists only for the http transports, cannot be used if the file size is
 
207
        unknown and is not used in breezy itself. This test must be (and is)
 
208
        overridden by daughter classes.
 
209
 
 
210
        Reading from end makes sense only when a range has been requested from
 
211
        the end of the file (see HttpTransportBase._get() when using the
 
212
        'tail_amount' parameter). The HTTP response can only be a whole file or
 
213
        a single range.
 
214
        """
 
215
        f = self._file
 
216
        f.seek(-2, 2)
 
217
        self.assertEqual(b'yz', f.read())
211
218
 
212
219
 
213
220
class TestRangeFileSizeUnknown(tests.TestCase, TestRangeFileMixin):
216
223
    def setUp(self):
217
224
        super(TestRangeFileSizeUnknown, self).setUp()
218
225
        self._file = response.RangeFile('Whole_file_size_known',
219
 
                                        StringIO(self.alpha))
 
226
                                        BytesIO(self.alpha))
220
227
        # We define no range, relying on RangeFile to provide default values
221
 
        self.first_range_start = 0 # It's the whole file
 
228
        self.first_range_start = 0  # It's the whole file
222
229
 
223
230
    def test_seek_from_end(self):
224
231
        """See TestRangeFileMixin.test_seek_from_end.
230
237
    def test_read_at_range_end(self):
231
238
        """Test read behaviour at range end."""
232
239
        f = self._file
233
 
        self.assertEquals(self.alpha, f.read())
234
 
        self.assertEquals('', f.read(0))
235
 
        self.assertEquals('', f.read(1))
 
240
        self.assertEqual(self.alpha, f.read())
 
241
        self.assertEqual(b'', f.read(0))
 
242
        self.assertEqual(b'', f.read(1))
236
243
 
237
244
 
238
245
class TestRangeFileSizeKnown(tests.TestCase, TestRangeFileMixin):
241
248
    def setUp(self):
242
249
        super(TestRangeFileSizeKnown, self).setUp()
243
250
        self._file = response.RangeFile('Whole_file_size_known',
244
 
                                        StringIO(self.alpha))
 
251
                                        BytesIO(self.alpha))
245
252
        self._file.set_range(0, len(self.alpha))
246
 
        self.first_range_start = 0 # It's the whole file
 
253
        self.first_range_start = 0  # It's the whole file
247
254
 
248
255
 
249
256
class TestRangeFileSingleRange(tests.TestCase, TestRangeFileMixin):
252
259
    def setUp(self):
253
260
        super(TestRangeFileSingleRange, self).setUp()
254
261
        self._file = response.RangeFile('Single_range_file',
255
 
                                        StringIO(self.alpha))
 
262
                                        BytesIO(self.alpha))
256
263
        self.first_range_start = 15
257
264
        self._file.set_range(self.first_range_start, len(self.alpha))
258
265
 
259
 
 
260
266
    def test_read_before_range(self):
261
267
        # This can't occur under normal circumstances, we have to force it
262
268
        f = self._file
263
 
        f._pos = 0 # Force an invalid pos
 
269
        f._pos = 0  # Force an invalid pos
264
270
        self.assertRaises(errors.InvalidRange, f.read, 2)
265
271
 
266
272
 
281
287
    # in HTTP response headers and the boundary lines that separate
282
288
    # multipart content.
283
289
 
284
 
    boundary = "separation"
 
290
    boundary = b"separation"
285
291
 
286
292
    def setUp(self):
287
293
        super(TestRangeFileMultipleRanges, self).setUp()
288
294
 
289
295
        boundary = self.boundary
290
296
 
291
 
        content = ''
 
297
        content = b''
292
298
        self.first_range_start = 25
293
 
        file_size = 200 # big enough to encompass all ranges
 
299
        file_size = 200  # big enough to encompass all ranges
294
300
        for (start, part) in [(self.first_range_start, self.alpha),
295
301
                              # Two contiguous ranges
296
302
                              (100, self.alpha),
301
307
        content += self._boundary_line()
302
308
 
303
309
        self._file = response.RangeFile('Multiple_ranges_file',
304
 
                                        StringIO(content))
 
310
                                        BytesIO(content))
305
311
        self.set_file_boundary()
306
312
 
307
313
    def _boundary_line(self):
308
314
        """Helper to build the formatted boundary line."""
309
 
        return '--' + self.boundary + '\r\n'
 
315
        return b'--' + self.boundary + b'\r\n'
310
316
 
311
317
    def set_file_boundary(self):
312
318
        # Ranges are set by decoding the range headers, the RangeFile user is
315
321
        # which is part of the Content-Type header).
316
322
        self._file.set_boundary(self.boundary)
317
323
 
318
 
    def _multipart_byterange(self, data, offset, boundary, file_size='*'):
 
324
    def _multipart_byterange(self, data, offset, boundary, file_size=b'*'):
319
325
        """Encode a part of a file as a multipart/byterange MIME type.
320
326
 
321
327
        When a range request is issued, the HTTP response body can be
337
343
        # A range is described by a set of headers, but only 'Content-Range' is
338
344
        # required for our implementation (TestHandleResponse below will
339
345
        # exercise ranges with multiple or missing headers')
340
 
        range += 'Content-Range: bytes %d-%d/%d\r\n' % (offset,
341
 
                                                        offset+len(data)-1,
342
 
                                                        file_size)
343
 
        range += '\r\n'
 
346
        if isinstance(file_size, int):
 
347
            file_size = b'%d' % file_size
 
348
        range += b'Content-Range: bytes %d-%d/%s\r\n' % (offset,
 
349
                                                         offset +
 
350
                                                         len(data) - 1,
 
351
                                                         file_size)
 
352
        range += b'\r\n'
344
353
        # Finally the raw bytes
345
354
        range += data
346
355
        return range
347
356
 
348
357
    def test_read_all_ranges(self):
349
358
        f = self._file
350
 
        self.assertEquals(self.alpha, f.read()) # Read first range
351
 
        f.seek(100) # Trigger the second range recognition
352
 
        self.assertEquals(self.alpha, f.read()) # Read second range
353
 
        self.assertEquals(126, f.tell())
354
 
        f.seek(126) # Start of third range which is also the current pos !
355
 
        self.assertEquals('A', f.read(1))
 
359
        self.assertEqual(self.alpha, f.read())  # Read first range
 
360
        f.seek(100)  # Trigger the second range recognition
 
361
        self.assertEqual(self.alpha, f.read())  # Read second range
 
362
        self.assertEqual(126, f.tell())
 
363
        f.seek(126)  # Start of third range which is also the current pos !
 
364
        self.assertEqual(b'A', f.read(1))
356
365
        f.seek(10, 1)
357
 
        self.assertEquals('LMN', f.read(3))
 
366
        self.assertEqual(b'LMN', f.read(3))
358
367
 
359
368
    def test_seek_from_end(self):
360
369
        """See TestRangeFileMixin.test_seek_from_end."""
364
373
        # behaviour.
365
374
        f = self._file
366
375
        f.seek(-2, 2)
367
 
        self.assertEquals('yz', f.read())
 
376
        self.assertEqual(b'yz', f.read())
368
377
        self.assertRaises(errors.InvalidRange, f.seek, -2, 2)
369
378
 
370
379
    def test_seek_into_void(self):
381
390
 
382
391
    def test_seek_across_ranges(self):
383
392
        f = self._file
384
 
        f.seek(126) # skip the two first ranges
385
 
        self.assertEquals('AB', f.read(2))
 
393
        f.seek(126)  # skip the two first ranges
 
394
        self.assertEqual(b'AB', f.read(2))
386
395
 
387
396
    def test_checked_read_dont_overflow_buffers(self):
388
397
        f = self._file
389
398
        # We force a very low value to exercise all code paths in _checked_read
390
399
        f._discarded_buf_size = 8
391
 
        f.seek(126) # skip the two first ranges
392
 
        self.assertEquals('AB', f.read(2))
 
400
        f.seek(126)  # skip the two first ranges
 
401
        self.assertEqual(b'AB', f.read(2))
393
402
 
394
403
    def test_seek_twice_between_ranges(self):
395
404
        f = self._file
396
405
        start = self.first_range_start
397
 
        f.seek(start + 40) # Past the first range but before the second
 
406
        f.seek(start + 40)  # Past the first range but before the second
398
407
        # Now the file is positioned at the second range start (100)
399
408
        self.assertRaises(errors.InvalidRange, f.seek, start + 41)
400
409
 
407
416
 
408
417
    def test_read_at_range_end(self):
409
418
        f = self._file
410
 
        self.assertEquals(self.alpha, f.read())
411
 
        self.assertEquals(self.alpha, f.read())
412
 
        self.assertEquals(self.alpha.upper(), f.read())
 
419
        self.assertEqual(self.alpha, f.read())
 
420
        self.assertEqual(self.alpha, f.read())
 
421
        self.assertEqual(self.alpha.upper(), f.read())
413
422
        self.assertRaises(errors.InvalidHttpResponse, f.read, 1)
414
423
 
415
424
 
430
439
    """
431
440
    # The boundary as it appears in boundary lines
432
441
    # IIS 6 and 7 use this value
433
 
    _boundary_trimmed = "q1w2e3r4t5y6u7i8o9p0zaxscdvfbgnhmjklkl"
434
 
    boundary = '<' + _boundary_trimmed + '>'
 
442
    _boundary_trimmed = b"q1w2e3r4t5y6u7i8o9p0zaxscdvfbgnhmjklkl"
 
443
    boundary = b'<' + _boundary_trimmed + b'>'
435
444
 
436
445
    def set_file_boundary(self):
437
446
        # Emulate broken rfc822.unquote() here by removing angles
443
452
 
444
453
    def test_seek_whence(self):
445
454
        """Test the seek whence parameter values."""
446
 
        f = response.RangeFile('foo', StringIO('abc'))
 
455
        f = response.RangeFile('foo', BytesIO(b'abc'))
447
456
        f.set_range(0, 3)
448
457
        f.seek(0)
449
458
        f.seek(1, 1)
453
462
    def test_range_syntax(self):
454
463
        """Test the Content-Range scanning."""
455
464
 
456
 
        f = response.RangeFile('foo', StringIO())
 
465
        f = response.RangeFile('foo', BytesIO())
457
466
 
458
467
        def ok(expected, header_value):
459
468
            f.set_range_from_header(header_value)
460
469
            # Slightly peek under the covers to get the size
461
 
            self.assertEquals(expected, (f.tell(), f._size))
 
470
            self.assertEqual(expected, (f.tell(), f._size))
462
471
 
463
472
        ok((1, 10), 'bytes 1-10/11')
464
473
        ok((1, 10), 'bytes 1-10/*')
465
474
        ok((12, 2), '\tbytes 12-13/*')
466
475
        ok((28, 1), '  bytes 28-28/*')
467
476
        ok((2123, 2120), 'bytes  2123-4242/12310')
468
 
        ok((1, 10), 'bytes 1-10/ttt') # We don't check total (ttt)
 
477
        ok((1, 10), 'bytes 1-10/ttt')  # We don't check total (ttt)
469
478
 
470
479
        def nok(header_value):
471
480
            self.assertRaises(errors.InvalidHttpRange,
480
489
 
481
490
 
482
491
# Taken from real request responses
483
 
_full_text_response = (200, """HTTP/1.1 200 OK\r
 
492
_full_text_response = (200, b"""HTTP/1.1 200 OK\r
484
493
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
485
494
Server: Apache/2.0.54 (Fedora)\r
486
495
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
490
499
Connection: close\r
491
500
Content-Type: text/plain; charset=UTF-8\r
492
501
\r
493
 
""", """Bazaar-NG meta directory, format 1
 
502
""", b"""Bazaar-NG meta directory, format 1
494
503
""")
495
504
 
496
505
 
497
 
_single_range_response = (206, """HTTP/1.1 206 Partial Content\r
 
506
_single_range_response = (206, b"""HTTP/1.1 206 Partial Content\r
498
507
Date: Tue, 11 Jul 2006 04:45:22 GMT\r
499
508
Server: Apache/2.0.54 (Fedora)\r
500
509
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
505
514
Connection: close\r
506
515
Content-Type: text/plain; charset=UTF-8\r
507
516
\r
508
 
""", """mbp@sourcefrog.net-20050309040815-13242001617e4a06
 
517
""", b"""mbp@sourcefrog.net-20050309040815-13242001617e4a06
509
518
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e762""")
510
519
 
511
520
 
512
 
_single_range_no_content_type = (206, """HTTP/1.1 206 Partial Content\r
 
521
_single_range_no_content_type = (206, b"""HTTP/1.1 206 Partial Content\r
513
522
Date: Tue, 11 Jul 2006 04:45:22 GMT\r
514
523
Server: Apache/2.0.54 (Fedora)\r
515
524
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
519
528
Content-Range: bytes 100-199/93890\r
520
529
Connection: close\r
521
530
\r
522
 
""", """mbp@sourcefrog.net-20050309040815-13242001617e4a06
 
531
""", b"""mbp@sourcefrog.net-20050309040815-13242001617e4a06
523
532
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e762""")
524
533
 
525
534
 
526
 
_multipart_range_response = (206, """HTTP/1.1 206 Partial Content\r
 
535
_multipart_range_response = (206, b"""HTTP/1.1 206 Partial Content\r
527
536
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
528
537
Server: Apache/2.0.54 (Fedora)\r
529
538
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
533
542
Connection: close\r
534
543
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
535
544
\r
536
 
\r""", """--418470f848b63279b\r
 
545
\r""", b"""--418470f848b63279b\r
537
546
Content-type: text/plain; charset=UTF-8\r
538
547
Content-range: bytes 0-254/93890\r
539
548
\r
573
582
""")
574
583
 
575
584
 
576
 
_multipart_squid_range_response = (206, """HTTP/1.0 206 Partial Content\r
 
585
_multipart_squid_range_response = (206, b"""HTTP/1.0 206 Partial Content\r
577
586
Date: Thu, 31 Aug 2006 21:16:22 GMT\r
578
587
Server: Apache/2.2.2 (Unix) DAV/2\r
579
588
Last-Modified: Thu, 31 Aug 2006 17:57:06 GMT\r
585
594
Proxy-Connection: keep-alive\r
586
595
\r
587
596
""",
588
 
"""\r
 
597
                                   b"""\r
589
598
--squid/2.5.STABLE12:C99323425AD4FE26F726261FA6C24196\r
590
599
Content-Type: text/plain\r
591
600
Content-Range: bytes 0-99/18672\r
606
615
 
607
616
 
608
617
# This is made up
609
 
_full_text_response_no_content_type = (200, """HTTP/1.1 200 OK\r
 
618
_full_text_response_no_content_type = (200, b"""HTTP/1.1 200 OK\r
610
619
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
611
620
Server: Apache/2.0.54 (Fedora)\r
612
621
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
615
624
Content-Length: 35\r
616
625
Connection: close\r
617
626
\r
618
 
""", """Bazaar-NG meta directory, format 1
 
627
""", b"""Bazaar-NG meta directory, format 1
619
628
""")
620
629
 
621
630
 
622
 
_full_text_response_no_content_length = (200, """HTTP/1.1 200 OK\r
 
631
_full_text_response_no_content_length = (200, b"""HTTP/1.1 200 OK\r
623
632
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
624
633
Server: Apache/2.0.54 (Fedora)\r
625
634
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
628
637
Connection: close\r
629
638
Content-Type: text/plain; charset=UTF-8\r
630
639
\r
631
 
""", """Bazaar-NG meta directory, format 1
 
640
""", b"""Bazaar-NG meta directory, format 1
632
641
""")
633
642
 
634
643
 
635
 
_single_range_no_content_range = (206, """HTTP/1.1 206 Partial Content\r
 
644
_single_range_no_content_range = (206, b"""HTTP/1.1 206 Partial Content\r
636
645
Date: Tue, 11 Jul 2006 04:45:22 GMT\r
637
646
Server: Apache/2.0.54 (Fedora)\r
638
647
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
641
650
Content-Length: 100\r
642
651
Connection: close\r
643
652
\r
644
 
""", """mbp@sourcefrog.net-20050309040815-13242001617e4a06
 
653
""", b"""mbp@sourcefrog.net-20050309040815-13242001617e4a06
645
654
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e762""")
646
655
 
647
656
 
648
 
_single_range_response_truncated = (206, """HTTP/1.1 206 Partial Content\r
 
657
_single_range_response_truncated = (206, b"""HTTP/1.1 206 Partial Content\r
649
658
Date: Tue, 11 Jul 2006 04:45:22 GMT\r
650
659
Server: Apache/2.0.54 (Fedora)\r
651
660
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
656
665
Connection: close\r
657
666
Content-Type: text/plain; charset=UTF-8\r
658
667
\r
659
 
""", """mbp@sourcefrog.net-20050309040815-13242001617e4a06""")
660
 
 
661
 
 
662
 
_invalid_response = (444, """HTTP/1.1 444 Bad Response\r
 
668
""", b"""mbp@sourcefrog.net-20050309040815-13242001617e4a06""")
 
669
 
 
670
 
 
671
_invalid_response = (444, b"""HTTP/1.1 444 Bad Response\r
663
672
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
664
673
Connection: close\r
665
674
Content-Type: text/html; charset=iso-8859-1\r
666
675
\r
667
 
""", """<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
 
676
""", b"""<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
668
677
<html><head>
669
678
<title>404 Not Found</title>
670
679
</head><body>
675
684
""")
676
685
 
677
686
 
678
 
_multipart_no_content_range = (206, """HTTP/1.0 206 Partial Content\r
 
687
_multipart_no_content_range = (206, b"""HTTP/1.0 206 Partial Content\r
679
688
Content-Type: multipart/byteranges; boundary=THIS_SEPARATES\r
680
689
Content-Length: 598\r
681
690
\r
682
691
""",
683
 
"""\r
 
692
                               b"""\r
684
693
--THIS_SEPARATES\r
685
694
Content-Type: text/plain\r
686
695
\r
689
698
""")
690
699
 
691
700
 
692
 
_multipart_no_boundary = (206, """HTTP/1.0 206 Partial Content\r
 
701
_multipart_no_boundary = (206, b"""HTTP/1.0 206 Partial Content\r
693
702
Content-Type: multipart/byteranges; boundary=THIS_SEPARATES\r
694
703
Content-Length: 598\r
695
704
\r
696
705
""",
697
 
"""\r
 
706
                          b"""\r
698
707
--THIS_SEPARATES\r
699
708
Content-Type: text/plain\r
700
709
Content-Range: bytes 0-18/18672\r
709
718
class TestHandleResponse(tests.TestCase):
710
719
 
711
720
    def _build_HTTPMessage(self, raw_headers):
712
 
        status_and_headers = StringIO(raw_headers)
 
721
        status_and_headers = BytesIO(raw_headers)
713
722
        # Get rid of the status line
714
723
        status_and_headers.readline()
715
 
        msg = httplib.HTTPMessage(status_and_headers)
716
 
        return msg
 
724
        msg = parse_headers(status_and_headers)
 
725
        return msg.get
717
726
 
718
727
    def get_response(self, a_response):
719
728
        """Process a supplied response, and return the result."""
720
729
        code, raw_headers, body = a_response
721
 
        msg = self._build_HTTPMessage(raw_headers)
722
 
        return response.handle_response('http://foo', code, msg,
723
 
                                        StringIO(a_response[2]))
 
730
        getheader = self._build_HTTPMessage(raw_headers)
 
731
        return response.handle_response(
 
732
            'http://foo', code, getheader, BytesIO(a_response[2]))
724
733
 
725
734
    def test_full_text(self):
726
735
        out = self.get_response(_full_text_response)
727
 
        # It is a StringIO from the original data
 
736
        # It is a BytesIO from the original data
728
737
        self.assertEqual(_full_text_response[2], out.read())
729
738
 
730
739
    def test_single_range(self):
771
780
    def test_full_text_no_content_type(self):
772
781
        # We should not require Content-Type for a full response
773
782
        code, raw_headers, body = _full_text_response_no_content_type
774
 
        msg = self._build_HTTPMessage(raw_headers)
775
 
        out = response.handle_response('http://foo', code, msg, StringIO(body))
 
783
        getheader = self._build_HTTPMessage(raw_headers)
 
784
        out = response.handle_response(
 
785
            'http://foo', code, getheader, BytesIO(body))
776
786
        self.assertEqual(body, out.read())
777
787
 
778
788
    def test_full_text_no_content_length(self):
779
789
        code, raw_headers, body = _full_text_response_no_content_length
780
 
        msg = self._build_HTTPMessage(raw_headers)
781
 
        out = response.handle_response('http://foo', code, msg, StringIO(body))
 
790
        getheader = self._build_HTTPMessage(raw_headers)
 
791
        out = response.handle_response(
 
792
            'http://foo', code, getheader, BytesIO(body))
782
793
        self.assertEqual(body, out.read())
783
794
 
784
795
    def test_missing_content_range(self):
785
796
        code, raw_headers, body = _single_range_no_content_range
786
 
        msg = self._build_HTTPMessage(raw_headers)
 
797
        getheader = self._build_HTTPMessage(raw_headers)
787
798
        self.assertRaises(errors.InvalidHttpResponse,
788
799
                          response.handle_response,
789
 
                          'http://bogus', code, msg, StringIO(body))
 
800
                          'http://bogus', code, getheader, BytesIO(body))
790
801
 
791
802
    def test_multipart_no_content_range(self):
792
803
        code, raw_headers, body = _multipart_no_content_range
793
 
        msg = self._build_HTTPMessage(raw_headers)
 
804
        getheader = self._build_HTTPMessage(raw_headers)
794
805
        self.assertRaises(errors.InvalidHttpResponse,
795
806
                          response.handle_response,
796
 
                          'http://bogus', code, msg, StringIO(body))
 
807
                          'http://bogus', code, getheader, BytesIO(body))
797
808
 
798
809
    def test_multipart_no_boundary(self):
799
810
        out = self.get_response(_multipart_no_boundary)
811
822
        super(TestRangeFileSizeReadLimited, self).setUp()
812
823
        # create a test datablock larger than _max_read_size.
813
824
        chunk_size = response.RangeFile._max_read_size
814
 
        test_pattern = '0123456789ABCDEF'
815
 
        self.test_data =  test_pattern * (3 * chunk_size / len(test_pattern))
 
825
        test_pattern = b'0123456789ABCDEF'
 
826
        self.test_data = test_pattern * (3 * chunk_size // len(test_pattern))
816
827
        self.test_data_len = len(self.test_data)
817
828
 
818
829
    def test_max_read_size(self):
835
846
        if response_data != self.test_data:
836
847
            message = "Data not equal.  Expected %d bytes, received %d."
837
848
            self.fail(message % (len(response_data), self.test_data_len))
838