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 import osutils
29
from bzrlib.tests import (
33
from bzrlib.tests.HttpServer import (
38
from bzrlib.tests.HTTPTestUtil import (
39
BadProtocolRequestHandler,
40
BadStatusRequestHandler,
41
ForbiddenRequestHandler,
42
InvalidStatusRequestHandler,
43
NoRangeRequestHandler,
44
SingleRangeRequestHandler,
45
TestCaseWithWebserver,
48
from bzrlib.transport import (
52
from bzrlib.transport.http import (
56
from bzrlib.transport.http._urllib import HttpTransport_urllib
59
class FakeManager(object):
64
def add_password(self, realm, host, username, password):
65
self.credentials.append([realm, host, username, password])
68
class RecordingServer(object):
69
"""A fake HTTP server.
71
It records the bytes sent to it, and replies with a 200.
74
def __init__(self, expect_body_tail=None):
77
:type expect_body_tail: str
78
:param expect_body_tail: a reply won't be sent until this string is
81
self._expect_body_tail = expect_body_tail
84
self.received_bytes = ''
87
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
88
self._sock.bind(('127.0.0.1', 0))
89
self.host, self.port = self._sock.getsockname()
90
self._ready = threading.Event()
91
self._thread = threading.Thread(target=self._accept_read_and_reply)
92
self._thread.setDaemon(True)
96
def _accept_read_and_reply(self):
99
self._sock.settimeout(5)
101
conn, address = self._sock.accept()
102
# On win32, the accepted connection will be non-blocking to start
103
# with because we're using settimeout.
104
conn.setblocking(True)
105
while not self.received_bytes.endswith(self._expect_body_tail):
106
self.received_bytes += conn.recv(4096)
107
conn.sendall('HTTP/1.1 200 OK\r\n')
108
except socket.timeout:
109
# Make sure the client isn't stuck waiting for us to e.g. accept.
116
# We might have already closed it. We don't care.
122
class TestHttpUrls(TestCase):
124
# FIXME: Some of these tests should be done for both
127
def test_url_parsing(self):
129
url = extract_auth('http://example.com', f)
130
self.assertEquals('http://example.com', url)
131
self.assertEquals(0, len(f.credentials))
132
url = extract_auth('http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
133
self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
134
self.assertEquals(1, len(f.credentials))
135
self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
138
def test_abs_url(self):
139
"""Construction of absolute http URLs"""
140
t = HttpTransport_urllib('http://bazaar-vcs.org/bzr/bzr.dev/')
141
eq = self.assertEqualDiff
143
'http://bazaar-vcs.org/bzr/bzr.dev')
144
eq(t.abspath('foo/bar'),
145
'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
146
eq(t.abspath('.bzr'),
147
'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
148
eq(t.abspath('.bzr/1//2/./3'),
149
'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
151
def test_invalid_http_urls(self):
152
"""Trap invalid construction of urls"""
153
t = HttpTransport_urllib('http://bazaar-vcs.org/bzr/bzr.dev/')
154
self.assertRaises(ValueError,
157
t = HttpTransport_urllib('http://http://bazaar-vcs.org/bzr/bzr.dev/')
158
self.assertRaises(errors.InvalidURL, t.has, 'foo/bar')
160
def test_http_root_urls(self):
161
"""Construction of URLs from server root"""
162
t = HttpTransport_urllib('http://bzr.ozlabs.org/')
163
eq = self.assertEqualDiff
164
eq(t.abspath('.bzr/tree-version'),
165
'http://bzr.ozlabs.org/.bzr/tree-version')
167
def test_http_impl_urls(self):
168
"""There are servers which ask for particular clients to connect"""
169
server = HttpServer_PyCurl()
172
url = server.get_url()
173
self.assertTrue(url.startswith('http+pycurl://'))
178
class TestHttpConnections(object):
179
"""Test the http connections.
181
This MUST be used by daughter classes that also inherit from
182
TestCaseWithWebserver.
184
We can't inherit directly from TestCaseWithWebserver or the
185
test framework will try to create an instance which cannot
186
run, its implementation being incomplete.
190
TestCaseWithWebserver.setUp(self)
191
self.build_tree(['xxx', 'foo/', 'foo/bar'], line_endings='binary',
192
transport=self.get_transport())
194
def test_http_has(self):
195
server = self.get_readonly_server()
196
t = self._transport(server.get_url())
197
self.assertEqual(t.has('foo/bar'), True)
198
self.assertEqual(len(server.logs), 1)
199
self.assertContainsRe(server.logs[0],
200
r'"HEAD /foo/bar HTTP/1.." (200|302) - "-" "bzr/')
202
def test_http_has_not_found(self):
203
server = self.get_readonly_server()
204
t = self._transport(server.get_url())
205
self.assertEqual(t.has('not-found'), False)
206
self.assertContainsRe(server.logs[1],
207
r'"HEAD /not-found HTTP/1.." 404 - "-" "bzr/')
209
def test_http_get(self):
210
server = self.get_readonly_server()
211
t = self._transport(server.get_url())
212
fp = t.get('foo/bar')
213
self.assertEqualDiff(
215
'contents of foo/bar\n')
216
self.assertEqual(len(server.logs), 1)
217
self.assertTrue(server.logs[0].find(
218
'"GET /foo/bar HTTP/1.1" 200 - "-" "bzr/%s'
219
% bzrlib.__version__) > -1)
221
def test_get_smart_medium(self):
222
# For HTTP, get_smart_medium should return the transport object.
223
server = self.get_readonly_server()
224
http_transport = self._transport(server.get_url())
225
medium = http_transport.get_smart_medium()
226
self.assertIs(medium, http_transport)
228
def test_has_on_bogus_host(self):
229
# Get a free address and don't 'accept' on it, so that we
230
# can be sure there is no http handler there, but set a
231
# reasonable timeout to not slow down tests too much.
232
default_timeout = socket.getdefaulttimeout()
234
socket.setdefaulttimeout(2)
236
s.bind(('localhost', 0))
237
t = self._transport('http://%s:%s/' % s.getsockname())
238
self.assertRaises(errors.ConnectionError, t.has, 'foo/bar')
240
socket.setdefaulttimeout(default_timeout)
243
class TestWithTransport_pycurl(object):
244
"""Test case to inherit from if pycurl is present"""
246
def _get_pycurl_maybe(self):
248
from bzrlib.transport.http._pycurl import PyCurlTransport
249
return PyCurlTransport
250
except errors.DependencyNotPresent:
251
raise TestSkipped('pycurl not present')
253
_transport = property(_get_pycurl_maybe)
256
class TestHttpConnections_urllib(TestHttpConnections, TestCaseWithWebserver):
257
"""Test http connections with urllib"""
259
_transport = HttpTransport_urllib
263
class TestHttpConnections_pycurl(TestWithTransport_pycurl,
265
TestCaseWithWebserver):
266
"""Test http connections with pycurl"""
269
class TestHttpTransportRegistration(TestCase):
270
"""Test registrations of various http implementations"""
272
def test_http_registered(self):
273
# urlllib should always be present
274
t = get_transport('http+urllib://bzr.google.com/')
275
self.assertIsInstance(t, Transport)
276
self.assertIsInstance(t, HttpTransport_urllib)
279
class TestOffsets(TestCase):
280
"""Test offsets_to_ranges method"""
282
def test_offsets_to_ranges_simple(self):
283
to_range = HttpTransportBase.offsets_to_ranges
284
ranges = to_range([(10, 1)])
285
self.assertEqual([[10, 10]], ranges)
287
ranges = to_range([(0, 1), (1, 1)])
288
self.assertEqual([[0, 1]], ranges)
290
ranges = to_range([(1, 1), (0, 1)])
291
self.assertEqual([[0, 1]], ranges)
293
def test_offset_to_ranges_overlapped(self):
294
to_range = HttpTransportBase.offsets_to_ranges
296
ranges = to_range([(10, 1), (20, 2), (22, 5)])
297
self.assertEqual([[10, 10], [20, 26]], ranges)
299
ranges = to_range([(10, 1), (11, 2), (22, 5)])
300
self.assertEqual([[10, 12], [22, 26]], ranges)
303
class TestPost(TestCase):
305
def _test_post_body_is_received(self, scheme):
306
server = RecordingServer(expect_body_tail='end-of-body')
308
self.addCleanup(server.tearDown)
309
url = '%s://%s:%s/' % (scheme, server.host, server.port)
311
http_transport = get_transport(url)
312
except errors.UnsupportedProtocol:
313
raise TestSkipped('%s not available' % scheme)
314
code, response = http_transport._post('abc def end-of-body')
316
server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
317
self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
318
# The transport should not be assuming that the server can accept
319
# chunked encoding the first time it connects, because HTTP/1.1, so we
320
# check for the literal string.
322
server.received_bytes.endswith('\r\n\r\nabc def end-of-body'))
324
def test_post_body_is_received_urllib(self):
325
self._test_post_body_is_received('http+urllib')
327
def test_post_body_is_received_pycurl(self):
328
self._test_post_body_is_received('http+pycurl')
331
class TestRangeHeader(TestCase):
332
"""Test range_header method"""
334
def check_header(self, value, ranges=[], tail=0):
335
range_header = HttpTransportBase.range_header
336
self.assertEqual(value, range_header(ranges, tail))
338
def test_range_header_single(self):
339
self.check_header('0-9', ranges=[[0,9]])
340
self.check_header('100-109', ranges=[[100,109]])
342
def test_range_header_tail(self):
343
self.check_header('-10', tail=10)
344
self.check_header('-50', tail=50)
346
def test_range_header_multi(self):
347
self.check_header('0-9,100-200,300-5000',
348
ranges=[(0,9), (100, 200), (300,5000)])
350
def test_range_header_mixed(self):
351
self.check_header('0-9,300-5000,-50',
352
ranges=[(0,9), (300,5000)],
356
class TestWallServer(object):
357
"""Tests exceptions during the connection phase"""
359
def create_transport_readonly_server(self):
360
return HttpServer(WallRequestHandler)
362
def test_http_has(self):
363
server = self.get_readonly_server()
364
t = self._transport(server.get_url())
365
# Unfortunately httplib (see HTTPResponse._read_status
366
# for details) make no distinction between a closed
367
# socket and badly formatted status line, so we can't
368
# just test for ConnectionError, we have to test
369
# InvalidHttpResponse too.
370
self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
373
def test_http_get(self):
374
server = self.get_readonly_server()
375
t = self._transport(server.get_url())
376
self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
380
class TestWallServer_urllib(TestWallServer, TestCaseWithWebserver):
381
"""Tests "wall" server for urllib implementation"""
383
_transport = HttpTransport_urllib
386
class TestWallServer_pycurl(TestWithTransport_pycurl,
388
TestCaseWithWebserver):
389
"""Tests "wall" server for pycurl implementation"""
392
class TestBadStatusServer(object):
393
"""Tests bad status from server."""
395
def create_transport_readonly_server(self):
396
return HttpServer(BadStatusRequestHandler)
398
def test_http_has(self):
399
server = self.get_readonly_server()
400
t = self._transport(server.get_url())
401
self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
403
def test_http_get(self):
404
server = self.get_readonly_server()
405
t = self._transport(server.get_url())
406
self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
409
class TestBadStatusServer_urllib(TestBadStatusServer, TestCaseWithWebserver):
410
"""Tests bad status server for urllib implementation"""
412
_transport = HttpTransport_urllib
415
class TestBadStatusServer_pycurl(TestWithTransport_pycurl,
417
TestCaseWithWebserver):
418
"""Tests bad status server for pycurl implementation"""
421
class TestInvalidStatusServer(TestBadStatusServer):
422
"""Tests invalid status from server.
424
Both implementations raises the same error as for a bad status.
427
def create_transport_readonly_server(self):
428
return HttpServer(InvalidStatusRequestHandler)
431
class TestInvalidStatusServer_urllib(TestInvalidStatusServer,
432
TestCaseWithWebserver):
433
"""Tests invalid status server for urllib implementation"""
435
_transport = HttpTransport_urllib
438
class TestInvalidStatusServer_pycurl(TestWithTransport_pycurl,
439
TestInvalidStatusServer,
440
TestCaseWithWebserver):
441
"""Tests invalid status server for pycurl implementation"""
444
class TestBadProtocolServer(object):
445
"""Tests bad protocol from server."""
447
def create_transport_readonly_server(self):
448
return HttpServer(BadProtocolRequestHandler)
450
def test_http_has(self):
451
server = self.get_readonly_server()
452
t = self._transport(server.get_url())
453
self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
455
def test_http_get(self):
456
server = self.get_readonly_server()
457
t = self._transport(server.get_url())
458
self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
461
class TestBadProtocolServer_urllib(TestBadProtocolServer,
462
TestCaseWithWebserver):
463
"""Tests bad protocol server for urllib implementation"""
465
_transport = HttpTransport_urllib
467
# curl don't check the protocol version
468
#class TestBadProtocolServer_pycurl(TestWithTransport_pycurl,
469
# TestBadProtocolServer,
470
# TestCaseWithWebserver):
471
# """Tests bad protocol server for pycurl implementation"""
474
class TestForbiddenServer(object):
475
"""Tests forbidden server"""
477
def create_transport_readonly_server(self):
478
return HttpServer(ForbiddenRequestHandler)
480
def test_http_has(self):
481
server = self.get_readonly_server()
482
t = self._transport(server.get_url())
483
self.assertRaises(errors.TransportError, t.has, 'foo/bar')
485
def test_http_get(self):
486
server = self.get_readonly_server()
487
t = self._transport(server.get_url())
488
self.assertRaises(errors.TransportError, t.get, 'foo/bar')
491
class TestForbiddenServer_urllib(TestForbiddenServer, TestCaseWithWebserver):
492
"""Tests forbidden server for urllib implementation"""
494
_transport = HttpTransport_urllib
497
class TestForbiddenServer_pycurl(TestWithTransport_pycurl,
499
TestCaseWithWebserver):
500
"""Tests forbidden server for pycurl implementation"""
503
class TestRecordingServer(TestCase):
505
def test_create(self):
506
server = RecordingServer(expect_body_tail=None)
507
self.assertEqual('', server.received_bytes)
508
self.assertEqual(None, server.host)
509
self.assertEqual(None, server.port)
511
def test_setUp_and_tearDown(self):
512
server = RecordingServer(expect_body_tail=None)
515
self.assertNotEqual(None, server.host)
516
self.assertNotEqual(None, server.port)
519
self.assertEqual(None, server.host)
520
self.assertEqual(None, server.port)
522
def test_send_receive_bytes(self):
523
server = RecordingServer(expect_body_tail='c')
525
self.addCleanup(server.tearDown)
526
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
527
sock.connect((server.host, server.port))
529
self.assertEqual('HTTP/1.1 200 OK\r\n',
530
osutils.recv_all(sock, 4096))
531
self.assertEqual('abc', server.received_bytes)
534
class TestRangeRequestServer(object):
535
"""Test the http connections.
537
This MUST be used by daughter classes that also inherit from
538
TestCaseWithWebserver.
540
We can't inherit directly from TestCaseWithWebserver or the
541
test framework will try to create an instance which cannot
542
run, its implementation being incomplete.
546
TestCaseWithWebserver.setUp(self)
547
self.build_tree_contents([('a', '0123456789')],)
549
"""Tests readv requests against server"""
551
def test_readv(self):
552
server = self.get_readonly_server()
553
t = self._transport(server.get_url())
554
l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
555
self.assertEqual(l[0], (0, '0'))
556
self.assertEqual(l[1], (1, '1'))
557
self.assertEqual(l[2], (3, '34'))
558
self.assertEqual(l[3], (9, '9'))
560
def test_readv_out_of_order(self):
561
server = self.get_readonly_server()
562
t = self._transport(server.get_url())
563
l = list(t.readv('a', ((1, 1), (9, 1), (0, 1), (3, 2))))
564
self.assertEqual(l[0], (1, '1'))
565
self.assertEqual(l[1], (9, '9'))
566
self.assertEqual(l[2], (0, '0'))
567
self.assertEqual(l[3], (3, '34'))
569
def test_readv_short_read(self):
570
server = self.get_readonly_server()
571
t = self._transport(server.get_url())
573
# This is intentionally reading off the end of the file
574
# since we are sure that it cannot get there
575
self.assertListRaises((errors.ShortReadvError, AssertionError),
576
t.readv, 'a', [(1,1), (8,10)])
578
# This is trying to seek past the end of the file, it should
579
# also raise a special error
580
self.assertListRaises(errors.ShortReadvError,
581
t.readv, 'a', [(12,2)])
584
class TestSingleRangeRequestServer(TestRangeRequestServer):
585
"""Test readv against a server which accept only single range requests"""
587
def create_transport_readonly_server(self):
588
return HttpServer(SingleRangeRequestHandler)
591
class TestSingleRangeRequestServer_urllib(TestSingleRangeRequestServer,
592
TestCaseWithWebserver):
593
"""Tests single range requests accepting server for urllib implementation"""
595
_transport = HttpTransport_urllib
598
class TestSingleRangeRequestServer_pycurl(TestWithTransport_pycurl,
599
TestSingleRangeRequestServer,
600
TestCaseWithWebserver):
601
"""Tests single range requests accepting server for pycurl implementation"""
604
class TestNoRangeRequestServer(TestRangeRequestServer):
605
"""Test readv against a server which do not accept range requests"""
607
def create_transport_readonly_server(self):
608
return HttpServer(NoRangeRequestHandler)
611
class TestNoRangeRequestServer_urllib(TestNoRangeRequestServer,
612
TestCaseWithWebserver):
613
"""Tests range requests refusing server for urllib implementation"""
615
_transport = HttpTransport_urllib
618
class TestNoRangeRequestServer_pycurl(TestWithTransport_pycurl,
619
TestNoRangeRequestServer,
620
TestCaseWithWebserver):
621
"""Tests range requests refusing server for pycurl implementation"""