1
# Copyright (C) 2005, 2006 Canonical Ltd
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.
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.
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
17
# FIXME: This test should be repeated for each available http client
18
# implementation; at the moment we have urllib and pycurl.
20
# TODO: Should be renamed to bzrlib.transport.http.tests?
27
from bzrlib import errors
28
from bzrlib.tests import (
32
from bzrlib.tests.HttpServer import (
37
from bzrlib.tests.HTTPTestUtil import (
38
BadProtocolRequestHandler,
39
BadStatusRequestHandler,
40
ForbiddenRequestHandler,
41
InvalidStatusRequestHandler,
42
NoRangeRequestHandler,
43
SingleRangeRequestHandler,
44
TestCaseWithWebserver,
47
from bzrlib.transport import (
51
from bzrlib.transport.http import (
55
from bzrlib.transport.http._urllib import HttpTransport_urllib
58
class FakeManager(object):
63
def add_password(self, realm, host, username, password):
64
self.credentials.append([realm, host, username, password])
67
class RecordingServer(object):
68
"""A fake HTTP server.
70
It records the bytes sent to it, and replies with a 200.
73
def __init__(self, expect_body_tail=None):
76
:type expect_body_tail: str
77
:param expect_body_tail: a reply won't be sent until this string is
80
self._expect_body_tail = expect_body_tail
83
self.received_bytes = ''
86
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
87
self._sock.bind(('127.0.0.1', 0))
88
self.host, self.port = self._sock.getsockname()
89
self._ready = threading.Event()
90
self._thread = threading.Thread(target=self._accept_read_and_reply)
91
self._thread.setDaemon(True)
95
def _accept_read_and_reply(self):
98
self._sock.settimeout(5)
100
conn, address = self._sock.accept()
101
# On win32, the accepted connection will be non-blocking to start
102
# with because we're using settimeout.
103
conn.setblocking(True)
104
while not self.received_bytes.endswith(self._expect_body_tail):
105
self.received_bytes += conn.recv(4096)
106
conn.sendall('HTTP/1.1 200 OK\r\n')
107
except socket.timeout:
108
# Make sure the client isn't stuck waiting for us to e.g. accept.
115
# We might have already closed it. We don't care.
121
class TestHttpUrls(TestCase):
123
def test_url_parsing(self):
125
url = extract_auth('http://example.com', f)
126
self.assertEquals('http://example.com', url)
127
self.assertEquals(0, len(f.credentials))
128
url = extract_auth('http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
129
self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
130
self.assertEquals(1, len(f.credentials))
131
self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
134
def test_abs_url(self):
135
"""Construction of absolute http URLs"""
136
t = HttpTransport_urllib('http://bazaar-vcs.org/bzr/bzr.dev/')
137
eq = self.assertEqualDiff
139
'http://bazaar-vcs.org/bzr/bzr.dev')
140
eq(t.abspath('foo/bar'),
141
'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
142
eq(t.abspath('.bzr'),
143
'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
144
eq(t.abspath('.bzr/1//2/./3'),
145
'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
147
def test_invalid_http_urls(self):
148
"""Trap invalid construction of urls"""
149
t = HttpTransport_urllib('http://bazaar-vcs.org/bzr/bzr.dev/')
150
self.assertRaises(ValueError,
154
def test_http_root_urls(self):
155
"""Construction of URLs from server root"""
156
t = HttpTransport_urllib('http://bzr.ozlabs.org/')
157
eq = self.assertEqualDiff
158
eq(t.abspath('.bzr/tree-version'),
159
'http://bzr.ozlabs.org/.bzr/tree-version')
161
def test_http_impl_urls(self):
162
"""There are servers which ask for particular clients to connect"""
163
server = HttpServer_PyCurl()
166
url = server.get_url()
167
self.assertTrue(url.startswith('http+pycurl://'))
172
class TestHttpConnections(object):
173
"""Test the http connections.
175
This MUST be used by daughter classes that also inherit from
176
TestCaseWithWebserver.
178
We can't inherit directly from TestCaseWithWebserver or the
179
test framework will try to create an instance which cannot
180
run, its implementation being incomplete.
184
TestCaseWithWebserver.setUp(self)
185
self.build_tree(['xxx', 'foo/', 'foo/bar'], line_endings='binary',
186
transport=self.get_transport())
188
def test_http_has(self):
189
server = self.get_readonly_server()
190
t = self._transport(server.get_url())
191
self.assertEqual(t.has('foo/bar'), True)
192
self.assertEqual(len(server.logs), 1)
193
self.assertContainsRe(server.logs[0],
194
r'"HEAD /foo/bar HTTP/1.." (200|302) - "-" "bzr/')
196
def test_http_has_not_found(self):
197
server = self.get_readonly_server()
198
t = self._transport(server.get_url())
199
self.assertEqual(t.has('not-found'), False)
200
self.assertContainsRe(server.logs[1],
201
r'"HEAD /not-found HTTP/1.." 404 - "-" "bzr/')
203
def test_http_get(self):
204
server = self.get_readonly_server()
205
t = self._transport(server.get_url())
206
fp = t.get('foo/bar')
207
self.assertEqualDiff(
209
'contents of foo/bar\n')
210
self.assertEqual(len(server.logs), 1)
211
self.assertTrue(server.logs[0].find(
212
'"GET /foo/bar HTTP/1.1" 200 - "-" "bzr/%s'
213
% bzrlib.__version__) > -1)
215
def test_get_smart_medium(self):
216
# For HTTP, get_smart_medium should return the transport object.
217
server = self.get_readonly_server()
218
http_transport = self._transport(server.get_url())
219
medium = http_transport.get_smart_medium()
220
self.assertIs(medium, http_transport)
222
def test_has_on_bogus_host(self):
223
# Get a free address and don't 'accept' on it, so that we
224
# can be sure there is no http handler there, but set a
225
# reasonable timeout to not slow down tests too much.
226
default_timeout = socket.getdefaulttimeout()
228
socket.setdefaulttimeout(2)
230
s.bind(('localhost', 0))
231
t = self._transport('http://%s:%s/' % s.getsockname())
232
self.assertRaises(errors.ConnectionError, t.has, 'foo/bar')
234
socket.setdefaulttimeout(default_timeout)
237
class TestWithTransport_pycurl(object):
238
"""Test case to inherit from if pycurl is present"""
239
def _get_pycurl_maybe(self):
241
from bzrlib.transport.http._pycurl import PyCurlTransport
242
return PyCurlTransport
243
except errors.DependencyNotPresent:
244
raise TestSkipped('pycurl not present')
246
_transport = property(_get_pycurl_maybe)
249
class TestHttpConnections_urllib(TestHttpConnections, TestCaseWithWebserver):
250
"""Test http connections with urllib"""
252
_transport = HttpTransport_urllib
256
class TestHttpConnections_pycurl(TestWithTransport_pycurl,
258
TestCaseWithWebserver):
259
"""Test http connections with pycurl"""
262
class TestHttpTransportRegistration(TestCase):
263
"""Test registrations of various http implementations"""
265
def test_http_registered(self):
266
# urlllib should always be present
267
t = get_transport('http+urllib://bzr.google.com/')
268
self.assertIsInstance(t, Transport)
269
self.assertIsInstance(t, HttpTransport_urllib)
272
class TestOffsets(TestCase):
273
"""Test offsets_to_ranges method"""
275
def test_offsets_to_ranges_simple(self):
276
to_range = HttpTransportBase.offsets_to_ranges
277
ranges = to_range([(10, 1)])
278
self.assertEqual([[10, 10]], ranges)
280
ranges = to_range([(0, 1), (1, 1)])
281
self.assertEqual([[0, 1]], ranges)
283
ranges = to_range([(1, 1), (0, 1)])
284
self.assertEqual([[0, 1]], ranges)
286
def test_offset_to_ranges_overlapped(self):
287
to_range = HttpTransportBase.offsets_to_ranges
289
ranges = to_range([(10, 1), (20, 2), (22, 5)])
290
self.assertEqual([[10, 10], [20, 26]], ranges)
292
ranges = to_range([(10, 1), (11, 2), (22, 5)])
293
self.assertEqual([[10, 12], [22, 26]], ranges)
296
class TestPost(TestCase):
298
def _test_post_body_is_received(self, scheme):
299
server = RecordingServer(expect_body_tail='end-of-body')
301
self.addCleanup(server.tearDown)
302
url = '%s://%s:%s/' % (scheme, server.host, server.port)
304
http_transport = get_transport(url)
305
except errors.UnsupportedProtocol:
306
raise TestSkipped('%s not available' % scheme)
307
code, response = http_transport._post('abc def end-of-body')
309
server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
310
self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
311
# The transport should not be assuming that the server can accept
312
# chunked encoding the first time it connects, because HTTP/1.1, so we
313
# check for the literal string.
315
server.received_bytes.endswith('\r\n\r\nabc def end-of-body'))
317
def test_post_body_is_received_urllib(self):
318
self._test_post_body_is_received('http+urllib')
320
def test_post_body_is_received_pycurl(self):
321
self._test_post_body_is_received('http+pycurl')
324
class TestRangeHeader(TestCase):
325
"""Test range_header method"""
327
def check_header(self, value, ranges=[], tail=0):
328
range_header = HttpTransportBase.range_header
329
self.assertEqual(value, range_header(ranges, tail))
331
def test_range_header_single(self):
332
self.check_header('0-9', ranges=[[0,9]])
333
self.check_header('100-109', ranges=[[100,109]])
335
def test_range_header_tail(self):
336
self.check_header('-10', tail=10)
337
self.check_header('-50', tail=50)
339
def test_range_header_multi(self):
340
self.check_header('0-9,100-200,300-5000',
341
ranges=[(0,9), (100, 200), (300,5000)])
343
def test_range_header_mixed(self):
344
self.check_header('0-9,300-5000,-50',
345
ranges=[(0,9), (300,5000)],
349
class TestWallServer(object):
350
"""Tests exceptions during the connection phase"""
352
def create_transport_readonly_server(self):
353
return HttpServer(WallRequestHandler)
355
def test_http_has(self):
356
server = self.get_readonly_server()
357
t = self._transport(server.get_url())
358
self.assertRaises(errors.ConnectionError, t.has, 'foo/bar')
360
def test_http_get(self):
361
server = self.get_readonly_server()
362
t = self._transport(server.get_url())
363
self.assertRaises(errors.ConnectionError, t.get, 'foo/bar')
366
class TestWallServer_urllib(TestWallServer, TestCaseWithWebserver):
367
"""Tests "wall" server for urllib implementation"""
369
_transport = HttpTransport_urllib
372
class TestWallServer_pycurl(TestWithTransport_pycurl,
374
TestCaseWithWebserver):
375
"""Tests "wall" server for pycurl implementation"""
378
class TestBadStatusServer(object):
379
"""Tests bad status from server."""
381
def create_transport_readonly_server(self):
382
return HttpServer(BadStatusRequestHandler)
384
def test_http_has(self):
385
server = self.get_readonly_server()
386
t = self._transport(server.get_url())
387
self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
389
def test_http_get(self):
390
server = self.get_readonly_server()
391
t = self._transport(server.get_url())
392
self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
395
class TestBadStatusServer_urllib(TestBadStatusServer, TestCaseWithWebserver):
396
"""Tests bad status server for urllib implementation"""
398
_transport = HttpTransport_urllib
401
class TestBadStatusServer_pycurl(TestWithTransport_pycurl,
403
TestCaseWithWebserver):
404
"""Tests bad status server for pycurl implementation"""
407
class TestInvalidStatusServer(TestBadStatusServer):
408
"""Tests invalid status from server.
410
Both implementations raises the same error as for a bad status.
413
def create_transport_readonly_server(self):
414
return HttpServer(InvalidStatusRequestHandler)
417
class TestInvalidStatusServer_urllib(TestInvalidStatusServer,
418
TestCaseWithWebserver):
419
"""Tests invalid status server for urllib implementation"""
421
_transport = HttpTransport_urllib
424
class TestInvalidStatusServer_pycurl(TestWithTransport_pycurl,
425
TestInvalidStatusServer,
426
TestCaseWithWebserver):
427
"""Tests invalid status server for pycurl implementation"""
430
class TestBadProtocolServer(object):
431
"""Tests bad protocol from server."""
433
def create_transport_readonly_server(self):
434
return HttpServer(BadProtocolRequestHandler)
436
def test_http_has(self):
437
server = self.get_readonly_server()
438
t = self._transport(server.get_url())
439
self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
441
def test_http_get(self):
442
server = self.get_readonly_server()
443
t = self._transport(server.get_url())
444
self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
447
class TestBadProtocolServer_urllib(TestBadProtocolServer,
448
TestCaseWithWebserver):
449
"""Tests bad protocol server for urllib implementation"""
451
_transport = HttpTransport_urllib
453
# curl don't check the protocol version
454
#class TestBadProtocolServer_pycurl(TestWithTransport_pycurl,
455
# TestBadProtocolServer,
456
# TestCaseWithWebserver):
457
# """Tests bad protocol server for pycurl implementation"""
460
class TestForbiddenServer(object):
461
"""Tests forbidden server"""
463
def create_transport_readonly_server(self):
464
return HttpServer(ForbiddenRequestHandler)
466
def test_http_has(self):
467
server = self.get_readonly_server()
468
t = self._transport(server.get_url())
469
self.assertRaises(errors.TransportError, t.has, 'foo/bar')
471
def test_http_get(self):
472
server = self.get_readonly_server()
473
t = self._transport(server.get_url())
474
self.assertRaises(errors.TransportError, t.get, 'foo/bar')
477
class TestForbiddenServer_urllib(TestForbiddenServer, TestCaseWithWebserver):
478
"""Tests forbidden server for urllib implementation"""
480
_transport = HttpTransport_urllib
483
class TestForbiddenServer_pycurl(TestWithTransport_pycurl,
485
TestCaseWithWebserver):
486
"""Tests forbidden server for pycurl implementation"""
489
class TestRecordingServer(TestCase):
491
def test_create(self):
492
server = RecordingServer(expect_body_tail=None)
493
self.assertEqual('', server.received_bytes)
494
self.assertEqual(None, server.host)
495
self.assertEqual(None, server.port)
497
def test_setUp_and_tearDown(self):
498
server = RecordingServer(expect_body_tail=None)
501
self.assertNotEqual(None, server.host)
502
self.assertNotEqual(None, server.port)
505
self.assertEqual(None, server.host)
506
self.assertEqual(None, server.port)
508
def test_send_receive_bytes(self):
509
server = RecordingServer(expect_body_tail='c')
511
self.addCleanup(server.tearDown)
512
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
513
sock.connect((server.host, server.port))
515
self.assertEqual('HTTP/1.1 200 OK\r\n',
516
sock.recv(4096, socket.MSG_WAITALL))
517
self.assertEqual('abc', server.received_bytes)
520
class TestRangeRequestServer(object):
521
"""Test the http connections.
523
This MUST be used by daughter classes that also inherit from
524
TestCaseWithWebserver.
526
We can't inherit directly from TestCaseWithWebserver or the
527
test framework will try to create an instance which cannot
528
run, its implementation being incomplete.
532
TestCaseWithWebserver.setUp(self)
533
self.build_tree_contents([('a', '0123456789')],)
535
"""Tests readv requests against server"""
537
def test_readv(self):
538
server = self.get_readonly_server()
539
t = self._transport(server.get_url())
540
l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
541
self.assertEqual(l[0], (0, '0'))
542
self.assertEqual(l[1], (1, '1'))
543
self.assertEqual(l[2], (3, '34'))
544
self.assertEqual(l[3], (9, '9'))
546
def test_readv_out_of_order(self):
547
server = self.get_readonly_server()
548
t = self._transport(server.get_url())
549
l = list(t.readv('a', ((1, 1), (9, 1), (0, 1), (3, 2))))
550
self.assertEqual(l[0], (1, '1'))
551
self.assertEqual(l[1], (9, '9'))
552
self.assertEqual(l[2], (0, '0'))
553
self.assertEqual(l[3], (3, '34'))
555
def test_readv_short_read(self):
556
server = self.get_readonly_server()
557
t = self._transport(server.get_url())
559
# This is intentionally reading off the end of the file
560
# since we are sure that it cannot get there
561
self.assertListRaises((errors.ShortReadvError, AssertionError),
562
t.readv, 'a', [(1,1), (8,10)])
564
# This is trying to seek past the end of the file, it should
565
# also raise a special error
566
self.assertListRaises(errors.ShortReadvError,
567
t.readv, 'a', [(12,2)])
570
class TestSingleRangeRequestServer(TestRangeRequestServer):
571
"""Test readv against a server which accept only single range requests"""
573
def create_transport_readonly_server(self):
574
return HttpServer(SingleRangeRequestHandler)
577
class TestSingleRangeRequestServer_urllib(TestSingleRangeRequestServer,
578
TestCaseWithWebserver):
579
"""Tests single range requests accepting server for urllib implementation"""
581
_transport = HttpTransport_urllib
584
class TestSingleRangeRequestServer_pycurl(TestWithTransport_pycurl,
585
TestSingleRangeRequestServer,
586
TestCaseWithWebserver):
587
"""Tests single range requests accepting server for pycurl implementation"""
590
class TestNoRangeRequestServer(TestRangeRequestServer):
591
"""Test readv against a server which do not accept range requests"""
593
def create_transport_readonly_server(self):
594
return HttpServer(NoRangeRequestHandler)
597
class TestNoRangeRequestServer_urllib(TestNoRangeRequestServer,
598
TestCaseWithWebserver):
599
"""Tests range requests refusing server for urllib implementation"""
601
_transport = HttpTransport_urllib
604
class TestNoRangeRequestServer_pycurl(TestWithTransport_pycurl,
605
TestNoRangeRequestServer,
606
TestCaseWithWebserver):
607
"""Tests range requests refusing server for pycurl implementation"""