~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

Merge bzr.dev to resolve news conflict

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
# TODO: Should be renamed to bzrlib.transport.http.tests?
24
24
# TODO: What about renaming to bzrlib.tests.transport.http ?
25
25
 
26
 
from cStringIO import StringIO
27
26
import httplib
28
 
import os
29
 
import select
30
27
import SimpleHTTPServer
31
28
import socket
32
29
import sys
42
39
    tests,
43
40
    transport,
44
41
    ui,
45
 
    urlutils,
46
42
    )
47
43
from bzrlib.tests import (
48
44
    features,
50
46
    http_utils,
51
47
    test_server,
52
48
    )
 
49
from bzrlib.tests.scenarios import (
 
50
    load_tests_apply_scenarios,
 
51
    multiply_scenarios,
 
52
    )
53
53
from bzrlib.transport import (
54
54
    http,
55
55
    remote,
64
64
    from bzrlib.transport.http._pycurl import PyCurlTransport
65
65
 
66
66
 
67
 
def load_tests(standard_tests, module, loader):
68
 
    """Multiply tests for http clients and protocol versions."""
69
 
    result = loader.suiteClass()
70
 
 
71
 
    # one for each transport implementation
72
 
    t_tests, remaining_tests = tests.split_suite_by_condition(
73
 
        standard_tests, tests.condition_isinstance((
74
 
                TestHttpTransportRegistration,
75
 
                TestHttpTransportUrls,
76
 
                Test_redirected_to,
77
 
                )))
 
67
load_tests = load_tests_apply_scenarios
 
68
 
 
69
 
 
70
def vary_by_http_client_implementation():
 
71
    """Test the two libraries we can use, pycurl and urllib."""
78
72
    transport_scenarios = [
79
73
        ('urllib', dict(_transport=_urllib.HttpTransport_urllib,
80
74
                        _server=http_server.HttpServer_urllib,
85
79
            ('pycurl', dict(_transport=PyCurlTransport,
86
80
                            _server=http_server.HttpServer_PyCurl,
87
81
                            _url_protocol='http+pycurl',)))
88
 
    tests.multiply_tests(t_tests, transport_scenarios, result)
89
 
 
90
 
    protocol_scenarios = [
91
 
            ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
92
 
            ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
93
 
            ]
94
 
 
95
 
    # some tests are parametrized by the protocol version only
96
 
    p_tests, remaining_tests = tests.split_suite_by_condition(
97
 
        remaining_tests, tests.condition_isinstance((
98
 
                TestAuthOnRedirected,
99
 
                )))
100
 
    tests.multiply_tests(p_tests, protocol_scenarios, result)
101
 
 
102
 
    # each implementation tested with each HTTP version
103
 
    tp_tests, remaining_tests = tests.split_suite_by_condition(
104
 
        remaining_tests, tests.condition_isinstance((
105
 
                SmartHTTPTunnellingTest,
106
 
                TestDoCatchRedirections,
107
 
                TestHTTPConnections,
108
 
                TestHTTPRedirections,
109
 
                TestHTTPSilentRedirections,
110
 
                TestLimitedRangeRequestServer,
111
 
                TestPost,
112
 
                TestProxyHttpServer,
113
 
                TestRanges,
114
 
                TestSpecificRequestHandler,
115
 
                )))
116
 
    tp_scenarios = tests.multiply_scenarios(transport_scenarios,
117
 
                                            protocol_scenarios)
118
 
    tests.multiply_tests(tp_tests, tp_scenarios, result)
119
 
 
120
 
    # proxy auth: each auth scheme on all http versions on all implementations.
121
 
    tppa_tests, remaining_tests = tests.split_suite_by_condition(
122
 
        remaining_tests, tests.condition_isinstance((
123
 
                TestProxyAuth,
124
 
                )))
125
 
    proxy_auth_scheme_scenarios = [
 
82
    return transport_scenarios
 
83
 
 
84
 
 
85
def vary_by_http_protocol_version():
 
86
    """Test on http/1.0 and 1.1"""
 
87
    return [
 
88
        ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
 
89
        ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
 
90
        ]
 
91
 
 
92
 
 
93
def vary_by_http_proxy_auth_scheme():
 
94
    return [
126
95
        ('basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
127
96
        ('digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
128
97
        ('basicdigest',
129
 
         dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
 
98
            dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
130
99
        ]
131
 
    tppa_scenarios = tests.multiply_scenarios(tp_scenarios,
132
 
                                              proxy_auth_scheme_scenarios)
133
 
    tests.multiply_tests(tppa_tests, tppa_scenarios, result)
134
 
 
135
 
    # auth: each auth scheme on all http versions on all implementations.
136
 
    tpa_tests, remaining_tests = tests.split_suite_by_condition(
137
 
        remaining_tests, tests.condition_isinstance((
138
 
                TestAuth,
139
 
                )))
140
 
    auth_scheme_scenarios = [
 
100
 
 
101
 
 
102
def vary_by_http_auth_scheme():
 
103
    return [
141
104
        ('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
142
105
        ('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
143
106
        ('basicdigest',
144
 
         dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
 
107
            dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
145
108
        ]
146
 
    tpa_scenarios = tests.multiply_scenarios(tp_scenarios,
147
 
                                             auth_scheme_scenarios)
148
 
    tests.multiply_tests(tpa_tests, tpa_scenarios, result)
149
 
 
150
 
    # activity: on all http[s] versions on all implementations
151
 
    tpact_tests, remaining_tests = tests.split_suite_by_condition(
152
 
        remaining_tests, tests.condition_isinstance((
153
 
                TestActivity,
154
 
                )))
 
109
 
 
110
 
 
111
def vary_by_http_activity():
155
112
    activity_scenarios = [
156
113
        ('urllib,http', dict(_activity_server=ActivityHTTPServer,
157
 
                             _transport=_urllib.HttpTransport_urllib,)),
 
114
                            _transport=_urllib.HttpTransport_urllib,)),
158
115
        ]
159
116
    if tests.HTTPSServerFeature.available():
160
117
        activity_scenarios.append(
161
118
            ('urllib,https', dict(_activity_server=ActivityHTTPSServer,
162
 
                                  _transport=_urllib.HttpTransport_urllib,)),)
 
119
                                _transport=_urllib.HttpTransport_urllib,)),)
163
120
    if features.pycurl.available():
164
121
        activity_scenarios.append(
165
122
            ('pycurl,http', dict(_activity_server=ActivityHTTPServer,
166
 
                                 _transport=PyCurlTransport,)),)
 
123
                                _transport=PyCurlTransport,)),)
167
124
        if tests.HTTPSServerFeature.available():
168
125
            from bzrlib.tests import (
169
126
                ssl_certs,
181
138
 
182
139
            activity_scenarios.append(
183
140
                ('pycurl,https', dict(_activity_server=ActivityHTTPSServer,
184
 
                                      _transport=HTTPS_pycurl_transport,)),)
185
 
 
186
 
    tpact_scenarios = tests.multiply_scenarios(activity_scenarios,
187
 
                                               protocol_scenarios)
188
 
    tests.multiply_tests(tpact_tests, tpact_scenarios, result)
189
 
 
190
 
    # No parametrization for the remaining tests
191
 
    result.addTests(remaining_tests)
192
 
 
193
 
    return result
 
141
                                    _transport=HTTPS_pycurl_transport,)),)
 
142
    return activity_scenarios
194
143
 
195
144
 
196
145
class FakeManager(object):
401
350
class TestHttpTransportUrls(tests.TestCase):
402
351
    """Test the http urls."""
403
352
 
 
353
    scenarios = vary_by_http_client_implementation()
 
354
 
404
355
    def test_abs_url(self):
405
356
        """Construction of absolute http URLs"""
406
357
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
413
364
 
414
365
    def test_invalid_http_urls(self):
415
366
        """Trap invalid construction of urls"""
416
 
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
367
        self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
417
368
        self.assertRaises(errors.InvalidURL,
418
369
                          self._transport,
419
370
                          'http://http://bazaar-vcs.org/bzr/bzr.dev/')
475
426
class TestHTTPConnections(http_utils.TestCaseWithWebserver):
476
427
    """Test the http connections."""
477
428
 
 
429
    scenarios = multiply_scenarios(
 
430
        vary_by_http_client_implementation(), 
 
431
        vary_by_http_protocol_version(),
 
432
        )
 
433
 
478
434
    def setUp(self):
479
435
        http_utils.TestCaseWithWebserver.setUp(self)
480
436
        self.build_tree(['foo/', 'foo/bar'], line_endings='binary',
525
481
class TestHttpTransportRegistration(tests.TestCase):
526
482
    """Test registrations of various http implementations"""
527
483
 
 
484
    scenarios = vary_by_http_client_implementation()
 
485
 
528
486
    def test_http_registered(self):
529
487
        t = transport.get_transport('%s://foo.com/' % self._url_protocol)
530
488
        self.assertIsInstance(t, transport.Transport)
533
491
 
534
492
class TestPost(tests.TestCase):
535
493
 
 
494
    scenarios = multiply_scenarios(
 
495
        vary_by_http_client_implementation(), 
 
496
        vary_by_http_protocol_version(),
 
497
        )
 
498
 
536
499
    def test_post_body_is_received(self):
537
500
        server = RecordingServer(expect_body_tail='end-of-body',
538
501
                                 scheme=self._url_protocol)
544
507
        self.assertTrue(
545
508
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
546
509
        self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
 
510
        self.assertTrue('content-type: application/octet-stream\r'
 
511
                        in server.received_bytes.lower())
547
512
        # The transport should not be assuming that the server can accept
548
513
        # chunked encoding the first time it connects, because HTTP/1.1, so we
549
514
        # check for the literal string.
585
550
    Daughter classes are expected to override _req_handler_class
586
551
    """
587
552
 
 
553
    scenarios = multiply_scenarios(
 
554
        vary_by_http_client_implementation(), 
 
555
        vary_by_http_protocol_version(),
 
556
        )
 
557
 
588
558
    # Provide a useful default
589
559
    _req_handler_class = http_server.TestingHTTPRequestHandler
590
560
 
841
811
        t = self.get_readonly_transport()
842
812
        # force transport to issue multiple requests
843
813
        t._get_max_size = 2
844
 
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
 
814
        list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
845
815
        # The server should have issued 3 requests
846
816
        self.assertEqual(3, server.GET_request_nb)
847
817
        self.assertEqual('0123456789', t.get_bytes('a'))
924
894
    def get_multiple_ranges(self, file, file_size, ranges):
925
895
        self.send_response(206)
926
896
        self.send_header('Accept-Ranges', 'bytes')
 
897
        # XXX: this is strange; the 'random' name below seems undefined and
 
898
        # yet the tests pass -- mbp 2010-10-11 bug 658773
927
899
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
928
900
        self.send_header("Content-Type",
929
901
                         "multipart/byteranges; boundary=%s" % boundary)
991
963
                return
992
964
            self.send_range_content(file, start, end - start + 1)
993
965
            cur += 1
994
 
        # No final boundary
 
966
        # Final boundary
995
967
        self.wfile.write(boundary_line)
996
968
 
997
969
 
1026
998
        # that mode
1027
999
        self.assertEqual('single', t._range_hint)
1028
1000
 
 
1001
 
1029
1002
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1030
1003
    """Errors out when range specifiers exceed the limit"""
1031
1004
 
1055
1028
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1056
1029
    """Tests readv requests against a server erroring out on too much ranges."""
1057
1030
 
 
1031
    scenarios = multiply_scenarios(
 
1032
        vary_by_http_client_implementation(), 
 
1033
        vary_by_http_protocol_version(),
 
1034
        )
 
1035
 
1058
1036
    # Requests with more range specifiers will error out
1059
1037
    range_limit = 3
1060
1038
 
1134
1112
    to the file names).
1135
1113
    """
1136
1114
 
 
1115
    scenarios = multiply_scenarios(
 
1116
        vary_by_http_client_implementation(), 
 
1117
        vary_by_http_protocol_version(),
 
1118
        )
 
1119
 
1137
1120
    # FIXME: We don't have an https server available, so we don't
1138
1121
    # test https connections. --vila toolongago
1139
1122
 
1236
1219
class TestRanges(http_utils.TestCaseWithWebserver):
1237
1220
    """Test the Range header in GET methods."""
1238
1221
 
 
1222
    scenarios = multiply_scenarios(
 
1223
        vary_by_http_client_implementation(), 
 
1224
        vary_by_http_protocol_version(),
 
1225
        )
 
1226
 
1239
1227
    def setUp(self):
1240
1228
        http_utils.TestCaseWithWebserver.setUp(self)
1241
1229
        self.build_tree_contents([('a', '0123456789')],)
1281
1269
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1282
1270
    """Test redirection between http servers."""
1283
1271
 
 
1272
    scenarios = multiply_scenarios(
 
1273
        vary_by_http_client_implementation(), 
 
1274
        vary_by_http_protocol_version(),
 
1275
        )
 
1276
 
1284
1277
    def setUp(self):
1285
1278
        super(TestHTTPRedirections, self).setUp()
1286
1279
        self.build_tree_contents([('a', '0123456789'),
1349
1342
    -- vila 20070212
1350
1343
    """
1351
1344
 
 
1345
    scenarios = multiply_scenarios(
 
1346
        vary_by_http_client_implementation(), 
 
1347
        vary_by_http_protocol_version(),
 
1348
        )
 
1349
 
1352
1350
    def setUp(self):
1353
1351
        if (features.pycurl.available()
1354
1352
            and self._transport == PyCurlTransport):
1399
1397
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
1400
1398
    """Test transport.do_catching_redirections."""
1401
1399
 
 
1400
    scenarios = multiply_scenarios(
 
1401
        vary_by_http_client_implementation(), 
 
1402
        vary_by_http_protocol_version(),
 
1403
        )
 
1404
 
1402
1405
    def setUp(self):
1403
1406
        super(TestDoCatchRedirections, self).setUp()
1404
1407
        self.build_tree_contents([('a', '0123456789'),],)
1446
1449
class TestAuth(http_utils.TestCaseWithWebserver):
1447
1450
    """Test authentication scheme"""
1448
1451
 
 
1452
    scenarios = multiply_scenarios(
 
1453
        vary_by_http_client_implementation(),
 
1454
        vary_by_http_protocol_version(),
 
1455
        vary_by_http_auth_scheme(),
 
1456
        )
 
1457
 
1449
1458
    _auth_header = 'Authorization'
1450
1459
    _password_prompt_prefix = ''
1451
1460
    _username_prompt_prefix = ''
1598
1607
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1599
1608
                                            stderr=tests.StringIOWrapper())
1600
1609
        # Create a minimal config file with the right password
1601
 
        conf = config.AuthenticationConfig()
1602
 
        conf._get_config().update(
1603
 
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1604
 
                          'user': user, 'password': password}})
1605
 
        conf._save()
 
1610
        _setup_authentication_config(
 
1611
            scheme='http', 
 
1612
            port=self.server.port,
 
1613
            user=user,
 
1614
            password=password)
1606
1615
        # Issue a request to the server to connect
1607
1616
        self.assertEqual('contents of a\n',t.get('a').read())
1608
1617
        # stdin should have  been left untouched
1610
1619
        # Only one 'Authentication Required' error should occur
1611
1620
        self.assertEqual(1, self.server.auth_required_errors)
1612
1621
 
1613
 
    def test_user_from_auth_conf(self):
1614
 
        if self._testing_pycurl():
1615
 
            raise tests.TestNotApplicable(
1616
 
                'pycurl does not support authentication.conf')
1617
 
        user = 'joe'
1618
 
        password = 'foo'
1619
 
        self.server.add_user(user, password)
1620
 
        # Create a minimal config file with the right password
1621
 
        conf = config.AuthenticationConfig()
1622
 
        conf._get_config().update(
1623
 
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1624
 
                          'user': user, 'password': password}})
1625
 
        conf._save()
1626
 
        t = self.get_user_transport(None, None)
1627
 
        # Issue a request to the server to connect
1628
 
        self.assertEqual('contents of a\n', t.get('a').read())
1629
 
        # Only one 'Authentication Required' error should occur
1630
 
        self.assertEqual(1, self.server.auth_required_errors)
1631
 
 
1632
1622
    def test_changing_nonce(self):
1633
1623
        if self._auth_server not in (http_utils.HTTPDigestAuthServer,
1634
1624
                                     http_utils.ProxyDigestAuthServer):
1650
1640
        # initial 'who are you' and a second 'who are you' with the new nonce)
1651
1641
        self.assertEqual(2, self.server.auth_required_errors)
1652
1642
 
 
1643
    def test_user_from_auth_conf(self):
 
1644
        if self._testing_pycurl():
 
1645
            raise tests.TestNotApplicable(
 
1646
                'pycurl does not support authentication.conf')
 
1647
        user = 'joe'
 
1648
        password = 'foo'
 
1649
        self.server.add_user(user, password)
 
1650
        _setup_authentication_config(
 
1651
            scheme='http', 
 
1652
            port=self.server.port,
 
1653
            user=user,
 
1654
            password=password)
 
1655
        t = self.get_user_transport(None, None)
 
1656
        # Issue a request to the server to connect
 
1657
        self.assertEqual('contents of a\n', t.get('a').read())
 
1658
        # Only one 'Authentication Required' error should occur
 
1659
        self.assertEqual(1, self.server.auth_required_errors)
 
1660
 
 
1661
 
 
1662
def _setup_authentication_config(**kwargs):
 
1663
    conf = config.AuthenticationConfig()
 
1664
    conf._get_config().update({'httptest': kwargs})
 
1665
    conf._save()
 
1666
 
 
1667
 
 
1668
 
 
1669
class TestUrllib2AuthHandler(tests.TestCaseWithTransport):
 
1670
    """Unit tests for glue by which urllib2 asks us for authentication"""
 
1671
 
 
1672
    def test_get_user_password_without_port(self):
 
1673
        """We cope if urllib2 doesn't tell us the port.
 
1674
 
 
1675
        See https://bugs.launchpad.net/bzr/+bug/654684
 
1676
        """
 
1677
        user = 'joe'
 
1678
        password = 'foo'
 
1679
        _setup_authentication_config(
 
1680
            scheme='http', 
 
1681
            host='localhost',
 
1682
            user=user,
 
1683
            password=password)
 
1684
        handler = _urllib2_wrappers.HTTPAuthHandler()
 
1685
        got_pass = handler.get_user_password(dict(
 
1686
            user='joe',
 
1687
            protocol='http',
 
1688
            host='localhost',
 
1689
            path='/',
 
1690
            realm='Realm',
 
1691
            ))
 
1692
        self.assertEquals((user, password), got_pass)
1653
1693
 
1654
1694
 
1655
1695
class TestProxyAuth(TestAuth):
1656
1696
    """Test proxy authentication schemes."""
1657
1697
 
 
1698
    scenarios = multiply_scenarios(
 
1699
        vary_by_http_client_implementation(),
 
1700
        vary_by_http_protocol_version(),
 
1701
        vary_by_http_proxy_auth_scheme(),
 
1702
        )
 
1703
 
1658
1704
    _auth_header = 'Proxy-authorization'
1659
1705
    _password_prompt_prefix = 'Proxy '
1660
1706
    _username_prompt_prefix = 'Proxy '
1716
1762
 
1717
1763
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1718
1764
 
 
1765
    scenarios = multiply_scenarios(
 
1766
        vary_by_http_client_implementation(), 
 
1767
        vary_by_http_protocol_version(),
 
1768
        )
 
1769
 
1719
1770
    def setUp(self):
1720
1771
        super(SmartHTTPTunnellingTest, self).setUp()
1721
1772
        # We use the VFS layer as part of HTTP tunnelling tests.
1810
1861
 
1811
1862
class Test_redirected_to(tests.TestCase):
1812
1863
 
 
1864
    scenarios = vary_by_http_client_implementation()
 
1865
 
1813
1866
    def test_redirected_to_subdir(self):
1814
1867
        t = self._transport('http://www.example.com/foo')
1815
1868
        r = t._redirected_to('http://www.example.com/foo',
2061
2114
 
2062
2115
class TestActivity(tests.TestCase, TestActivityMixin):
2063
2116
 
 
2117
    scenarios = multiply_scenarios(
 
2118
        vary_by_http_activity(),
 
2119
        vary_by_http_protocol_version(),
 
2120
        )
 
2121
 
2064
2122
    def setUp(self):
2065
2123
        TestActivityMixin.setUp(self)
2066
2124
 
2087
2145
class TestAuthOnRedirected(http_utils.TestCaseWithRedirectedWebserver):
2088
2146
    """Test authentication on the redirected http server."""
2089
2147
 
 
2148
    scenarios = vary_by_http_protocol_version()
 
2149
 
2090
2150
    _auth_header = 'Authorization'
2091
2151
    _password_prompt_prefix = ''
2092
2152
    _username_prompt_prefix = ''