~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Haw Loeung (hloeung)
  • Date: 2012-07-24 12:53:36 UTC
  • mfrom: (6541 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6542.
  • Revision ID: haw.loeung@canonical.com-20120724125336-r3wzxm02lyec7jm6
[hloeung] Merged with upstream resolving conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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
34
31
 
35
32
import bzrlib
36
33
from bzrlib import (
37
 
    bzrdir,
38
34
    config,
 
35
    controldir,
 
36
    debug,
39
37
    errors,
40
38
    osutils,
41
39
    remote as _mod_remote,
42
40
    tests,
 
41
    trace,
43
42
    transport,
44
43
    ui,
45
 
    urlutils,
46
44
    )
47
45
from bzrlib.tests import (
48
46
    features,
50
48
    http_utils,
51
49
    test_server,
52
50
    )
 
51
from bzrlib.tests.scenarios import (
 
52
    load_tests_apply_scenarios,
 
53
    multiply_scenarios,
 
54
    )
53
55
from bzrlib.transport import (
54
56
    http,
55
57
    remote,
64
66
    from bzrlib.transport.http._pycurl import PyCurlTransport
65
67
 
66
68
 
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
 
                )))
 
69
load_tests = load_tests_apply_scenarios
 
70
 
 
71
 
 
72
def vary_by_http_client_implementation():
 
73
    """Test the two libraries we can use, pycurl and urllib."""
78
74
    transport_scenarios = [
79
75
        ('urllib', dict(_transport=_urllib.HttpTransport_urllib,
80
76
                        _server=http_server.HttpServer_urllib,
85
81
            ('pycurl', dict(_transport=PyCurlTransport,
86
82
                            _server=http_server.HttpServer_PyCurl,
87
83
                            _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 = [
126
 
        ('basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
127
 
        ('digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
128
 
        ('basicdigest',
129
 
         dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
 
84
    return transport_scenarios
 
85
 
 
86
 
 
87
def vary_by_http_protocol_version():
 
88
    """Test on http/1.0 and 1.1"""
 
89
    return [
 
90
        ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
 
91
        ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
130
92
        ]
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 = [
 
93
 
 
94
 
 
95
def vary_by_http_auth_scheme():
 
96
    scenarios = [
141
97
        ('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
142
98
        ('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
143
99
        ('basicdigest',
144
 
         dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
145
 
        ]
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
 
                )))
 
100
            dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
 
101
        ]
 
102
    # Add some attributes common to all scenarios
 
103
    for scenario_id, scenario_dict in scenarios:
 
104
        scenario_dict.update(_auth_header='Authorization',
 
105
                             _username_prompt_prefix='',
 
106
                             _password_prompt_prefix='')
 
107
    return scenarios
 
108
 
 
109
 
 
110
def vary_by_http_proxy_auth_scheme():
 
111
    scenarios = [
 
112
        ('proxy-basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
 
113
        ('proxy-digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
 
114
        ('proxy-basicdigest',
 
115
            dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
 
116
        ]
 
117
    # Add some attributes common to all scenarios
 
118
    for scenario_id, scenario_dict in scenarios:
 
119
        scenario_dict.update(_auth_header='Proxy-Authorization',
 
120
                             _username_prompt_prefix='Proxy ',
 
121
                             _password_prompt_prefix='Proxy ')
 
122
    return scenarios
 
123
 
 
124
 
 
125
def vary_by_http_activity():
155
126
    activity_scenarios = [
156
127
        ('urllib,http', dict(_activity_server=ActivityHTTPServer,
157
 
                             _transport=_urllib.HttpTransport_urllib,)),
 
128
                            _transport=_urllib.HttpTransport_urllib,)),
158
129
        ]
159
 
    if tests.HTTPSServerFeature.available():
 
130
    if features.pycurl.available():
 
131
        activity_scenarios.append(
 
132
            ('pycurl,http', dict(_activity_server=ActivityHTTPServer,
 
133
                                _transport=PyCurlTransport,)),)
 
134
    if features.HTTPSServerFeature.available():
 
135
        # FIXME: Until we have a better way to handle self-signed certificates
 
136
        # (like allowing them in a test specific authentication.conf for
 
137
        # example), we need some specialized pycurl/urllib transport for tests.
 
138
        # -- vila 2012-01-20
 
139
        from bzrlib.tests import (
 
140
            ssl_certs,
 
141
            )
 
142
        class HTTPS_urllib_transport(_urllib.HttpTransport_urllib):
 
143
 
 
144
            def __init__(self, base, _from_transport=None):
 
145
                super(HTTPS_urllib_transport, self).__init__(
 
146
                    base, _from_transport=_from_transport,
 
147
                    ca_certs=ssl_certs.build_path('ca.crt'))
 
148
 
160
149
        activity_scenarios.append(
161
150
            ('urllib,https', dict(_activity_server=ActivityHTTPSServer,
162
 
                                  _transport=_urllib.HttpTransport_urllib,)),)
163
 
    if features.pycurl.available():
164
 
        activity_scenarios.append(
165
 
            ('pycurl,http', dict(_activity_server=ActivityHTTPServer,
166
 
                                 _transport=PyCurlTransport,)),)
167
 
        if tests.HTTPSServerFeature.available():
168
 
            from bzrlib.tests import (
169
 
                ssl_certs,
170
 
                )
171
 
            # FIXME: Until we have a better way to handle self-signed
172
 
            # certificates (like allowing them in a test specific
173
 
            # authentication.conf for example), we need some specialized pycurl
174
 
            # transport for tests.
 
151
                                  _transport=HTTPS_urllib_transport,)),)
 
152
        if features.pycurl.available():
175
153
            class HTTPS_pycurl_transport(PyCurlTransport):
176
154
 
177
155
                def __init__(self, base, _from_transport=None):
181
159
 
182
160
            activity_scenarios.append(
183
161
                ('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
 
162
                                    _transport=HTTPS_pycurl_transport,)),)
 
163
    return activity_scenarios
194
164
 
195
165
 
196
166
class FakeManager(object):
229
199
        self._sock.bind(('127.0.0.1', 0))
230
200
        self.host, self.port = self._sock.getsockname()
231
201
        self._ready = threading.Event()
232
 
        self._thread = test_server.ThreadWithException(
233
 
            event=self._ready, target=self._accept_read_and_reply)
 
202
        self._thread = test_server.TestThread(
 
203
            sync_event=self._ready, target=self._accept_read_and_reply)
234
204
        self._thread.start()
235
205
        if 'threads' in tests.selftest_debug_flags:
236
206
            sys.stderr.write('Thread started: %s\n' % (self._thread.ident,))
305
275
        self.assertEqual('realm="Thou should not pass"', remainder)
306
276
 
307
277
 
 
278
class TestHTTPRangeParsing(tests.TestCase):
 
279
 
 
280
    def setUp(self):
 
281
        super(TestHTTPRangeParsing, self).setUp()
 
282
        # We focus on range  parsing here and ignore everything else
 
283
        class RequestHandler(http_server.TestingHTTPRequestHandler):
 
284
            def setup(self): pass
 
285
            def handle(self): pass
 
286
            def finish(self): pass
 
287
 
 
288
        self.req_handler = RequestHandler(None, None, None)
 
289
 
 
290
    def assertRanges(self, ranges, header, file_size):
 
291
        self.assertEquals(ranges,
 
292
                          self.req_handler._parse_ranges(header, file_size))
 
293
 
 
294
    def test_simple_range(self):
 
295
        self.assertRanges([(0,2)], 'bytes=0-2', 12)
 
296
 
 
297
    def test_tail(self):
 
298
        self.assertRanges([(8, 11)], 'bytes=-4', 12)
 
299
 
 
300
    def test_tail_bigger_than_file(self):
 
301
        self.assertRanges([(0, 11)], 'bytes=-99', 12)
 
302
 
 
303
    def test_range_without_end(self):
 
304
        self.assertRanges([(4, 11)], 'bytes=4-', 12)
 
305
 
 
306
    def test_invalid_ranges(self):
 
307
        self.assertRanges(None, 'bytes=12-22', 12)
 
308
        self.assertRanges(None, 'bytes=1-3,12-22', 12)
 
309
        self.assertRanges(None, 'bytes=-', 12)
 
310
 
 
311
 
308
312
class TestHTTPServer(tests.TestCase):
309
313
    """Test the HTTP servers implementations."""
310
314
 
401
405
class TestHttpTransportUrls(tests.TestCase):
402
406
    """Test the http urls."""
403
407
 
 
408
    scenarios = vary_by_http_client_implementation()
 
409
 
404
410
    def test_abs_url(self):
405
411
        """Construction of absolute http URLs"""
406
 
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
412
        t = self._transport('http://example.com/bzr/bzr.dev/')
407
413
        eq = self.assertEqualDiff
408
 
        eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
409
 
        eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
410
 
        eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
 
414
        eq(t.abspath('.'), 'http://example.com/bzr/bzr.dev')
 
415
        eq(t.abspath('foo/bar'), 'http://example.com/bzr/bzr.dev/foo/bar')
 
416
        eq(t.abspath('.bzr'), 'http://example.com/bzr/bzr.dev/.bzr')
411
417
        eq(t.abspath('.bzr/1//2/./3'),
412
 
           'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
 
418
           'http://example.com/bzr/bzr.dev/.bzr/1/2/3')
413
419
 
414
420
    def test_invalid_http_urls(self):
415
421
        """Trap invalid construction of urls"""
416
 
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
422
        self._transport('http://example.com/bzr/bzr.dev/')
417
423
        self.assertRaises(errors.InvalidURL,
418
424
                          self._transport,
419
 
                          'http://http://bazaar-vcs.org/bzr/bzr.dev/')
 
425
                          'http://http://example.com/bzr/bzr.dev/')
420
426
 
421
427
    def test_http_root_urls(self):
422
428
        """Construction of URLs from server root"""
423
 
        t = self._transport('http://bzr.ozlabs.org/')
 
429
        t = self._transport('http://example.com/')
424
430
        eq = self.assertEqualDiff
425
431
        eq(t.abspath('.bzr/tree-version'),
426
 
           'http://bzr.ozlabs.org/.bzr/tree-version')
 
432
           'http://example.com/.bzr/tree-version')
427
433
 
428
434
    def test_http_impl_urls(self):
429
435
        """There are servers which ask for particular clients to connect"""
475
481
class TestHTTPConnections(http_utils.TestCaseWithWebserver):
476
482
    """Test the http connections."""
477
483
 
 
484
    scenarios = multiply_scenarios(
 
485
        vary_by_http_client_implementation(),
 
486
        vary_by_http_protocol_version(),
 
487
        )
 
488
 
478
489
    def setUp(self):
479
490
        http_utils.TestCaseWithWebserver.setUp(self)
480
491
        self.build_tree(['foo/', 'foo/bar'], line_endings='binary',
525
536
class TestHttpTransportRegistration(tests.TestCase):
526
537
    """Test registrations of various http implementations"""
527
538
 
 
539
    scenarios = vary_by_http_client_implementation()
 
540
 
528
541
    def test_http_registered(self):
529
 
        t = transport.get_transport('%s://foo.com/' % self._url_protocol)
 
542
        t = transport.get_transport_from_url(
 
543
            '%s://foo.com/' % self._url_protocol)
530
544
        self.assertIsInstance(t, transport.Transport)
531
545
        self.assertIsInstance(t, self._transport)
532
546
 
533
547
 
534
548
class TestPost(tests.TestCase):
535
549
 
 
550
    scenarios = multiply_scenarios(
 
551
        vary_by_http_client_implementation(),
 
552
        vary_by_http_protocol_version(),
 
553
        )
 
554
 
536
555
    def test_post_body_is_received(self):
537
556
        server = RecordingServer(expect_body_tail='end-of-body',
538
557
                                 scheme=self._url_protocol)
539
558
        self.start_server(server)
540
559
        url = server.get_url()
541
560
        # FIXME: needs a cleanup -- vila 20100611
542
 
        http_transport = transport.get_transport(url)
 
561
        http_transport = transport.get_transport_from_url(url)
543
562
        code, response = http_transport._post('abc def end-of-body')
544
563
        self.assertTrue(
545
564
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
546
565
        self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
 
566
        self.assertTrue('content-type: application/octet-stream\r'
 
567
                        in server.received_bytes.lower())
547
568
        # The transport should not be assuming that the server can accept
548
569
        # chunked encoding the first time it connects, because HTTP/1.1, so we
549
570
        # check for the literal string.
585
606
    Daughter classes are expected to override _req_handler_class
586
607
    """
587
608
 
 
609
    scenarios = multiply_scenarios(
 
610
        vary_by_http_client_implementation(),
 
611
        vary_by_http_protocol_version(),
 
612
        )
 
613
 
588
614
    # Provide a useful default
589
615
    _req_handler_class = http_server.TestingHTTPRequestHandler
590
616
 
841
867
        t = self.get_readonly_transport()
842
868
        # force transport to issue multiple requests
843
869
        t._get_max_size = 2
844
 
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
 
870
        list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
845
871
        # The server should have issued 3 requests
846
872
        self.assertEqual(3, server.GET_request_nb)
847
873
        self.assertEqual('0123456789', t.get_bytes('a'))
924
950
    def get_multiple_ranges(self, file, file_size, ranges):
925
951
        self.send_response(206)
926
952
        self.send_header('Accept-Ranges', 'bytes')
 
953
        # XXX: this is strange; the 'random' name below seems undefined and
 
954
        # yet the tests pass -- mbp 2010-10-11 bug 658773
927
955
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
928
956
        self.send_header("Content-Type",
929
957
                         "multipart/byteranges; boundary=%s" % boundary)
991
1019
                return
992
1020
            self.send_range_content(file, start, end - start + 1)
993
1021
            cur += 1
994
 
        # No final boundary
 
1022
        # Final boundary
995
1023
        self.wfile.write(boundary_line)
996
1024
 
997
1025
 
1026
1054
        # that mode
1027
1055
        self.assertEqual('single', t._range_hint)
1028
1056
 
 
1057
 
 
1058
class TruncatedBeforeBoundaryRequestHandler(
 
1059
    http_server.TestingHTTPRequestHandler):
 
1060
    """Truncation before a boundary, like in bug 198646"""
 
1061
 
 
1062
    _truncated_ranges = 1
 
1063
 
 
1064
    def get_multiple_ranges(self, file, file_size, ranges):
 
1065
        self.send_response(206)
 
1066
        self.send_header('Accept-Ranges', 'bytes')
 
1067
        boundary = 'tagada'
 
1068
        self.send_header('Content-Type',
 
1069
                         'multipart/byteranges; boundary=%s' % boundary)
 
1070
        boundary_line = '--%s\r\n' % boundary
 
1071
        # Calculate the Content-Length
 
1072
        content_length = 0
 
1073
        for (start, end) in ranges:
 
1074
            content_length += len(boundary_line)
 
1075
            content_length += self._header_line_length(
 
1076
                'Content-type', 'application/octet-stream')
 
1077
            content_length += self._header_line_length(
 
1078
                'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
 
1079
            content_length += len('\r\n') # end headers
 
1080
            content_length += end - start # + 1
 
1081
        content_length += len(boundary_line)
 
1082
        self.send_header('Content-length', content_length)
 
1083
        self.end_headers()
 
1084
 
 
1085
        # Send the multipart body
 
1086
        cur = 0
 
1087
        for (start, end) in ranges:
 
1088
            if cur + self._truncated_ranges >= len(ranges):
 
1089
                # Abruptly ends the response and close the connection
 
1090
                self.close_connection = 1
 
1091
                return
 
1092
            self.wfile.write(boundary_line)
 
1093
            self.send_header('Content-type', 'application/octet-stream')
 
1094
            self.send_header('Content-Range', 'bytes %d-%d/%d'
 
1095
                             % (start, end, file_size))
 
1096
            self.end_headers()
 
1097
            self.send_range_content(file, start, end - start + 1)
 
1098
            cur += 1
 
1099
        # Final boundary
 
1100
        self.wfile.write(boundary_line)
 
1101
 
 
1102
 
 
1103
class TestTruncatedBeforeBoundary(TestSpecificRequestHandler):
 
1104
    """Tests the case of bug 198646, disconnecting before a boundary."""
 
1105
 
 
1106
    _req_handler_class = TruncatedBeforeBoundaryRequestHandler
 
1107
 
 
1108
    def setUp(self):
 
1109
        super(TestTruncatedBeforeBoundary, self).setUp()
 
1110
        self.build_tree_contents([('a', '0123456789')],)
 
1111
 
 
1112
    def test_readv_with_short_reads(self):
 
1113
        server = self.get_readonly_server()
 
1114
        t = self.get_readonly_transport()
 
1115
        # Force separate ranges for each offset
 
1116
        t._bytes_to_read_before_seek = 0
 
1117
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
 
1118
        self.assertEqual((0, '0'), ireadv.next())
 
1119
        self.assertEqual((2, '2'), ireadv.next())
 
1120
        self.assertEqual((4, '45'), ireadv.next())
 
1121
        self.assertEqual((9, '9'), ireadv.next())
 
1122
 
 
1123
 
1029
1124
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1030
1125
    """Errors out when range specifiers exceed the limit"""
1031
1126
 
1055
1150
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1056
1151
    """Tests readv requests against a server erroring out on too much ranges."""
1057
1152
 
 
1153
    scenarios = multiply_scenarios(
 
1154
        vary_by_http_client_implementation(),
 
1155
        vary_by_http_protocol_version(),
 
1156
        )
 
1157
 
1058
1158
    # Requests with more range specifiers will error out
1059
1159
    range_limit = 3
1060
1160
 
1095
1195
    Only the urllib implementation is tested here.
1096
1196
    """
1097
1197
 
1098
 
    def setUp(self):
1099
 
        tests.TestCase.setUp(self)
1100
 
        self._old_env = {}
1101
 
        self.addCleanup(self._restore_env)
1102
 
 
1103
 
    def _install_env(self, env):
1104
 
        for name, value in env.iteritems():
1105
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1106
 
 
1107
 
    def _restore_env(self):
1108
 
        for name, value in self._old_env.iteritems():
1109
 
            osutils.set_or_unset_env(name, value)
1110
 
 
1111
1198
    def _proxied_request(self):
1112
1199
        handler = _urllib2_wrappers.ProxyHandler()
1113
 
        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
 
1200
        request = _urllib2_wrappers.Request('GET', 'http://baz/buzzle')
1114
1201
        handler.set_proxy(request, 'http')
1115
1202
        return request
1116
1203
 
 
1204
    def assertEvaluateProxyBypass(self, expected, host, no_proxy):
 
1205
        handler = _urllib2_wrappers.ProxyHandler()
 
1206
        self.assertEquals(expected,
 
1207
                          handler.evaluate_proxy_bypass(host, no_proxy))
 
1208
 
1117
1209
    def test_empty_user(self):
1118
 
        self._install_env({'http_proxy': 'http://bar.com'})
 
1210
        self.overrideEnv('http_proxy', 'http://bar.com')
 
1211
        request = self._proxied_request()
 
1212
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
 
1213
 
 
1214
    def test_user_with_at(self):
 
1215
        self.overrideEnv('http_proxy',
 
1216
                         'http://username@domain:password@proxy_host:1234')
1119
1217
        request = self._proxied_request()
1120
1218
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
1121
1219
 
1122
1220
    def test_invalid_proxy(self):
1123
1221
        """A proxy env variable without scheme"""
1124
 
        self._install_env({'http_proxy': 'host:1234'})
 
1222
        self.overrideEnv('http_proxy', 'host:1234')
1125
1223
        self.assertRaises(errors.InvalidURL, self._proxied_request)
1126
1224
 
 
1225
    def test_evaluate_proxy_bypass_true(self):
 
1226
        """The host is not proxied"""
 
1227
        self.assertEvaluateProxyBypass(True, 'example.com', 'example.com')
 
1228
        self.assertEvaluateProxyBypass(True, 'bzr.example.com', '*example.com')
 
1229
 
 
1230
    def test_evaluate_proxy_bypass_false(self):
 
1231
        """The host is proxied"""
 
1232
        self.assertEvaluateProxyBypass(False, 'bzr.example.com', None)
 
1233
 
 
1234
    def test_evaluate_proxy_bypass_unknown(self):
 
1235
        """The host is not explicitly proxied"""
 
1236
        self.assertEvaluateProxyBypass(None, 'example.com', 'not.example.com')
 
1237
        self.assertEvaluateProxyBypass(None, 'bzr.example.com', 'example.com')
 
1238
 
 
1239
    def test_evaluate_proxy_bypass_empty_entries(self):
 
1240
        """Ignore empty entries"""
 
1241
        self.assertEvaluateProxyBypass(None, 'example.com', '')
 
1242
        self.assertEvaluateProxyBypass(None, 'example.com', ',')
 
1243
        self.assertEvaluateProxyBypass(None, 'example.com', 'foo,,bar')
 
1244
 
1127
1245
 
1128
1246
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
1129
1247
    """Tests proxy server.
1134
1252
    to the file names).
1135
1253
    """
1136
1254
 
 
1255
    scenarios = multiply_scenarios(
 
1256
        vary_by_http_client_implementation(),
 
1257
        vary_by_http_protocol_version(),
 
1258
        )
 
1259
 
1137
1260
    # FIXME: We don't have an https server available, so we don't
1138
1261
    # test https connections. --vila toolongago
1139
1262
 
1153
1276
            self.no_proxy_host = self.server_host_port
1154
1277
        # The secondary server is the proxy
1155
1278
        self.proxy_url = self.get_secondary_url()
1156
 
        self._old_env = {}
1157
1279
 
1158
1280
    def _testing_pycurl(self):
1159
1281
        # TODO: This is duplicated for lots of the classes in this file
1160
1282
        return (features.pycurl.available()
1161
1283
                and self._transport == PyCurlTransport)
1162
1284
 
1163
 
    def _install_env(self, env):
1164
 
        for name, value in env.iteritems():
1165
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1166
 
 
1167
 
    def _restore_env(self):
1168
 
        for name, value in self._old_env.iteritems():
1169
 
            osutils.set_or_unset_env(name, value)
1170
 
 
1171
 
    def proxied_in_env(self, env):
1172
 
        self._install_env(env)
1173
 
        t = self.get_readonly_transport()
1174
 
        try:
1175
 
            self.assertEqual('proxied contents of foo\n', t.get('foo').read())
1176
 
        finally:
1177
 
            self._restore_env()
1178
 
 
1179
 
    def not_proxied_in_env(self, env):
1180
 
        self._install_env(env)
1181
 
        t = self.get_readonly_transport()
1182
 
        try:
1183
 
            self.assertEqual('contents of foo\n', t.get('foo').read())
1184
 
        finally:
1185
 
            self._restore_env()
 
1285
    def assertProxied(self):
 
1286
        t = self.get_readonly_transport()
 
1287
        self.assertEqual('proxied contents of foo\n', t.get('foo').read())
 
1288
 
 
1289
    def assertNotProxied(self):
 
1290
        t = self.get_readonly_transport()
 
1291
        self.assertEqual('contents of foo\n', t.get('foo').read())
1186
1292
 
1187
1293
    def test_http_proxy(self):
1188
 
        self.proxied_in_env({'http_proxy': self.proxy_url})
 
1294
        self.overrideEnv('http_proxy', self.proxy_url)
 
1295
        self.assertProxied()
1189
1296
 
1190
1297
    def test_HTTP_PROXY(self):
1191
1298
        if self._testing_pycurl():
1194
1301
            # about. Should we ?)
1195
1302
            raise tests.TestNotApplicable(
1196
1303
                'pycurl does not check HTTP_PROXY for security reasons')
1197
 
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
 
1304
        self.overrideEnv('HTTP_PROXY', self.proxy_url)
 
1305
        self.assertProxied()
1198
1306
 
1199
1307
    def test_all_proxy(self):
1200
 
        self.proxied_in_env({'all_proxy': self.proxy_url})
 
1308
        self.overrideEnv('all_proxy', self.proxy_url)
 
1309
        self.assertProxied()
1201
1310
 
1202
1311
    def test_ALL_PROXY(self):
1203
 
        self.proxied_in_env({'ALL_PROXY': self.proxy_url})
 
1312
        self.overrideEnv('ALL_PROXY', self.proxy_url)
 
1313
        self.assertProxied()
1204
1314
 
1205
1315
    def test_http_proxy_with_no_proxy(self):
1206
 
        self.not_proxied_in_env({'http_proxy': self.proxy_url,
1207
 
                                 'no_proxy': self.no_proxy_host})
 
1316
        self.overrideEnv('no_proxy', self.no_proxy_host)
 
1317
        self.overrideEnv('http_proxy', self.proxy_url)
 
1318
        self.assertNotProxied()
1208
1319
 
1209
1320
    def test_HTTP_PROXY_with_NO_PROXY(self):
1210
1321
        if self._testing_pycurl():
1211
1322
            raise tests.TestNotApplicable(
1212
1323
                'pycurl does not check HTTP_PROXY for security reasons')
1213
 
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
1214
 
                                 'NO_PROXY': self.no_proxy_host})
 
1324
        self.overrideEnv('NO_PROXY', self.no_proxy_host)
 
1325
        self.overrideEnv('HTTP_PROXY', self.proxy_url)
 
1326
        self.assertNotProxied()
1215
1327
 
1216
1328
    def test_all_proxy_with_no_proxy(self):
1217
 
        self.not_proxied_in_env({'all_proxy': self.proxy_url,
1218
 
                                 'no_proxy': self.no_proxy_host})
 
1329
        self.overrideEnv('no_proxy', self.no_proxy_host)
 
1330
        self.overrideEnv('all_proxy', self.proxy_url)
 
1331
        self.assertNotProxied()
1219
1332
 
1220
1333
    def test_ALL_PROXY_with_NO_PROXY(self):
1221
 
        self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
1222
 
                                 'NO_PROXY': self.no_proxy_host})
 
1334
        self.overrideEnv('NO_PROXY', self.no_proxy_host)
 
1335
        self.overrideEnv('ALL_PROXY', self.proxy_url)
 
1336
        self.assertNotProxied()
1223
1337
 
1224
1338
    def test_http_proxy_without_scheme(self):
 
1339
        self.overrideEnv('http_proxy', self.server_host_port)
1225
1340
        if self._testing_pycurl():
1226
1341
            # pycurl *ignores* invalid proxy env variables. If that ever change
1227
1342
            # in the future, this test will fail indicating that pycurl do not
1228
1343
            # ignore anymore such variables.
1229
 
            self.not_proxied_in_env({'http_proxy': self.server_host_port})
 
1344
            self.assertNotProxied()
1230
1345
        else:
1231
 
            self.assertRaises(errors.InvalidURL,
1232
 
                              self.proxied_in_env,
1233
 
                              {'http_proxy': self.server_host_port})
 
1346
            self.assertRaises(errors.InvalidURL, self.assertProxied)
1234
1347
 
1235
1348
 
1236
1349
class TestRanges(http_utils.TestCaseWithWebserver):
1237
1350
    """Test the Range header in GET methods."""
1238
1351
 
 
1352
    scenarios = multiply_scenarios(
 
1353
        vary_by_http_client_implementation(),
 
1354
        vary_by_http_protocol_version(),
 
1355
        )
 
1356
 
1239
1357
    def setUp(self):
1240
1358
        http_utils.TestCaseWithWebserver.setUp(self)
1241
1359
        self.build_tree_contents([('a', '0123456789')],)
1281
1399
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1282
1400
    """Test redirection between http servers."""
1283
1401
 
 
1402
    scenarios = multiply_scenarios(
 
1403
        vary_by_http_client_implementation(),
 
1404
        vary_by_http_protocol_version(),
 
1405
        )
 
1406
 
1284
1407
    def setUp(self):
1285
1408
        super(TestHTTPRedirections, self).setUp()
1286
1409
        self.build_tree_contents([('a', '0123456789'),
1349
1472
    -- vila 20070212
1350
1473
    """
1351
1474
 
 
1475
    scenarios = multiply_scenarios(
 
1476
        vary_by_http_client_implementation(),
 
1477
        vary_by_http_protocol_version(),
 
1478
        )
 
1479
 
1352
1480
    def setUp(self):
1353
1481
        if (features.pycurl.available()
1354
1482
            and self._transport == PyCurlTransport):
1399
1527
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
1400
1528
    """Test transport.do_catching_redirections."""
1401
1529
 
 
1530
    scenarios = multiply_scenarios(
 
1531
        vary_by_http_client_implementation(),
 
1532
        vary_by_http_protocol_version(),
 
1533
        )
 
1534
 
1402
1535
    def setUp(self):
1403
1536
        super(TestDoCatchRedirections, self).setUp()
1404
1537
        self.build_tree_contents([('a', '0123456789'),],)
1443
1576
                          self.get_a, self.old_transport, redirected)
1444
1577
 
1445
1578
 
 
1579
def _setup_authentication_config(**kwargs):
 
1580
    conf = config.AuthenticationConfig()
 
1581
    conf._get_config().update({'httptest': kwargs})
 
1582
    conf._save()
 
1583
 
 
1584
 
 
1585
class TestUrllib2AuthHandler(tests.TestCaseWithTransport):
 
1586
    """Unit tests for glue by which urllib2 asks us for authentication"""
 
1587
 
 
1588
    def test_get_user_password_without_port(self):
 
1589
        """We cope if urllib2 doesn't tell us the port.
 
1590
 
 
1591
        See https://bugs.launchpad.net/bzr/+bug/654684
 
1592
        """
 
1593
        user = 'joe'
 
1594
        password = 'foo'
 
1595
        _setup_authentication_config(scheme='http', host='localhost',
 
1596
                                     user=user, password=password)
 
1597
        handler = _urllib2_wrappers.HTTPAuthHandler()
 
1598
        got_pass = handler.get_user_password(dict(
 
1599
            user='joe',
 
1600
            protocol='http',
 
1601
            host='localhost',
 
1602
            path='/',
 
1603
            realm='Realm',
 
1604
            ))
 
1605
        self.assertEquals((user, password), got_pass)
 
1606
 
 
1607
 
1446
1608
class TestAuth(http_utils.TestCaseWithWebserver):
1447
1609
    """Test authentication scheme"""
1448
1610
 
1449
 
    _auth_header = 'Authorization'
1450
 
    _password_prompt_prefix = ''
1451
 
    _username_prompt_prefix = ''
1452
 
    # Set by load_tests
1453
 
    _auth_server = None
 
1611
    scenarios = multiply_scenarios(
 
1612
        vary_by_http_client_implementation(),
 
1613
        vary_by_http_protocol_version(),
 
1614
        vary_by_http_auth_scheme(),
 
1615
        )
1454
1616
 
1455
1617
    def setUp(self):
1456
1618
        super(TestAuth, self).setUp()
1480
1642
        return url
1481
1643
 
1482
1644
    def get_user_transport(self, user, password):
1483
 
        t = transport.get_transport(self.get_user_url(user, password))
 
1645
        t = transport.get_transport_from_url(
 
1646
            self.get_user_url(user, password))
1484
1647
        return t
1485
1648
 
1486
1649
    def test_no_user(self):
1598
1761
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1599
1762
                                            stderr=tests.StringIOWrapper())
1600
1763
        # 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()
 
1764
        _setup_authentication_config(scheme='http', port=self.server.port,
 
1765
                                     user=user, password=password)
1606
1766
        # Issue a request to the server to connect
1607
1767
        self.assertEqual('contents of a\n',t.get('a').read())
1608
1768
        # stdin should have  been left untouched
1610
1770
        # Only one 'Authentication Required' error should occur
1611
1771
        self.assertEqual(1, self.server.auth_required_errors)
1612
1772
 
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
1773
    def test_changing_nonce(self):
1633
1774
        if self._auth_server not in (http_utils.HTTPDigestAuthServer,
1634
1775
                                     http_utils.ProxyDigestAuthServer):
1635
1776
            raise tests.TestNotApplicable('HTTP/proxy auth digest only test')
1636
1777
        if self._testing_pycurl():
1637
 
            raise tests.KnownFailure(
 
1778
            self.knownFailure(
1638
1779
                'pycurl does not handle a nonce change')
1639
1780
        self.server.add_user('joe', 'foo')
1640
1781
        t = self.get_user_transport('joe', 'foo')
1650
1791
        # initial 'who are you' and a second 'who are you' with the new nonce)
1651
1792
        self.assertEqual(2, self.server.auth_required_errors)
1652
1793
 
 
1794
    def test_user_from_auth_conf(self):
 
1795
        if self._testing_pycurl():
 
1796
            raise tests.TestNotApplicable(
 
1797
                'pycurl does not support authentication.conf')
 
1798
        user = 'joe'
 
1799
        password = 'foo'
 
1800
        self.server.add_user(user, password)
 
1801
        _setup_authentication_config(scheme='http', port=self.server.port,
 
1802
                                     user=user, password=password)
 
1803
        t = self.get_user_transport(None, None)
 
1804
        # Issue a request to the server to connect
 
1805
        self.assertEqual('contents of a\n', t.get('a').read())
 
1806
        # Only one 'Authentication Required' error should occur
 
1807
        self.assertEqual(1, self.server.auth_required_errors)
 
1808
 
 
1809
    def test_no_credential_leaks_in_log(self):
 
1810
        self.overrideAttr(debug, 'debug_flags', set(['http']))
 
1811
        user = 'joe'
 
1812
        password = 'very-sensitive-password'
 
1813
        self.server.add_user(user, password)
 
1814
        t = self.get_user_transport(user, password)
 
1815
        # Capture the debug calls to mutter
 
1816
        self.mutters = []
 
1817
        def mutter(*args):
 
1818
            lines = args[0] % args[1:]
 
1819
            # Some calls output multiple lines, just split them now since we
 
1820
            # care about a single one later.
 
1821
            self.mutters.extend(lines.splitlines())
 
1822
        self.overrideAttr(trace, 'mutter', mutter)
 
1823
        # Issue a request to the server to connect
 
1824
        self.assertEqual(True, t.has('a'))
 
1825
        # Only one 'Authentication Required' error should occur
 
1826
        self.assertEqual(1, self.server.auth_required_errors)
 
1827
        # Since the authentification succeeded, there should be a corresponding
 
1828
        # debug line
 
1829
        sent_auth_headers = [line for line in self.mutters
 
1830
                             if line.startswith('> %s' % (self._auth_header,))]
 
1831
        self.assertLength(1, sent_auth_headers)
 
1832
        self.assertStartsWith(sent_auth_headers[0],
 
1833
                              '> %s: <masked>' % (self._auth_header,))
1653
1834
 
1654
1835
 
1655
1836
class TestProxyAuth(TestAuth):
1656
 
    """Test proxy authentication schemes."""
1657
 
 
1658
 
    _auth_header = 'Proxy-authorization'
1659
 
    _password_prompt_prefix = 'Proxy '
1660
 
    _username_prompt_prefix = 'Proxy '
 
1837
    """Test proxy authentication schemes.
 
1838
 
 
1839
    This inherits from TestAuth to tweak the setUp and filter some failing
 
1840
    tests.
 
1841
    """
 
1842
 
 
1843
    scenarios = multiply_scenarios(
 
1844
        vary_by_http_client_implementation(),
 
1845
        vary_by_http_protocol_version(),
 
1846
        vary_by_http_proxy_auth_scheme(),
 
1847
        )
1661
1848
 
1662
1849
    def setUp(self):
1663
1850
        super(TestProxyAuth, self).setUp()
1664
 
        self._old_env = {}
1665
 
        self.addCleanup(self._restore_env)
1666
1851
        # Override the contents to avoid false positives
1667
1852
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
1668
1853
                                  ('b', 'not proxied contents of b\n'),
1671
1856
                                  ])
1672
1857
 
1673
1858
    def get_user_transport(self, user, password):
1674
 
        self._install_env({'all_proxy': self.get_user_url(user, password)})
 
1859
        self.overrideEnv('all_proxy', self.get_user_url(user, password))
1675
1860
        return TestAuth.get_user_transport(self, user, password)
1676
1861
 
1677
 
    def _install_env(self, env):
1678
 
        for name, value in env.iteritems():
1679
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1680
 
 
1681
 
    def _restore_env(self):
1682
 
        for name, value in self._old_env.iteritems():
1683
 
            osutils.set_or_unset_env(name, value)
1684
 
 
1685
1862
    def test_empty_pass(self):
1686
1863
        if self._testing_pycurl():
1687
1864
            import pycurl
1688
1865
            if pycurl.version_info()[1] < '7.16.0':
1689
 
                raise tests.KnownFailure(
 
1866
                self.knownFailure(
1690
1867
                    'pycurl < 7.16.0 does not handle empty proxy passwords')
1691
1868
        super(TestProxyAuth, self).test_empty_pass()
1692
1869
 
1716
1893
 
1717
1894
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1718
1895
 
 
1896
    scenarios = multiply_scenarios(
 
1897
        vary_by_http_client_implementation(),
 
1898
        vary_by_http_protocol_version(),
 
1899
        )
 
1900
 
1719
1901
    def setUp(self):
1720
1902
        super(SmartHTTPTunnellingTest, self).setUp()
1721
1903
        # We use the VFS layer as part of HTTP tunnelling tests.
1722
 
        self._captureVar('BZR_NO_SMART_VFS', None)
 
1904
        self.overrideEnv('BZR_NO_SMART_VFS', None)
1723
1905
        self.transport_readonly_server = http_utils.HTTPServerWithSmarts
1724
1906
        self.http_server = self.get_readonly_server()
1725
1907
 
1729
1911
        server._url_protocol = self._url_protocol
1730
1912
        return server
1731
1913
 
1732
 
    def test_open_bzrdir(self):
 
1914
    def test_open_controldir(self):
1733
1915
        branch = self.make_branch('relpath')
1734
1916
        url = self.http_server.get_url() + 'relpath'
1735
 
        bd = bzrdir.BzrDir.open(url)
 
1917
        bd = controldir.ControlDir.open(url)
1736
1918
        self.addCleanup(bd.transport.disconnect)
1737
1919
        self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1738
1920
 
1741
1923
        # The 'readv' command in the smart protocol both sends and receives
1742
1924
        # bulk data, so we use that.
1743
1925
        self.build_tree(['data-file'])
1744
 
        http_transport = transport.get_transport(self.http_server.get_url())
 
1926
        http_transport = transport.get_transport_from_url(
 
1927
            self.http_server.get_url())
1745
1928
        medium = http_transport.get_smart_medium()
1746
1929
        # Since we provide the medium, the url below will be mostly ignored
1747
1930
        # during the test, as long as the path is '/'.
1755
1938
        post_body = 'hello\n'
1756
1939
        expected_reply_body = 'ok\x012\n'
1757
1940
 
1758
 
        http_transport = transport.get_transport(self.http_server.get_url())
 
1941
        http_transport = transport.get_transport_from_url(
 
1942
            self.http_server.get_url())
1759
1943
        medium = http_transport.get_smart_medium()
1760
1944
        response = medium.send_http_smart_request(post_body)
1761
1945
        reply_body = response.read()
1810
1994
 
1811
1995
class Test_redirected_to(tests.TestCase):
1812
1996
 
 
1997
    scenarios = vary_by_http_client_implementation()
 
1998
 
1813
1999
    def test_redirected_to_subdir(self):
1814
2000
        t = self._transport('http://www.example.com/foo')
1815
2001
        r = t._redirected_to('http://www.example.com/foo',
1817
2003
        self.assertIsInstance(r, type(t))
1818
2004
        # Both transports share the some connection
1819
2005
        self.assertEqual(t._get_connection(), r._get_connection())
 
2006
        self.assertEquals('http://www.example.com/foo/subdir/', r.base)
1820
2007
 
1821
2008
    def test_redirected_to_self_with_slash(self):
1822
2009
        t = self._transport('http://www.example.com/foo')
1833
2020
        r = t._redirected_to('http://www.example.com/foo',
1834
2021
                             'http://foo.example.com/foo/subdir')
1835
2022
        self.assertIsInstance(r, type(t))
 
2023
        self.assertEquals('http://foo.example.com/foo/subdir/',
 
2024
            r.external_url())
1836
2025
 
1837
2026
    def test_redirected_to_same_host_sibling_protocol(self):
1838
2027
        t = self._transport('http://www.example.com/foo')
1839
2028
        r = t._redirected_to('http://www.example.com/foo',
1840
2029
                             'https://www.example.com/foo')
1841
2030
        self.assertIsInstance(r, type(t))
 
2031
        self.assertEquals('https://www.example.com/foo/',
 
2032
            r.external_url())
1842
2033
 
1843
2034
    def test_redirected_to_same_host_different_protocol(self):
1844
2035
        t = self._transport('http://www.example.com/foo')
1845
2036
        r = t._redirected_to('http://www.example.com/foo',
1846
2037
                             'ftp://www.example.com/foo')
1847
2038
        self.assertNotEquals(type(r), type(t))
 
2039
        self.assertEquals('ftp://www.example.com/foo/', r.external_url())
 
2040
 
 
2041
    def test_redirected_to_same_host_specific_implementation(self):
 
2042
        t = self._transport('http://www.example.com/foo')
 
2043
        r = t._redirected_to('http://www.example.com/foo',
 
2044
                             'https+urllib://www.example.com/foo')
 
2045
        self.assertEquals('https://www.example.com/foo/', r.external_url())
1848
2046
 
1849
2047
    def test_redirected_to_different_host_same_user(self):
1850
2048
        t = self._transport('http://joe@www.example.com/foo')
1851
2049
        r = t._redirected_to('http://www.example.com/foo',
1852
2050
                             'https://foo.example.com/foo')
1853
2051
        self.assertIsInstance(r, type(t))
1854
 
        self.assertEqual(t._user, r._user)
 
2052
        self.assertEqual(t._parsed_url.user, r._parsed_url.user)
 
2053
        self.assertEquals('https://joe@foo.example.com/foo/', r.external_url())
1855
2054
 
1856
2055
 
1857
2056
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
1910
2109
    pass
1911
2110
 
1912
2111
 
1913
 
if tests.HTTPSServerFeature.available():
 
2112
if features.HTTPSServerFeature.available():
1914
2113
    from bzrlib.tests import https_server
1915
2114
    class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
1916
2115
        pass
1927
2126
        tests.TestCase.setUp(self)
1928
2127
        self.server = self._activity_server(self._protocol_version)
1929
2128
        self.server.start_server()
1930
 
        self.activities = {}
 
2129
        self.addCleanup(self.server.stop_server)
 
2130
        _activities = {} # Don't close over self and create a cycle
1931
2131
        def report_activity(t, bytes, direction):
1932
 
            count = self.activities.get(direction, 0)
 
2132
            count = _activities.get(direction, 0)
1933
2133
            count += bytes
1934
 
            self.activities[direction] = count
1935
 
 
 
2134
            _activities[direction] = count
 
2135
        self.activities = _activities
1936
2136
        # We override at class level because constructors may propagate the
1937
2137
        # bound method and render instance overriding ineffective (an
1938
2138
        # alternative would be to define a specific ui factory instead...)
1939
2139
        self.overrideAttr(self._transport, '_report_activity', report_activity)
1940
 
        self.addCleanup(self.server.stop_server)
1941
2140
 
1942
2141
    def get_transport(self):
1943
2142
        t = self._transport(self.server.get_url())
2061
2260
 
2062
2261
class TestActivity(tests.TestCase, TestActivityMixin):
2063
2262
 
 
2263
    scenarios = multiply_scenarios(
 
2264
        vary_by_http_activity(),
 
2265
        vary_by_http_protocol_version(),
 
2266
        )
 
2267
 
2064
2268
    def setUp(self):
2065
2269
        TestActivityMixin.setUp(self)
2066
2270
 
2087
2291
class TestAuthOnRedirected(http_utils.TestCaseWithRedirectedWebserver):
2088
2292
    """Test authentication on the redirected http server."""
2089
2293
 
 
2294
    scenarios = vary_by_http_protocol_version()
 
2295
 
2090
2296
    _auth_header = 'Authorization'
2091
2297
    _password_prompt_prefix = ''
2092
2298
    _username_prompt_prefix = ''
2155
2361
        # stdout should be empty, stderr will contains the prompts
2156
2362
        self.assertEqual('', stdout.getvalue())
2157
2363
 
2158