68
65
pycurl_present = False
68
class TransportAdapter(tests.TestScenarioApplier):
69
"""Generate the same test for each transport implementation."""
72
transport_scenarios = [
73
('urllib', dict(_transport=_urllib.HttpTransport_urllib,
74
_server=http_server.HttpServer_urllib,
75
_qualified_prefix='http+urllib',)),
78
transport_scenarios.append(
79
('pycurl', dict(_transport=PyCurlTransport,
80
_server=http_server.HttpServer_PyCurl,
81
_qualified_prefix='http+pycurl',)))
82
self.scenarios = transport_scenarios
85
class TransportProtocolAdapter(TransportAdapter):
86
"""Generate the same test for each protocol implementation.
88
In addition to the transport adaptatation that we inherit from.
92
super(TransportProtocolAdapter, self).__init__()
93
protocol_scenarios = [
94
('HTTP/1.0', dict(_protocol_version='HTTP/1.0')),
95
('HTTP/1.1', dict(_protocol_version='HTTP/1.1')),
97
self.scenarios = tests.multiply_scenarios(self.scenarios,
101
class TransportProtocolAuthenticationAdapter(TransportProtocolAdapter):
102
"""Generate the same test for each authentication scheme implementation.
104
In addition to the protocol adaptatation that we inherit from.
108
super(TransportProtocolAuthenticationAdapter, self).__init__()
109
auth_scheme_scenarios = [
110
('basic', dict(_auth_scheme='basic')),
111
('digest', dict(_auth_scheme='digest')),
114
self.scenarios = tests.multiply_scenarios(self.scenarios,
115
auth_scheme_scenarios)
71
117
def load_tests(standard_tests, module, loader):
72
118
"""Multiply tests for http clients and protocol versions."""
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,
119
# one for each transport
120
t_adapter = TransportAdapter()
121
t_classes= (TestHttpTransportRegistration,
79
122
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 (
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)
124
is_testing_for_transports = tests.condition_isinstance(t_classes)
126
# multiplied by one for each protocol version
127
tp_adapter = TransportProtocolAdapter()
128
tp_classes= (SmartHTTPTunnellingTest,
129
TestDoCatchRedirections,
131
TestHTTPRedirections,
132
TestHTTPSilentRedirections,
133
TestLimitedRangeRequestServer,
137
TestSpecificRequestHandler,
139
is_also_testing_for_protocols = tests.condition_isinstance(tp_classes)
141
# multiplied by one for each authentication scheme
142
tpa_adapter = TransportProtocolAuthenticationAdapter()
143
tpa_classes = (TestAuth,
145
is_also_testing_for_authentication = tests.condition_isinstance(
148
result = loader.suiteClass()
149
for test_class in tests.iter_suite_tests(standard_tests):
150
# Each test class is either standalone or testing for some combination
151
# of transport, protocol version, authentication scheme. Use the right
152
# adpater (or none) depending on the class.
153
if is_testing_for_transports(test_class):
154
result.addTests(t_adapter.adapt(test_class))
155
elif is_also_testing_for_protocols(test_class):
156
result.addTests(tp_adapter.adapt(test_class))
157
elif is_also_testing_for_authentication(test_class):
158
result.addTests(tpa_adapter.adapt(test_class))
160
result.addTest(test_class)
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)
297
230
class TestHTTPServer(tests.TestCase):
298
231
"""Test the HTTP servers implementations."""
1842
1747
# No need to build a valid smart request here, the server will not even
1843
1748
# try to interpret it.
1844
1749
self.assertRaises(errors.SmartProtocolError,
1845
t.get_smart_medium().send_http_smart_request,
1848
class Test_redirected_to(tests.TestCase):
1850
def test_redirected_to_subdir(self):
1851
t = self._transport('http://www.example.com/foo')
1852
r = t._redirected_to('http://www.example.com/foo',
1853
'http://www.example.com/foo/subdir')
1854
self.assertIsInstance(r, type(t))
1855
# Both transports share the some connection
1856
self.assertEquals(t._get_connection(), r._get_connection())
1858
def test_redirected_to_self_with_slash(self):
1859
t = self._transport('http://www.example.com/foo')
1860
r = t._redirected_to('http://www.example.com/foo',
1861
'http://www.example.com/foo/')
1862
self.assertIsInstance(r, type(t))
1863
# Both transports share the some connection (one can argue that we
1864
# should return the exact same transport here, but that seems
1866
self.assertEquals(t._get_connection(), r._get_connection())
1868
def test_redirected_to_host(self):
1869
t = self._transport('http://www.example.com/foo')
1870
r = t._redirected_to('http://www.example.com/foo',
1871
'http://foo.example.com/foo/subdir')
1872
self.assertIsInstance(r, type(t))
1874
def test_redirected_to_same_host_sibling_protocol(self):
1875
t = self._transport('http://www.example.com/foo')
1876
r = t._redirected_to('http://www.example.com/foo',
1877
'https://www.example.com/foo')
1878
self.assertIsInstance(r, type(t))
1880
def test_redirected_to_same_host_different_protocol(self):
1881
t = self._transport('http://www.example.com/foo')
1882
r = t._redirected_to('http://www.example.com/foo',
1883
'ftp://www.example.com/foo')
1884
self.assertNotEquals(type(r), type(t))
1886
def test_redirected_to_different_host_same_user(self):
1887
t = self._transport('http://joe@www.example.com/foo')
1888
r = t._redirected_to('http://www.example.com/foo',
1889
'https://foo.example.com/foo')
1890
self.assertIsInstance(r, type(t))
1891
self.assertEquals(t._user, r._user)
1894
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
1895
"""Request handler for a unique and pre-defined request.
1897
The only thing we care about here is how many bytes travel on the wire. But
1898
since we want to measure it for a real http client, we have to send it
1901
We expect to receive a *single* request nothing more (and we won't even
1902
check what request it is, we just measure the bytes read until an empty
1906
def handle_one_request(self):
1907
tcs = self.server.test_case_server
1908
requestline = self.rfile.readline()
1909
headers = self.MessageClass(self.rfile, 0)
1910
# We just read: the request, the headers, an empty line indicating the
1911
# end of the headers.
1912
bytes_read = len(requestline)
1913
for line in headers.headers:
1914
bytes_read += len(line)
1915
bytes_read += len('\r\n')
1916
if requestline.startswith('POST'):
1917
# The body should be a single line (or we don't know where it ends
1918
# and we don't want to issue a blocking read)
1919
body = self.rfile.readline()
1920
bytes_read += len(body)
1921
tcs.bytes_read = bytes_read
1923
# We set the bytes written *before* issuing the write, the client is
1924
# supposed to consume every produced byte *before* checking that value.
1926
# Doing the oppposite may lead to test failure: we may be interrupted
1927
# after the write but before updating the value. The client can then
1928
# continue and read the value *before* we can update it. And yes,
1929
# this has been observed -- vila 20090129
1930
tcs.bytes_written = len(tcs.canned_response)
1931
self.wfile.write(tcs.canned_response)
1934
class ActivityServerMixin(object):
1936
def __init__(self, protocol_version):
1937
super(ActivityServerMixin, self).__init__(
1938
request_handler=PredefinedRequestHandler,
1939
protocol_version=protocol_version)
1940
# Bytes read and written by the server
1942
self.bytes_written = 0
1943
self.canned_response = None
1946
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
1950
if tests.HTTPSServerFeature.available():
1951
from bzrlib.tests import https_server
1952
class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
1956
class TestActivity(tests.TestCase):
1957
"""Test socket activity reporting.
1959
We use a special purpose server to control the bytes sent and received and
1960
be able to predict the activity on the client socket.
1964
tests.TestCase.setUp(self)
1965
self.server = self._activity_server(self._protocol_version)
1967
self.activities = {}
1968
def report_activity(t, bytes, direction):
1969
count = self.activities.get(direction, 0)
1971
self.activities[direction] = count
1973
# We override at class level because constructors may propagate the
1974
# bound method and render instance overriding ineffective (an
1975
# alternative would be to define a specific ui factory instead...)
1976
self.orig_report_activity = self._transport._report_activity
1977
self._transport._report_activity = report_activity
1980
self._transport._report_activity = self.orig_report_activity
1981
self.server.tearDown()
1982
tests.TestCase.tearDown(self)
1984
def get_transport(self):
1985
return self._transport(self.server.get_url())
1987
def assertActivitiesMatch(self):
1988
self.assertEqual(self.server.bytes_read,
1989
self.activities.get('write', 0), 'written bytes')
1990
self.assertEqual(self.server.bytes_written,
1991
self.activities.get('read', 0), 'read bytes')
1994
self.server.canned_response = '''HTTP/1.1 200 OK\r
1995
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
1996
Server: Apache/2.0.54 (Fedora)\r
1997
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
1998
ETag: "56691-23-38e9ae00"\r
1999
Accept-Ranges: bytes\r
2000
Content-Length: 35\r
2002
Content-Type: text/plain; charset=UTF-8\r
2004
Bazaar-NG meta directory, format 1
2006
t = self.get_transport()
2007
self.assertEqual('Bazaar-NG meta directory, format 1\n',
2008
t.get('foo/bar').read())
2009
self.assertActivitiesMatch()
2012
self.server.canned_response = '''HTTP/1.1 200 OK\r
2013
Server: SimpleHTTP/0.6 Python/2.5.2\r
2014
Date: Thu, 29 Jan 2009 20:21:47 GMT\r
2015
Content-type: application/octet-stream\r
2016
Content-Length: 20\r
2017
Last-Modified: Thu, 29 Jan 2009 20:21:47 GMT\r
2020
t = self.get_transport()
2021
self.assertTrue(t.has('foo/bar'))
2022
self.assertActivitiesMatch()
2024
def test_readv(self):
2025
self.server.canned_response = '''HTTP/1.1 206 Partial Content\r
2026
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
2027
Server: Apache/2.0.54 (Fedora)\r
2028
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
2029
ETag: "238a3c-16ec2-805c5540"\r
2030
Accept-Ranges: bytes\r
2031
Content-Length: 1534\r
2033
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
2036
--418470f848b63279b\r
2037
Content-type: text/plain; charset=UTF-8\r
2038
Content-range: bytes 0-254/93890\r
2040
mbp@sourcefrog.net-20050309040815-13242001617e4a06
2041
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e7627
2042
mbp@sourcefrog.net-20050309040957-6cad07f466bb0bb8
2043
mbp@sourcefrog.net-20050309041501-c840e09071de3b67
2044
mbp@sourcefrog.net-20050309044615-c24a3250be83220a
2046
--418470f848b63279b\r
2047
Content-type: text/plain; charset=UTF-8\r
2048
Content-range: bytes 1000-2049/93890\r
2051
mbp@sourcefrog.net-20050311063625-07858525021f270b
2052
mbp@sourcefrog.net-20050311231934-aa3776aff5200bb9
2053
mbp@sourcefrog.net-20050311231953-73aeb3a131c3699a
2054
mbp@sourcefrog.net-20050311232353-f5e33da490872c6a
2055
mbp@sourcefrog.net-20050312071639-0a8f59a34a024ff0
2056
mbp@sourcefrog.net-20050312073432-b2c16a55e0d6e9fb
2057
mbp@sourcefrog.net-20050312073831-a47c3335ece1920f
2058
mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
2059
mbp@sourcefrog.net-20050313052251-2bf004cb96b39933
2060
mbp@sourcefrog.net-20050313052856-3edd84094687cb11
2061
mbp@sourcefrog.net-20050313053233-e30a4f28aef48f9d
2062
mbp@sourcefrog.net-20050313053853-7c64085594ff3072
2063
mbp@sourcefrog.net-20050313054757-a86c3f5871069e22
2064
mbp@sourcefrog.net-20050313061422-418f1f73b94879b9
2065
mbp@sourcefrog.net-20050313120651-497bd231b19df600
2066
mbp@sourcefrog.net-20050314024931-eae0170ef25a5d1a
2067
mbp@sourcefrog.net-20050314025438-d52099f915fe65fc
2068
mbp@sourcefrog.net-20050314025539-637a636692c055cf
2069
mbp@sourcefrog.net-20050314025737-55eb441f430ab4ba
2070
mbp@sourcefrog.net-20050314025901-d74aa93bb7ee8f62
2072
--418470f848b63279b--\r
2074
t = self.get_transport()
2075
# Remember that the request is ignored and that the ranges below
2076
# doesn't have to match the canned response.
2077
l = list(t.readv('/foo/bar', ((0, 255), (1000, 1050))))
2078
self.assertEqual(2, len(l))
2079
self.assertActivitiesMatch()
2081
def test_post(self):
2082
self.server.canned_response = '''HTTP/1.1 200 OK\r
2083
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2084
Server: Apache/2.0.54 (Fedora)\r
2085
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2086
ETag: "56691-23-38e9ae00"\r
2087
Accept-Ranges: bytes\r
2088
Content-Length: 35\r
2090
Content-Type: text/plain; charset=UTF-8\r
2092
lalala whatever as long as itsssss
2094
t = self.get_transport()
2095
# We must send a single line of body bytes, see
2096
# PredefinedRequestHandler.handle_one_request
2097
code, f = t._post('abc def end-of-body\n')
2098
self.assertEqual('lalala whatever as long as itsssss\n', f.read())
2099
self.assertActivitiesMatch()
1750
t.send_http_smart_request, 'whatever')