63
68
pycurl_present = False
66
class TransportAdapter(tests.TestScenarioApplier):
67
"""Generate the same test for each transport implementation."""
70
transport_scenarios = [
71
('urllib', dict(_transport=_urllib.HttpTransport_urllib,
72
_server=http_server.HttpServer_urllib,
73
_qualified_prefix='http+urllib',)),
76
transport_scenarios.append(
77
('pycurl', dict(_transport=PyCurlTransport,
78
_server=http_server.HttpServer_PyCurl,
79
_qualified_prefix='http+pycurl',)))
80
self.scenarios = transport_scenarios
83
class TransportProtocolAdapter(TransportAdapter):
84
"""Generate the same test for each protocol implementation.
86
In addition to the transport adaptatation that we inherit from.
90
super(TransportProtocolAdapter, self).__init__()
91
protocol_scenarios = [
92
('HTTP/1.0', dict(_protocol_version='HTTP/1.0')),
93
('HTTP/1.1', dict(_protocol_version='HTTP/1.1')),
95
self.scenarios = tests.multiply_scenarios(self.scenarios,
99
class TransportProtocolAuthenticationAdapter(TransportProtocolAdapter):
100
"""Generate the same test for each authentication scheme implementation.
102
In addition to the protocol adaptatation that we inherit from.
106
super(TransportProtocolAuthenticationAdapter, self).__init__()
107
auth_scheme_scenarios = [
108
('basic', dict(_auth_scheme='basic')),
109
('digest', dict(_auth_scheme='digest')),
112
self.scenarios = tests.multiply_scenarios(self.scenarios,
113
auth_scheme_scenarios)
115
71
def load_tests(standard_tests, module, loader):
116
72
"""Multiply tests for http clients and protocol versions."""
117
# one for each transport
118
t_adapter = TransportAdapter()
119
t_classes= (TestHttpTransportRegistration,
73
result = loader.suiteClass()
75
# one for each transport implementation
76
t_tests, remaining_tests = tests.split_suite_by_condition(
77
standard_tests, tests.condition_isinstance((
78
TestHttpTransportRegistration,
120
79
TestHttpTransportUrls,
82
transport_scenarios = [
83
('urllib', dict(_transport=_urllib.HttpTransport_urllib,
84
_server=http_server.HttpServer_urllib,
85
_qualified_prefix='http+urllib',)),
88
transport_scenarios.append(
89
('pycurl', dict(_transport=PyCurlTransport,
90
_server=http_server.HttpServer_PyCurl,
91
_qualified_prefix='http+pycurl',)))
92
tests.multiply_tests(t_tests, transport_scenarios, result)
94
# each implementation tested with each HTTP version
95
tp_tests, remaining_tests = tests.split_suite_by_condition(
96
remaining_tests, tests.condition_isinstance((
97
SmartHTTPTunnellingTest,
98
TestDoCatchRedirections,
100
TestHTTPRedirections,
101
TestHTTPSilentRedirections,
102
TestLimitedRangeRequestServer,
106
TestSpecificRequestHandler,
108
protocol_scenarios = [
109
('HTTP/1.0', dict(_protocol_version='HTTP/1.0')),
110
('HTTP/1.1', dict(_protocol_version='HTTP/1.1')),
112
tp_scenarios = tests.multiply_scenarios(transport_scenarios,
114
tests.multiply_tests(tp_tests, tp_scenarios, result)
116
# proxy auth: each auth scheme on all http versions on all implementations.
117
tppa_tests, remaining_tests = tests.split_suite_by_condition(
118
remaining_tests, tests.condition_isinstance((
121
proxy_auth_scheme_scenarios = [
122
('basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
123
('digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
125
dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
127
tppa_scenarios = tests.multiply_scenarios(tp_scenarios,
128
proxy_auth_scheme_scenarios)
129
tests.multiply_tests(tppa_tests, tppa_scenarios, result)
131
# auth: each auth scheme on all http versions on all implementations.
132
tpa_tests, remaining_tests = tests.split_suite_by_condition(
133
remaining_tests, tests.condition_isinstance((
136
auth_scheme_scenarios = [
137
('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
138
('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
140
dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
142
tpa_scenarios = tests.multiply_scenarios(tp_scenarios,
143
auth_scheme_scenarios)
144
tests.multiply_tests(tpa_tests, tpa_scenarios, result)
146
# activity: on all http[s] versions on all implementations
147
tpact_tests, remaining_tests = tests.split_suite_by_condition(
148
remaining_tests, tests.condition_isinstance((
151
activity_scenarios = [
152
('urllib,http', dict(_activity_server=ActivityHTTPServer,
153
_transport=_urllib.HttpTransport_urllib,)),
155
if tests.HTTPSServerFeature.available():
156
activity_scenarios.append(
157
('urllib,https', dict(_activity_server=ActivityHTTPSServer,
158
_transport=_urllib.HttpTransport_urllib,)),)
160
activity_scenarios.append(
161
('pycurl,http', dict(_activity_server=ActivityHTTPServer,
162
_transport=PyCurlTransport,)),)
163
if tests.HTTPSServerFeature.available():
164
from bzrlib.tests import (
122
is_testing_for_transports = tests.condition_isinstance(t_classes)
124
# multiplied by one for each protocol version
125
tp_adapter = TransportProtocolAdapter()
126
tp_classes= (SmartHTTPTunnellingTest,
127
TestDoCatchRedirections,
129
TestHTTPRedirections,
130
TestHTTPSilentRedirections,
131
TestLimitedRangeRequestServer,
135
TestSpecificRequestHandler,
137
is_also_testing_for_protocols = tests.condition_isinstance(tp_classes)
139
# multiplied by one for each authentication scheme
140
tpa_adapter = TransportProtocolAuthenticationAdapter()
141
tpa_classes = (TestAuth,
143
is_also_testing_for_authentication = tests.condition_isinstance(
146
result = loader.suiteClass()
147
for test_class in tests.iter_suite_tests(standard_tests):
148
# Each test class is either standalone or testing for some combination
149
# of transport, protocol version, authentication scheme. Use the right
150
# adpater (or none) depending on the class.
151
if is_testing_for_transports(test_class):
152
result.addTests(t_adapter.adapt(test_class))
153
elif is_also_testing_for_protocols(test_class):
154
result.addTests(tp_adapter.adapt(test_class))
155
elif is_also_testing_for_authentication(test_class):
156
result.addTests(tpa_adapter.adapt(test_class))
158
result.addTest(test_class)
167
# FIXME: Until we have a better way to handle self-signed
168
# certificates (like allowing them in a test specific
169
# authentication.conf for example), we need some specialized pycurl
170
# transport for tests.
171
class HTTPS_pycurl_transport(PyCurlTransport):
173
def __init__(self, base, _from_transport=None):
174
super(HTTPS_pycurl_transport, self).__init__(
175
base, _from_transport)
176
self.cabundle = str(ssl_certs.build_path('ca.crt'))
178
activity_scenarios.append(
179
('pycurl,https', dict(_activity_server=ActivityHTTPSServer,
180
_transport=HTTPS_pycurl_transport,)),)
182
tpact_scenarios = tests.multiply_scenarios(activity_scenarios,
184
tests.multiply_tests(tpact_tests, tpact_scenarios, result)
186
# No parametrization for the remaining tests
187
result.addTests(remaining_tests)
258
class TestAuthHeader(tests.TestCase):
260
def parse_header(self, header, auth_handler_class=None):
261
if auth_handler_class is None:
262
auth_handler_class = _urllib2_wrappers.AbstractAuthHandler
263
self.auth_handler = auth_handler_class()
264
return self.auth_handler._parse_auth_header(header)
266
def test_empty_header(self):
267
scheme, remainder = self.parse_header('')
268
self.assertEquals('', scheme)
269
self.assertIs(None, remainder)
271
def test_negotiate_header(self):
272
scheme, remainder = self.parse_header('Negotiate')
273
self.assertEquals('negotiate', scheme)
274
self.assertIs(None, remainder)
276
def test_basic_header(self):
277
scheme, remainder = self.parse_header(
278
'Basic realm="Thou should not pass"')
279
self.assertEquals('basic', scheme)
280
self.assertEquals('realm="Thou should not pass"', remainder)
282
def test_basic_extract_realm(self):
283
scheme, remainder = self.parse_header(
284
'Basic realm="Thou should not pass"',
285
_urllib2_wrappers.BasicAuthHandler)
286
match, realm = self.auth_handler.extract_realm(remainder)
287
self.assertTrue(match is not None)
288
self.assertEquals('Thou should not pass', realm)
290
def test_digest_header(self):
291
scheme, remainder = self.parse_header(
292
'Digest realm="Thou should not pass"')
293
self.assertEquals('digest', scheme)
294
self.assertEquals('realm="Thou should not pass"', remainder)
228
297
class TestHTTPServer(tests.TestCase):
229
298
"""Test the HTTP servers implementations."""
1738
1845
# No need to build a valid smart request here, the server will not even
1739
1846
# try to interpret it.
1740
1847
self.assertRaises(errors.SmartProtocolError,
1741
t.send_http_smart_request, 'whatever')
1848
t.get_smart_medium().send_http_smart_request,
1851
class Test_redirected_to(tests.TestCase):
1853
def test_redirected_to_subdir(self):
1854
t = self._transport('http://www.example.com/foo')
1855
r = t._redirected_to('http://www.example.com/foo',
1856
'http://www.example.com/foo/subdir')
1857
self.assertIsInstance(r, type(t))
1858
# Both transports share the some connection
1859
self.assertEquals(t._get_connection(), r._get_connection())
1861
def test_redirected_to_self_with_slash(self):
1862
t = self._transport('http://www.example.com/foo')
1863
r = t._redirected_to('http://www.example.com/foo',
1864
'http://www.example.com/foo/')
1865
self.assertIsInstance(r, type(t))
1866
# Both transports share the some connection (one can argue that we
1867
# should return the exact same transport here, but that seems
1869
self.assertEquals(t._get_connection(), r._get_connection())
1871
def test_redirected_to_host(self):
1872
t = self._transport('http://www.example.com/foo')
1873
r = t._redirected_to('http://www.example.com/foo',
1874
'http://foo.example.com/foo/subdir')
1875
self.assertIsInstance(r, type(t))
1877
def test_redirected_to_same_host_sibling_protocol(self):
1878
t = self._transport('http://www.example.com/foo')
1879
r = t._redirected_to('http://www.example.com/foo',
1880
'https://www.example.com/foo')
1881
self.assertIsInstance(r, type(t))
1883
def test_redirected_to_same_host_different_protocol(self):
1884
t = self._transport('http://www.example.com/foo')
1885
r = t._redirected_to('http://www.example.com/foo',
1886
'ftp://www.example.com/foo')
1887
self.assertNotEquals(type(r), type(t))
1889
def test_redirected_to_different_host_same_user(self):
1890
t = self._transport('http://joe@www.example.com/foo')
1891
r = t._redirected_to('http://www.example.com/foo',
1892
'https://foo.example.com/foo')
1893
self.assertIsInstance(r, type(t))
1894
self.assertEquals(t._user, r._user)
1897
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
1898
"""Request handler for a unique and pre-defined request.
1900
The only thing we care about here is how many bytes travel on the wire. But
1901
since we want to measure it for a real http client, we have to send it
1904
We expect to receive a *single* request nothing more (and we won't even
1905
check what request it is, we just measure the bytes read until an empty
1909
def handle_one_request(self):
1910
tcs = self.server.test_case_server
1911
requestline = self.rfile.readline()
1912
headers = self.MessageClass(self.rfile, 0)
1913
# We just read: the request, the headers, an empty line indicating the
1914
# end of the headers.
1915
bytes_read = len(requestline)
1916
for line in headers.headers:
1917
bytes_read += len(line)
1918
bytes_read += len('\r\n')
1919
if requestline.startswith('POST'):
1920
# The body should be a single line (or we don't know where it ends
1921
# and we don't want to issue a blocking read)
1922
body = self.rfile.readline()
1923
bytes_read += len(body)
1924
tcs.bytes_read = bytes_read
1926
# We set the bytes written *before* issuing the write, the client is
1927
# supposed to consume every produced byte *before* checking that value.
1929
# Doing the oppposite may lead to test failure: we may be interrupted
1930
# after the write but before updating the value. The client can then
1931
# continue and read the value *before* we can update it. And yes,
1932
# this has been observed -- vila 20090129
1933
tcs.bytes_written = len(tcs.canned_response)
1934
self.wfile.write(tcs.canned_response)
1937
class ActivityServerMixin(object):
1939
def __init__(self, protocol_version):
1940
super(ActivityServerMixin, self).__init__(
1941
request_handler=PredefinedRequestHandler,
1942
protocol_version=protocol_version)
1943
# Bytes read and written by the server
1945
self.bytes_written = 0
1946
self.canned_response = None
1949
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
1953
if tests.HTTPSServerFeature.available():
1954
from bzrlib.tests import https_server
1955
class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
1959
class TestActivity(tests.TestCase):
1960
"""Test socket activity reporting.
1962
We use a special purpose server to control the bytes sent and received and
1963
be able to predict the activity on the client socket.
1967
tests.TestCase.setUp(self)
1968
self.server = self._activity_server(self._protocol_version)
1970
self.activities = {}
1971
def report_activity(t, bytes, direction):
1972
count = self.activities.get(direction, 0)
1974
self.activities[direction] = count
1976
# We override at class level because constructors may propagate the
1977
# bound method and render instance overriding ineffective (an
1978
# alternative would be to define a specific ui factory instead...)
1979
self.orig_report_activity = self._transport._report_activity
1980
self._transport._report_activity = report_activity
1983
self._transport._report_activity = self.orig_report_activity
1984
self.server.tearDown()
1985
tests.TestCase.tearDown(self)
1987
def get_transport(self):
1988
return self._transport(self.server.get_url())
1990
def assertActivitiesMatch(self):
1991
self.assertEqual(self.server.bytes_read,
1992
self.activities.get('write', 0), 'written bytes')
1993
self.assertEqual(self.server.bytes_written,
1994
self.activities.get('read', 0), 'read bytes')
1997
self.server.canned_response = '''HTTP/1.1 200 OK\r
1998
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
1999
Server: Apache/2.0.54 (Fedora)\r
2000
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2001
ETag: "56691-23-38e9ae00"\r
2002
Accept-Ranges: bytes\r
2003
Content-Length: 35\r
2005
Content-Type: text/plain; charset=UTF-8\r
2007
Bazaar-NG meta directory, format 1
2009
t = self.get_transport()
2010
self.assertEqual('Bazaar-NG meta directory, format 1\n',
2011
t.get('foo/bar').read())
2012
self.assertActivitiesMatch()
2015
self.server.canned_response = '''HTTP/1.1 200 OK\r
2016
Server: SimpleHTTP/0.6 Python/2.5.2\r
2017
Date: Thu, 29 Jan 2009 20:21:47 GMT\r
2018
Content-type: application/octet-stream\r
2019
Content-Length: 20\r
2020
Last-Modified: Thu, 29 Jan 2009 20:21:47 GMT\r
2023
t = self.get_transport()
2024
self.assertTrue(t.has('foo/bar'))
2025
self.assertActivitiesMatch()
2027
def test_readv(self):
2028
self.server.canned_response = '''HTTP/1.1 206 Partial Content\r
2029
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
2030
Server: Apache/2.0.54 (Fedora)\r
2031
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
2032
ETag: "238a3c-16ec2-805c5540"\r
2033
Accept-Ranges: bytes\r
2034
Content-Length: 1534\r
2036
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
2039
--418470f848b63279b\r
2040
Content-type: text/plain; charset=UTF-8\r
2041
Content-range: bytes 0-254/93890\r
2043
mbp@sourcefrog.net-20050309040815-13242001617e4a06
2044
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e7627
2045
mbp@sourcefrog.net-20050309040957-6cad07f466bb0bb8
2046
mbp@sourcefrog.net-20050309041501-c840e09071de3b67
2047
mbp@sourcefrog.net-20050309044615-c24a3250be83220a
2049
--418470f848b63279b\r
2050
Content-type: text/plain; charset=UTF-8\r
2051
Content-range: bytes 1000-2049/93890\r
2054
mbp@sourcefrog.net-20050311063625-07858525021f270b
2055
mbp@sourcefrog.net-20050311231934-aa3776aff5200bb9
2056
mbp@sourcefrog.net-20050311231953-73aeb3a131c3699a
2057
mbp@sourcefrog.net-20050311232353-f5e33da490872c6a
2058
mbp@sourcefrog.net-20050312071639-0a8f59a34a024ff0
2059
mbp@sourcefrog.net-20050312073432-b2c16a55e0d6e9fb
2060
mbp@sourcefrog.net-20050312073831-a47c3335ece1920f
2061
mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
2062
mbp@sourcefrog.net-20050313052251-2bf004cb96b39933
2063
mbp@sourcefrog.net-20050313052856-3edd84094687cb11
2064
mbp@sourcefrog.net-20050313053233-e30a4f28aef48f9d
2065
mbp@sourcefrog.net-20050313053853-7c64085594ff3072
2066
mbp@sourcefrog.net-20050313054757-a86c3f5871069e22
2067
mbp@sourcefrog.net-20050313061422-418f1f73b94879b9
2068
mbp@sourcefrog.net-20050313120651-497bd231b19df600
2069
mbp@sourcefrog.net-20050314024931-eae0170ef25a5d1a
2070
mbp@sourcefrog.net-20050314025438-d52099f915fe65fc
2071
mbp@sourcefrog.net-20050314025539-637a636692c055cf
2072
mbp@sourcefrog.net-20050314025737-55eb441f430ab4ba
2073
mbp@sourcefrog.net-20050314025901-d74aa93bb7ee8f62
2075
--418470f848b63279b--\r
2077
t = self.get_transport()
2078
# Remember that the request is ignored and that the ranges below
2079
# doesn't have to match the canned response.
2080
l = list(t.readv('/foo/bar', ((0, 255), (1000, 1050))))
2081
self.assertEqual(2, len(l))
2082
self.assertActivitiesMatch()
2084
def test_post(self):
2085
self.server.canned_response = '''HTTP/1.1 200 OK\r
2086
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2087
Server: Apache/2.0.54 (Fedora)\r
2088
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2089
ETag: "56691-23-38e9ae00"\r
2090
Accept-Ranges: bytes\r
2091
Content-Length: 35\r
2093
Content-Type: text/plain; charset=UTF-8\r
2095
lalala whatever as long as itsssss
2097
t = self.get_transport()
2098
# We must send a single line of body bytes, see
2099
# PredefinedRequestHandler.handle_one_request
2100
code, f = t._post('abc def end-of-body\n')
2101
self.assertEqual('lalala whatever as long as itsssss\n', f.read())
2102
self.assertActivitiesMatch()