~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Martin Pool
  • Date: 2009-08-14 12:08:08 UTC
  • mto: This revision was merged to the branch mainline in revision 4614.
  • Revision ID: mbp@sourcefrog.net-20090814120808-4gvx7fhg44z29cj3
Use platform(aliased=1)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008, 2009 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
# FIXME: This test should be repeated for each available http client
18
 
# implementation; at the moment we have urllib and pycurl.
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Tests for HTTP implementations.
 
18
 
 
19
This module defines a load_tests() method that parametrize tests classes for
 
20
transport implementation, http protocol versions and authentication schemes.
 
21
"""
19
22
 
20
23
# TODO: Should be renamed to bzrlib.transport.http.tests?
21
24
# TODO: What about renaming to bzrlib.tests.transport.http ?
22
25
 
23
26
from cStringIO import StringIO
 
27
import httplib
24
28
import os
25
29
import select
 
30
import SimpleHTTPServer
26
31
import socket
27
32
import sys
28
33
import threading
29
34
 
30
35
import bzrlib
31
36
from bzrlib import (
 
37
    bzrdir,
 
38
    config,
32
39
    errors,
33
40
    osutils,
 
41
    remote as _mod_remote,
 
42
    tests,
 
43
    transport,
34
44
    ui,
35
45
    urlutils,
36
46
    )
 
47
from bzrlib.symbol_versioning import (
 
48
    deprecated_in,
 
49
    )
37
50
from bzrlib.tests import (
38
 
    TestCase,
39
 
    TestUIFactory,
40
 
    TestSkipped,
41
 
    StringIOWrapper,
42
 
    )
43
 
from bzrlib.tests.HttpServer import (
44
 
    HttpServer,
45
 
    HttpServer_PyCurl,
46
 
    HttpServer_urllib,
47
 
    )
48
 
from bzrlib.tests.HTTPTestUtil import (
49
 
    BadProtocolRequestHandler,
50
 
    BadStatusRequestHandler,
51
 
    ForbiddenRequestHandler,
52
 
    HTTPBasicAuthServer,
53
 
    HTTPDigestAuthServer,
54
 
    HTTPServerRedirecting,
55
 
    InvalidStatusRequestHandler,
56
 
    LimitedRangeHTTPServer,
57
 
    NoRangeRequestHandler,
58
 
    ProxyBasicAuthServer,
59
 
    ProxyDigestAuthServer,
60
 
    ProxyServer,
61
 
    SingleRangeRequestHandler,
62
 
    SingleOnlyRangeRequestHandler,
63
 
    TestCaseWithRedirectedWebserver,
64
 
    TestCaseWithTwoWebservers,
65
 
    TestCaseWithWebserver,
66
 
    WallRequestHandler,
 
51
    http_server,
 
52
    http_utils,
67
53
    )
68
54
from bzrlib.transport import (
69
 
    _CoalescedOffset,
70
 
    do_catching_redirections,
71
 
    get_transport,
72
 
    Transport,
 
55
    http,
 
56
    remote,
73
57
    )
74
58
from bzrlib.transport.http import (
75
 
    extract_auth,
76
 
    HttpTransportBase,
 
59
    _urllib,
77
60
    _urllib2_wrappers,
78
61
    )
79
 
from bzrlib.transport.http._urllib import HttpTransport_urllib
80
 
from bzrlib.transport.http._urllib2_wrappers import (
81
 
    PasswordManager,
82
 
    ProxyHandler,
83
 
    Request,
84
 
    )
 
62
 
 
63
 
 
64
try:
 
65
    from bzrlib.transport.http._pycurl import PyCurlTransport
 
66
    pycurl_present = True
 
67
except errors.DependencyNotPresent:
 
68
    pycurl_present = False
 
69
 
 
70
 
 
71
def load_tests(standard_tests, module, loader):
 
72
    """Multiply tests for http clients and protocol versions."""
 
73
    result = loader.suiteClass()
 
74
 
 
75
    # one for each transport implementation
 
76
    t_tests, remaining_tests = tests.split_suite_by_condition(
 
77
        standard_tests, tests.condition_isinstance((
 
78
                TestHttpTransportRegistration,
 
79
                TestHttpTransportUrls,
 
80
                Test_redirected_to,
 
81
                )))
 
82
    transport_scenarios = [
 
83
        ('urllib', dict(_transport=_urllib.HttpTransport_urllib,
 
84
                        _server=http_server.HttpServer_urllib,
 
85
                        _qualified_prefix='http+urllib',)),
 
86
        ]
 
87
    if pycurl_present:
 
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)
 
93
 
 
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,
 
99
                TestHTTPConnections,
 
100
                TestHTTPRedirections,
 
101
                TestHTTPSilentRedirections,
 
102
                TestLimitedRangeRequestServer,
 
103
                TestPost,
 
104
                TestProxyHttpServer,
 
105
                TestRanges,
 
106
                TestSpecificRequestHandler,
 
107
                )))
 
108
    protocol_scenarios = [
 
109
            ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
 
110
            ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
 
111
            ]
 
112
    tp_scenarios = tests.multiply_scenarios(transport_scenarios,
 
113
                                            protocol_scenarios)
 
114
    tests.multiply_tests(tp_tests, tp_scenarios, result)
 
115
 
 
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((
 
119
                TestProxyAuth,
 
120
                )))
 
121
    proxy_auth_scheme_scenarios = [
 
122
        ('basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
 
123
        ('digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
 
124
        ('basicdigest',
 
125
         dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
 
126
        ]
 
127
    tppa_scenarios = tests.multiply_scenarios(tp_scenarios,
 
128
                                              proxy_auth_scheme_scenarios)
 
129
    tests.multiply_tests(tppa_tests, tppa_scenarios, result)
 
130
 
 
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((
 
134
                TestAuth,
 
135
                )))
 
136
    auth_scheme_scenarios = [
 
137
        ('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
 
138
        ('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
 
139
        ('basicdigest',
 
140
         dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
 
141
        ]
 
142
    tpa_scenarios = tests.multiply_scenarios(tp_scenarios,
 
143
                                             auth_scheme_scenarios)
 
144
    tests.multiply_tests(tpa_tests, tpa_scenarios, result)
 
145
 
 
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((
 
149
                TestActivity,
 
150
                )))
 
151
    activity_scenarios = [
 
152
        ('urllib,http', dict(_activity_server=ActivityHTTPServer,
 
153
                             _transport=_urllib.HttpTransport_urllib,)),
 
154
        ]
 
155
    if tests.HTTPSServerFeature.available():
 
156
        activity_scenarios.append(
 
157
            ('urllib,https', dict(_activity_server=ActivityHTTPSServer,
 
158
                                  _transport=_urllib.HttpTransport_urllib,)),)
 
159
    if pycurl_present:
 
160
        activity_scenarios.append(
 
161
            ('pycurl,http', dict(_activity_server=ActivityHTTPServer,
 
162
                                 _transport=PyCurlTransport,)),)
 
163
        if tests.HTTPSServerFeature.available():
 
164
            from bzrlib.tests import (
 
165
                ssl_certs,
 
166
                )
 
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):
 
172
 
 
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'))
 
177
 
 
178
            activity_scenarios.append(
 
179
                ('pycurl,https', dict(_activity_server=ActivityHTTPSServer,
 
180
                                      _transport=HTTPS_pycurl_transport,)),)
 
181
 
 
182
    tpact_scenarios = tests.multiply_scenarios(activity_scenarios,
 
183
                                               protocol_scenarios)
 
184
    tests.multiply_tests(tpact_tests, tpact_scenarios, result)
 
185
 
 
186
    # No parametrization for the remaining tests
 
187
    result.addTests(remaining_tests)
 
188
 
 
189
    return result
85
190
 
86
191
 
87
192
class FakeManager(object):
95
200
 
96
201
class RecordingServer(object):
97
202
    """A fake HTTP server.
98
 
    
 
203
 
99
204
    It records the bytes sent to it, and replies with a 200.
100
205
    """
101
206
 
150
255
        self.port = None
151
256
 
152
257
 
 
258
class TestAuthHeader(tests.TestCase):
 
259
 
 
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)
 
265
 
 
266
    def test_empty_header(self):
 
267
        scheme, remainder = self.parse_header('')
 
268
        self.assertEquals('', scheme)
 
269
        self.assertIs(None, remainder)
 
270
 
 
271
    def test_negotiate_header(self):
 
272
        scheme, remainder = self.parse_header('Negotiate')
 
273
        self.assertEquals('negotiate', scheme)
 
274
        self.assertIs(None, remainder)
 
275
 
 
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)
 
281
 
 
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)
 
289
 
 
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)
 
295
 
 
296
 
 
297
class TestHTTPServer(tests.TestCase):
 
298
    """Test the HTTP servers implementations."""
 
299
 
 
300
    def test_invalid_protocol(self):
 
301
        class BogusRequestHandler(http_server.TestingHTTPRequestHandler):
 
302
 
 
303
            protocol_version = 'HTTP/0.1'
 
304
 
 
305
        server = http_server.HttpServer(BogusRequestHandler)
 
306
        try:
 
307
            self.assertRaises(httplib.UnknownProtocol,server.setUp)
 
308
        except:
 
309
            server.tearDown()
 
310
            self.fail('HTTP Server creation did not raise UnknownProtocol')
 
311
 
 
312
    def test_force_invalid_protocol(self):
 
313
        server = http_server.HttpServer(protocol_version='HTTP/0.1')
 
314
        try:
 
315
            self.assertRaises(httplib.UnknownProtocol,server.setUp)
 
316
        except:
 
317
            server.tearDown()
 
318
            self.fail('HTTP Server creation did not raise UnknownProtocol')
 
319
 
 
320
    def test_server_start_and_stop(self):
 
321
        server = http_server.HttpServer()
 
322
        server.setUp()
 
323
        self.assertTrue(server._http_running)
 
324
        server.tearDown()
 
325
        self.assertFalse(server._http_running)
 
326
 
 
327
    def test_create_http_server_one_zero(self):
 
328
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
 
329
 
 
330
            protocol_version = 'HTTP/1.0'
 
331
 
 
332
        server = http_server.HttpServer(RequestHandlerOneZero)
 
333
        server.setUp()
 
334
        self.addCleanup(server.tearDown)
 
335
        self.assertIsInstance(server._httpd, http_server.TestingHTTPServer)
 
336
 
 
337
    def test_create_http_server_one_one(self):
 
338
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
 
339
 
 
340
            protocol_version = 'HTTP/1.1'
 
341
 
 
342
        server = http_server.HttpServer(RequestHandlerOneOne)
 
343
        server.setUp()
 
344
        self.addCleanup(server.tearDown)
 
345
        self.assertIsInstance(server._httpd,
 
346
                              http_server.TestingThreadingHTTPServer)
 
347
 
 
348
    def test_create_http_server_force_one_one(self):
 
349
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
 
350
 
 
351
            protocol_version = 'HTTP/1.0'
 
352
 
 
353
        server = http_server.HttpServer(RequestHandlerOneZero,
 
354
                                        protocol_version='HTTP/1.1')
 
355
        server.setUp()
 
356
        self.addCleanup(server.tearDown)
 
357
        self.assertIsInstance(server._httpd,
 
358
                              http_server.TestingThreadingHTTPServer)
 
359
 
 
360
    def test_create_http_server_force_one_zero(self):
 
361
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
 
362
 
 
363
            protocol_version = 'HTTP/1.1'
 
364
 
 
365
        server = http_server.HttpServer(RequestHandlerOneOne,
 
366
                                        protocol_version='HTTP/1.0')
 
367
        server.setUp()
 
368
        self.addCleanup(server.tearDown)
 
369
        self.assertIsInstance(server._httpd,
 
370
                              http_server.TestingHTTPServer)
 
371
 
 
372
 
153
373
class TestWithTransport_pycurl(object):
154
374
    """Test case to inherit from if pycurl is present"""
155
375
 
158
378
            from bzrlib.transport.http._pycurl import PyCurlTransport
159
379
            return PyCurlTransport
160
380
        except errors.DependencyNotPresent:
161
 
            raise TestSkipped('pycurl not present')
 
381
            raise tests.TestSkipped('pycurl not present')
162
382
 
163
383
    _transport = property(_get_pycurl_maybe)
164
384
 
165
385
 
166
 
class TestHttpUrls(TestCase):
 
386
class TestHttpUrls(tests.TestCase):
167
387
 
168
388
    # TODO: This should be moved to authorization tests once they
169
389
    # are written.
170
390
 
171
391
    def test_url_parsing(self):
172
392
        f = FakeManager()
173
 
        url = extract_auth('http://example.com', f)
 
393
        url = http.extract_auth('http://example.com', f)
174
394
        self.assertEquals('http://example.com', url)
175
395
        self.assertEquals(0, len(f.credentials))
176
 
        url = extract_auth('http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
 
396
        url = http.extract_auth(
 
397
            'http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
177
398
        self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
178
399
        self.assertEquals(1, len(f.credentials))
179
400
        self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
180
401
                          f.credentials[0])
181
402
 
182
403
 
183
 
class TestHttpTransportUrls(object):
184
 
    """Test the http urls.
185
 
 
186
 
    This MUST be used by daughter classes that also inherit from
187
 
    TestCase.
188
 
 
189
 
    We can't inherit directly from TestCase or the
190
 
    test framework will try to create an instance which cannot
191
 
    run, its implementation being incomplete.
192
 
    """
 
404
class TestHttpTransportUrls(tests.TestCase):
 
405
    """Test the http urls."""
193
406
 
194
407
    def test_abs_url(self):
195
408
        """Construction of absolute http URLs"""
204
417
    def test_invalid_http_urls(self):
205
418
        """Trap invalid construction of urls"""
206
419
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
207
 
        self.assertRaises(ValueError, t.abspath, '.bzr/')
208
 
        t = self._transport('http://http://bazaar-vcs.org/bzr/bzr.dev/')
209
 
        self.assertRaises((errors.InvalidURL, errors.ConnectionError),
210
 
                          t.has, 'foo/bar')
 
420
        self.assertRaises(errors.InvalidURL,
 
421
                          self._transport,
 
422
                          'http://http://bazaar-vcs.org/bzr/bzr.dev/')
211
423
 
212
424
    def test_http_root_urls(self):
213
425
        """Construction of URLs from server root"""
227
439
            server.tearDown()
228
440
 
229
441
 
230
 
class TestHttpUrls_urllib(TestHttpTransportUrls, TestCase):
231
 
    """Test http urls with urllib"""
232
 
 
233
 
    _transport = HttpTransport_urllib
234
 
    _server = HttpServer_urllib
235
 
    _qualified_prefix = 'http+urllib'
236
 
 
237
 
 
238
 
class TestHttpUrls_pycurl(TestWithTransport_pycurl, TestHttpTransportUrls,
239
 
                          TestCase):
240
 
    """Test http urls with pycurl"""
241
 
 
242
 
    _server = HttpServer_PyCurl
243
 
    _qualified_prefix = 'http+pycurl'
 
442
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
244
443
 
245
444
    # TODO: This should really be moved into another pycurl
246
445
    # specific test. When https tests will be implemented, take
255
454
        try:
256
455
            import pycurl
257
456
        except ImportError:
258
 
            raise TestSkipped('pycurl not present')
259
 
        # Now that we have pycurl imported, we can fake its version_info
260
 
        # This was taken from a windows pycurl without SSL
261
 
        # (thanks to bialix)
262
 
        pycurl.version_info = lambda : (2,
263
 
                                        '7.13.2',
264
 
                                        462082,
265
 
                                        'i386-pc-win32',
266
 
                                        2576,
267
 
                                        None,
268
 
                                        0,
269
 
                                        None,
270
 
                                        ('ftp', 'gopher', 'telnet',
271
 
                                         'dict', 'ldap', 'http', 'file'),
272
 
                                        None,
273
 
                                        0,
274
 
                                        None)
275
 
        self.assertRaises(errors.DependencyNotPresent, self._transport,
276
 
                          'https://launchpad.net')
277
 
 
278
 
class TestHttpConnections(object):
279
 
    """Test the http connections.
280
 
 
281
 
    This MUST be used by daughter classes that also inherit from
282
 
    TestCaseWithWebserver.
283
 
 
284
 
    We can't inherit directly from TestCaseWithWebserver or the
285
 
    test framework will try to create an instance which cannot
286
 
    run, its implementation being incomplete.
287
 
    """
 
457
            raise tests.TestSkipped('pycurl not present')
 
458
 
 
459
        version_info_orig = pycurl.version_info
 
460
        try:
 
461
            # Now that we have pycurl imported, we can fake its version_info
 
462
            # This was taken from a windows pycurl without SSL
 
463
            # (thanks to bialix)
 
464
            pycurl.version_info = lambda : (2,
 
465
                                            '7.13.2',
 
466
                                            462082,
 
467
                                            'i386-pc-win32',
 
468
                                            2576,
 
469
                                            None,
 
470
                                            0,
 
471
                                            None,
 
472
                                            ('ftp', 'gopher', 'telnet',
 
473
                                             'dict', 'ldap', 'http', 'file'),
 
474
                                            None,
 
475
                                            0,
 
476
                                            None)
 
477
            self.assertRaises(errors.DependencyNotPresent, self._transport,
 
478
                              'https://launchpad.net')
 
479
        finally:
 
480
            # Restore the right function
 
481
            pycurl.version_info = version_info_orig
 
482
 
 
483
 
 
484
class TestHTTPConnections(http_utils.TestCaseWithWebserver):
 
485
    """Test the http connections."""
288
486
 
289
487
    def setUp(self):
290
 
        TestCaseWithWebserver.setUp(self)
291
 
        self.build_tree(['xxx', 'foo/', 'foo/bar'], line_endings='binary',
 
488
        http_utils.TestCaseWithWebserver.setUp(self)
 
489
        self.build_tree(['foo/', 'foo/bar'], line_endings='binary',
292
490
                        transport=self.get_transport())
293
491
 
294
492
    def test_http_has(self):
318
516
            '"GET /foo/bar HTTP/1.1" 200 - "-" "bzr/%s'
319
517
            % bzrlib.__version__) > -1)
320
518
 
321
 
    def test_get_smart_medium(self):
322
 
        # For HTTP, get_smart_medium should return the transport object.
323
 
        server = self.get_readonly_server()
324
 
        http_transport = self._transport(server.get_url())
325
 
        medium = http_transport.get_smart_medium()
326
 
        self.assertIs(medium, http_transport)
327
 
 
328
519
    def test_has_on_bogus_host(self):
329
520
        # Get a free address and don't 'accept' on it, so that we
330
521
        # can be sure there is no http handler there, but set a
340
531
            socket.setdefaulttimeout(default_timeout)
341
532
 
342
533
 
343
 
class TestHttpConnections_urllib(TestHttpConnections, TestCaseWithWebserver):
344
 
    """Test http connections with urllib"""
345
 
 
346
 
    _transport = HttpTransport_urllib
347
 
 
348
 
 
349
 
 
350
 
class TestHttpConnections_pycurl(TestWithTransport_pycurl,
351
 
                                 TestHttpConnections,
352
 
                                 TestCaseWithWebserver):
353
 
    """Test http connections with pycurl"""
354
 
 
355
 
 
356
 
class TestHttpTransportRegistration(TestCase):
 
534
class TestHttpTransportRegistration(tests.TestCase):
357
535
    """Test registrations of various http implementations"""
358
536
 
359
537
    def test_http_registered(self):
360
 
        # urlllib should always be present
361
 
        t = get_transport('http+urllib://bzr.google.com/')
362
 
        self.assertIsInstance(t, Transport)
363
 
        self.assertIsInstance(t, HttpTransport_urllib)
364
 
 
365
 
 
366
 
class TestPost(object):
367
 
 
368
 
    def _test_post_body_is_received(self, scheme):
 
538
        t = transport.get_transport('%s://foo.com/' % self._qualified_prefix)
 
539
        self.assertIsInstance(t, transport.Transport)
 
540
        self.assertIsInstance(t, self._transport)
 
541
 
 
542
 
 
543
class TestPost(tests.TestCase):
 
544
 
 
545
    def test_post_body_is_received(self):
369
546
        server = RecordingServer(expect_body_tail='end-of-body')
370
547
        server.setUp()
371
548
        self.addCleanup(server.tearDown)
 
549
        scheme = self._qualified_prefix
372
550
        url = '%s://%s:%s/' % (scheme, server.host, server.port)
373
 
        try:
374
 
            http_transport = get_transport(url)
375
 
        except errors.UnsupportedProtocol:
376
 
            raise TestSkipped('%s not available' % scheme)
 
551
        http_transport = self._transport(url)
377
552
        code, response = http_transport._post('abc def end-of-body')
378
553
        self.assertTrue(
379
554
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
385
560
            server.received_bytes.endswith('\r\n\r\nabc def end-of-body'))
386
561
 
387
562
 
388
 
class TestPost_urllib(TestCase, TestPost):
389
 
    """TestPost for urllib implementation"""
390
 
 
391
 
    _transport = HttpTransport_urllib
392
 
 
393
 
    def test_post_body_is_received_urllib(self):
394
 
        self._test_post_body_is_received('http+urllib')
395
 
 
396
 
 
397
 
class TestPost_pycurl(TestWithTransport_pycurl, TestCase, TestPost):
398
 
    """TestPost for pycurl implementation"""
399
 
 
400
 
    def test_post_body_is_received_pycurl(self):
401
 
        self._test_post_body_is_received('http+pycurl')
402
 
 
403
 
 
404
 
class TestRangeHeader(TestCase):
 
563
class TestRangeHeader(tests.TestCase):
405
564
    """Test range_header method"""
406
565
 
407
566
    def check_header(self, value, ranges=[], tail=0):
408
567
        offsets = [ (start, end - start + 1) for start, end in ranges]
409
 
        coalesce = Transport._coalesce_offsets
 
568
        coalesce = transport.Transport._coalesce_offsets
410
569
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
411
 
        range_header = HttpTransportBase._range_header
 
570
        range_header = http.HttpTransportBase._range_header
412
571
        self.assertEqual(value, range_header(coalesced, tail))
413
572
 
414
573
    def test_range_header_single(self):
429
588
                          tail=50)
430
589
 
431
590
 
432
 
class TestWallServer(object):
 
591
class TestSpecificRequestHandler(http_utils.TestCaseWithWebserver):
 
592
    """Tests a specific request handler.
 
593
 
 
594
    Daughter classes are expected to override _req_handler_class
 
595
    """
 
596
 
 
597
    # Provide a useful default
 
598
    _req_handler_class = http_server.TestingHTTPRequestHandler
 
599
 
 
600
    def create_transport_readonly_server(self):
 
601
        return http_server.HttpServer(self._req_handler_class,
 
602
                                      protocol_version=self._protocol_version)
 
603
 
 
604
    def _testing_pycurl(self):
 
605
        return pycurl_present and self._transport == PyCurlTransport
 
606
 
 
607
 
 
608
class WallRequestHandler(http_server.TestingHTTPRequestHandler):
 
609
    """Whatever request comes in, close the connection"""
 
610
 
 
611
    def handle_one_request(self):
 
612
        """Handle a single HTTP request, by abruptly closing the connection"""
 
613
        self.close_connection = 1
 
614
 
 
615
 
 
616
class TestWallServer(TestSpecificRequestHandler):
433
617
    """Tests exceptions during the connection phase"""
434
618
 
435
 
    def create_transport_readonly_server(self):
436
 
        return HttpServer(WallRequestHandler)
 
619
    _req_handler_class = WallRequestHandler
437
620
 
438
621
    def test_http_has(self):
439
622
        server = self.get_readonly_server()
453
636
                          t.get, 'foo/bar')
454
637
 
455
638
 
456
 
class TestWallServer_urllib(TestWallServer, TestCaseWithWebserver):
457
 
    """Tests "wall" server for urllib implementation"""
458
 
 
459
 
    _transport = HttpTransport_urllib
460
 
 
461
 
 
462
 
class TestWallServer_pycurl(TestWithTransport_pycurl,
463
 
                            TestWallServer,
464
 
                            TestCaseWithWebserver):
465
 
    """Tests "wall" server for pycurl implementation"""
466
 
 
467
 
 
468
 
class TestBadStatusServer(object):
 
639
class BadStatusRequestHandler(http_server.TestingHTTPRequestHandler):
 
640
    """Whatever request comes in, returns a bad status"""
 
641
 
 
642
    def parse_request(self):
 
643
        """Fakes handling a single HTTP request, returns a bad status"""
 
644
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
 
645
        self.send_response(0, "Bad status")
 
646
        self.close_connection = 1
 
647
        return False
 
648
 
 
649
 
 
650
class TestBadStatusServer(TestSpecificRequestHandler):
469
651
    """Tests bad status from server."""
470
652
 
471
 
    def create_transport_readonly_server(self):
472
 
        return HttpServer(BadStatusRequestHandler)
 
653
    _req_handler_class = BadStatusRequestHandler
473
654
 
474
655
    def test_http_has(self):
475
656
        server = self.get_readonly_server()
482
663
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
483
664
 
484
665
 
485
 
class TestBadStatusServer_urllib(TestBadStatusServer, TestCaseWithWebserver):
486
 
    """Tests bad status server for urllib implementation"""
487
 
 
488
 
    _transport = HttpTransport_urllib
489
 
 
490
 
 
491
 
class TestBadStatusServer_pycurl(TestWithTransport_pycurl,
492
 
                                 TestBadStatusServer,
493
 
                                 TestCaseWithWebserver):
494
 
    """Tests bad status server for pycurl implementation"""
 
666
class InvalidStatusRequestHandler(http_server.TestingHTTPRequestHandler):
 
667
    """Whatever request comes in, returns an invalid status"""
 
668
 
 
669
    def parse_request(self):
 
670
        """Fakes handling a single HTTP request, returns a bad status"""
 
671
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
 
672
        self.wfile.write("Invalid status line\r\n")
 
673
        return False
495
674
 
496
675
 
497
676
class TestInvalidStatusServer(TestBadStatusServer):
500
679
    Both implementations raises the same error as for a bad status.
501
680
    """
502
681
 
503
 
    def create_transport_readonly_server(self):
504
 
        return HttpServer(InvalidStatusRequestHandler)
505
 
 
506
 
 
507
 
class TestInvalidStatusServer_urllib(TestInvalidStatusServer,
508
 
                                     TestCaseWithWebserver):
509
 
    """Tests invalid status server for urllib implementation"""
510
 
 
511
 
    _transport = HttpTransport_urllib
512
 
 
513
 
 
514
 
class TestInvalidStatusServer_pycurl(TestWithTransport_pycurl,
515
 
                                     TestInvalidStatusServer,
516
 
                                     TestCaseWithWebserver):
517
 
    """Tests invalid status server for pycurl implementation"""
518
 
 
519
 
 
520
 
class TestBadProtocolServer(object):
 
682
    _req_handler_class = InvalidStatusRequestHandler
 
683
 
 
684
    def test_http_has(self):
 
685
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
 
686
            raise tests.KnownFailure(
 
687
                'pycurl hangs if the server send back garbage')
 
688
        super(TestInvalidStatusServer, self).test_http_has()
 
689
 
 
690
    def test_http_get(self):
 
691
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
 
692
            raise tests.KnownFailure(
 
693
                'pycurl hangs if the server send back garbage')
 
694
        super(TestInvalidStatusServer, self).test_http_get()
 
695
 
 
696
 
 
697
class BadProtocolRequestHandler(http_server.TestingHTTPRequestHandler):
 
698
    """Whatever request comes in, returns a bad protocol version"""
 
699
 
 
700
    def parse_request(self):
 
701
        """Fakes handling a single HTTP request, returns a bad status"""
 
702
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
 
703
        # Returns an invalid protocol version, but curl just
 
704
        # ignores it and those cannot be tested.
 
705
        self.wfile.write("%s %d %s\r\n" % ('HTTP/0.0',
 
706
                                           404,
 
707
                                           'Look at my protocol version'))
 
708
        return False
 
709
 
 
710
 
 
711
class TestBadProtocolServer(TestSpecificRequestHandler):
521
712
    """Tests bad protocol from server."""
522
713
 
523
 
    def create_transport_readonly_server(self):
524
 
        return HttpServer(BadProtocolRequestHandler)
 
714
    _req_handler_class = BadProtocolRequestHandler
 
715
 
 
716
    def setUp(self):
 
717
        if pycurl_present and self._transport == PyCurlTransport:
 
718
            raise tests.TestNotApplicable(
 
719
                "pycurl doesn't check the protocol version")
 
720
        super(TestBadProtocolServer, self).setUp()
525
721
 
526
722
    def test_http_has(self):
527
723
        server = self.get_readonly_server()
534
730
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
535
731
 
536
732
 
537
 
class TestBadProtocolServer_urllib(TestBadProtocolServer,
538
 
                                   TestCaseWithWebserver):
539
 
    """Tests bad protocol server for urllib implementation"""
540
 
 
541
 
    _transport = HttpTransport_urllib
542
 
 
543
 
# curl don't check the protocol version
544
 
#class TestBadProtocolServer_pycurl(TestWithTransport_pycurl,
545
 
#                                   TestBadProtocolServer,
546
 
#                                   TestCaseWithWebserver):
547
 
#    """Tests bad protocol server for pycurl implementation"""
548
 
 
549
 
 
550
 
class TestForbiddenServer(object):
 
733
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
 
734
    """Whatever request comes in, returns a 403 code"""
 
735
 
 
736
    def parse_request(self):
 
737
        """Handle a single HTTP request, by replying we cannot handle it"""
 
738
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
 
739
        self.send_error(403)
 
740
        return False
 
741
 
 
742
 
 
743
class TestForbiddenServer(TestSpecificRequestHandler):
551
744
    """Tests forbidden server"""
552
745
 
553
 
    def create_transport_readonly_server(self):
554
 
        return HttpServer(ForbiddenRequestHandler)
 
746
    _req_handler_class = ForbiddenRequestHandler
555
747
 
556
748
    def test_http_has(self):
557
749
        server = self.get_readonly_server()
564
756
        self.assertRaises(errors.TransportError, t.get, 'foo/bar')
565
757
 
566
758
 
567
 
class TestForbiddenServer_urllib(TestForbiddenServer, TestCaseWithWebserver):
568
 
    """Tests forbidden server for urllib implementation"""
569
 
 
570
 
    _transport = HttpTransport_urllib
571
 
 
572
 
 
573
 
class TestForbiddenServer_pycurl(TestWithTransport_pycurl,
574
 
                                 TestForbiddenServer,
575
 
                                 TestCaseWithWebserver):
576
 
    """Tests forbidden server for pycurl implementation"""
577
 
 
578
 
 
579
 
class TestRecordingServer(TestCase):
 
759
class TestRecordingServer(tests.TestCase):
580
760
 
581
761
    def test_create(self):
582
762
        server = RecordingServer(expect_body_tail=None)
607
787
        self.assertEqual('abc', server.received_bytes)
608
788
 
609
789
 
610
 
class TestRangeRequestServer(object):
 
790
class TestRangeRequestServer(TestSpecificRequestHandler):
611
791
    """Tests readv requests against server.
612
792
 
613
 
    This MUST be used by daughter classes that also inherit from
614
 
    TestCaseWithWebserver.
615
 
 
616
 
    We can't inherit directly from TestCaseWithWebserver or the
617
 
    test framework will try to create an instance which cannot
618
 
    run, its implementation being incomplete.
 
793
    We test against default "normal" server.
619
794
    """
620
795
 
621
796
    def setUp(self):
622
 
        TestCaseWithWebserver.setUp(self)
 
797
        super(TestRangeRequestServer, self).setUp()
623
798
        self.build_tree_contents([('a', '0123456789')],)
624
799
 
625
800
    def test_readv(self):
654
829
        self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
655
830
                              t.readv, 'a', [(12,2)])
656
831
 
 
832
    def test_readv_multiple_get_requests(self):
 
833
        server = self.get_readonly_server()
 
834
        t = self._transport(server.get_url())
 
835
        # force transport to issue multiple requests
 
836
        t._max_readv_combine = 1
 
837
        t._max_get_ranges = 1
 
838
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
 
839
        self.assertEqual(l[0], (0, '0'))
 
840
        self.assertEqual(l[1], (1, '1'))
 
841
        self.assertEqual(l[2], (3, '34'))
 
842
        self.assertEqual(l[3], (9, '9'))
 
843
        # The server should have issued 4 requests
 
844
        self.assertEqual(4, server.GET_request_nb)
 
845
 
 
846
    def test_readv_get_max_size(self):
 
847
        server = self.get_readonly_server()
 
848
        t = self._transport(server.get_url())
 
849
        # force transport to issue multiple requests by limiting the number of
 
850
        # bytes by request. Note that this apply to coalesced offsets only, a
 
851
        # single range will keep its size even if bigger than the limit.
 
852
        t._get_max_size = 2
 
853
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
 
854
        self.assertEqual(l[0], (0, '0'))
 
855
        self.assertEqual(l[1], (1, '1'))
 
856
        self.assertEqual(l[2], (2, '2345'))
 
857
        self.assertEqual(l[3], (6, '6789'))
 
858
        # The server should have issued 3 requests
 
859
        self.assertEqual(3, server.GET_request_nb)
 
860
 
 
861
    def test_complete_readv_leave_pipe_clean(self):
 
862
        server = self.get_readonly_server()
 
863
        t = self._transport(server.get_url())
 
864
        # force transport to issue multiple requests
 
865
        t._get_max_size = 2
 
866
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
 
867
        # The server should have issued 3 requests
 
868
        self.assertEqual(3, server.GET_request_nb)
 
869
        self.assertEqual('0123456789', t.get_bytes('a'))
 
870
        self.assertEqual(4, server.GET_request_nb)
 
871
 
 
872
    def test_incomplete_readv_leave_pipe_clean(self):
 
873
        server = self.get_readonly_server()
 
874
        t = self._transport(server.get_url())
 
875
        # force transport to issue multiple requests
 
876
        t._get_max_size = 2
 
877
        # Don't collapse readv results into a list so that we leave unread
 
878
        # bytes on the socket
 
879
        ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
 
880
        self.assertEqual((0, '0'), ireadv.next())
 
881
        # The server should have issued one request so far
 
882
        self.assertEqual(1, server.GET_request_nb)
 
883
        self.assertEqual('0123456789', t.get_bytes('a'))
 
884
        # get_bytes issued an additional request, the readv pending ones are
 
885
        # lost
 
886
        self.assertEqual(2, server.GET_request_nb)
 
887
 
 
888
 
 
889
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
 
890
    """Always reply to range request as if they were single.
 
891
 
 
892
    Don't be explicit about it, just to annoy the clients.
 
893
    """
 
894
 
 
895
    def get_multiple_ranges(self, file, file_size, ranges):
 
896
        """Answer as if it was a single range request and ignores the rest"""
 
897
        (start, end) = ranges[0]
 
898
        return self.get_single_range(file, file_size, start, end)
 
899
 
657
900
 
658
901
class TestSingleRangeRequestServer(TestRangeRequestServer):
659
902
    """Test readv against a server which accept only single range requests"""
660
903
 
661
 
    def create_transport_readonly_server(self):
662
 
        return HttpServer(SingleRangeRequestHandler)
663
 
 
664
 
 
665
 
class TestSingleRangeRequestServer_urllib(TestSingleRangeRequestServer,
666
 
                                          TestCaseWithWebserver):
667
 
    """Tests single range requests accepting server for urllib implementation"""
668
 
 
669
 
    _transport = HttpTransport_urllib
670
 
 
671
 
 
672
 
class TestSingleRangeRequestServer_pycurl(TestWithTransport_pycurl,
673
 
                                          TestSingleRangeRequestServer,
674
 
                                          TestCaseWithWebserver):
675
 
    """Tests single range requests accepting server for pycurl implementation"""
 
904
    _req_handler_class = SingleRangeRequestHandler
 
905
 
 
906
 
 
907
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
 
908
    """Only reply to simple range requests, errors out on multiple"""
 
909
 
 
910
    def get_multiple_ranges(self, file, file_size, ranges):
 
911
        """Refuses the multiple ranges request"""
 
912
        if len(ranges) > 1:
 
913
            file.close()
 
914
            self.send_error(416, "Requested range not satisfiable")
 
915
            return
 
916
        (start, end) = ranges[0]
 
917
        return self.get_single_range(file, file_size, start, end)
676
918
 
677
919
 
678
920
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
679
921
    """Test readv against a server which only accept single range requests"""
680
922
 
681
 
    def create_transport_readonly_server(self):
682
 
        return HttpServer(SingleOnlyRangeRequestHandler)
683
 
 
684
 
 
685
 
class TestSingleOnlyRangeRequestServer_urllib(TestSingleOnlyRangeRequestServer,
686
 
                                              TestCaseWithWebserver):
687
 
    """Tests single range requests accepting server for urllib implementation"""
688
 
 
689
 
    _transport = HttpTransport_urllib
690
 
 
691
 
 
692
 
class TestSingleOnlyRangeRequestServer_pycurl(TestWithTransport_pycurl,
693
 
                                              TestSingleOnlyRangeRequestServer,
694
 
                                              TestCaseWithWebserver):
695
 
    """Tests single range requests accepting server for pycurl implementation"""
 
923
    _req_handler_class = SingleOnlyRangeRequestHandler
 
924
 
 
925
 
 
926
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
 
927
    """Ignore range requests without notice"""
 
928
 
 
929
    def do_GET(self):
 
930
        # Update the statistics
 
931
        self.server.test_case_server.GET_request_nb += 1
 
932
        # Just bypass the range handling done by TestingHTTPRequestHandler
 
933
        return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
696
934
 
697
935
 
698
936
class TestNoRangeRequestServer(TestRangeRequestServer):
699
937
    """Test readv against a server which do not accept range requests"""
700
938
 
701
 
    def create_transport_readonly_server(self):
702
 
        return HttpServer(NoRangeRequestHandler)
703
 
 
704
 
 
705
 
class TestNoRangeRequestServer_urllib(TestNoRangeRequestServer,
706
 
                                      TestCaseWithWebserver):
707
 
    """Tests range requests refusing server for urllib implementation"""
708
 
 
709
 
    _transport = HttpTransport_urllib
710
 
 
711
 
 
712
 
class TestNoRangeRequestServer_pycurl(TestWithTransport_pycurl,
713
 
                               TestNoRangeRequestServer,
714
 
                               TestCaseWithWebserver):
715
 
    """Tests range requests refusing server for pycurl implementation"""
716
 
 
717
 
 
718
 
class TestLimitedRangeRequestServer(object):
719
 
    """Tests readv requests against server that errors out on too much ranges.
720
 
 
721
 
    This MUST be used by daughter classes that also inherit from
722
 
    TestCaseWithWebserver.
723
 
 
724
 
    We can't inherit directly from TestCaseWithWebserver or the
725
 
    test framework will try to create an instance which cannot
726
 
    run, its implementation being incomplete.
 
939
    _req_handler_class = NoRangeRequestHandler
 
940
 
 
941
 
 
942
class MultipleRangeWithoutContentLengthRequestHandler(
 
943
    http_server.TestingHTTPRequestHandler):
 
944
    """Reply to multiple range requests without content length header."""
 
945
 
 
946
    def get_multiple_ranges(self, file, file_size, ranges):
 
947
        self.send_response(206)
 
948
        self.send_header('Accept-Ranges', 'bytes')
 
949
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
 
950
        self.send_header("Content-Type",
 
951
                         "multipart/byteranges; boundary=%s" % boundary)
 
952
        self.end_headers()
 
953
        for (start, end) in ranges:
 
954
            self.wfile.write("--%s\r\n" % boundary)
 
955
            self.send_header("Content-type", 'application/octet-stream')
 
956
            self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
 
957
                                                                  end,
 
958
                                                                  file_size))
 
959
            self.end_headers()
 
960
            self.send_range_content(file, start, end - start + 1)
 
961
        # Final boundary
 
962
        self.wfile.write("--%s\r\n" % boundary)
 
963
 
 
964
 
 
965
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
 
966
 
 
967
    _req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
 
968
 
 
969
 
 
970
class TruncatedMultipleRangeRequestHandler(
 
971
    http_server.TestingHTTPRequestHandler):
 
972
    """Reply to multiple range requests truncating the last ones.
 
973
 
 
974
    This server generates responses whose Content-Length describes all the
 
975
    ranges, but fail to include the last ones leading to client short reads.
 
976
    This has been observed randomly with lighttpd (bug #179368).
727
977
    """
728
978
 
 
979
    _truncated_ranges = 2
 
980
 
 
981
    def get_multiple_ranges(self, file, file_size, ranges):
 
982
        self.send_response(206)
 
983
        self.send_header('Accept-Ranges', 'bytes')
 
984
        boundary = 'tagada'
 
985
        self.send_header('Content-Type',
 
986
                         'multipart/byteranges; boundary=%s' % boundary)
 
987
        boundary_line = '--%s\r\n' % boundary
 
988
        # Calculate the Content-Length
 
989
        content_length = 0
 
990
        for (start, end) in ranges:
 
991
            content_length += len(boundary_line)
 
992
            content_length += self._header_line_length(
 
993
                'Content-type', 'application/octet-stream')
 
994
            content_length += self._header_line_length(
 
995
                'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
 
996
            content_length += len('\r\n') # end headers
 
997
            content_length += end - start # + 1
 
998
        content_length += len(boundary_line)
 
999
        self.send_header('Content-length', content_length)
 
1000
        self.end_headers()
 
1001
 
 
1002
        # Send the multipart body
 
1003
        cur = 0
 
1004
        for (start, end) in ranges:
 
1005
            self.wfile.write(boundary_line)
 
1006
            self.send_header('Content-type', 'application/octet-stream')
 
1007
            self.send_header('Content-Range', 'bytes %d-%d/%d'
 
1008
                             % (start, end, file_size))
 
1009
            self.end_headers()
 
1010
            if cur + self._truncated_ranges >= len(ranges):
 
1011
                # Abruptly ends the response and close the connection
 
1012
                self.close_connection = 1
 
1013
                return
 
1014
            self.send_range_content(file, start, end - start + 1)
 
1015
            cur += 1
 
1016
        # No final boundary
 
1017
        self.wfile.write(boundary_line)
 
1018
 
 
1019
 
 
1020
class TestTruncatedMultipleRangeServer(TestSpecificRequestHandler):
 
1021
 
 
1022
    _req_handler_class = TruncatedMultipleRangeRequestHandler
 
1023
 
 
1024
    def setUp(self):
 
1025
        super(TestTruncatedMultipleRangeServer, self).setUp()
 
1026
        self.build_tree_contents([('a', '0123456789')],)
 
1027
 
 
1028
    def test_readv_with_short_reads(self):
 
1029
        server = self.get_readonly_server()
 
1030
        t = self._transport(server.get_url())
 
1031
        # Force separate ranges for each offset
 
1032
        t._bytes_to_read_before_seek = 0
 
1033
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
 
1034
        self.assertEqual((0, '0'), ireadv.next())
 
1035
        self.assertEqual((2, '2'), ireadv.next())
 
1036
        if not self._testing_pycurl():
 
1037
            # Only one request have been issued so far (except for pycurl that
 
1038
            # try to read the whole response at once)
 
1039
            self.assertEqual(1, server.GET_request_nb)
 
1040
        self.assertEqual((4, '45'), ireadv.next())
 
1041
        self.assertEqual((9, '9'), ireadv.next())
 
1042
        # Both implementations issue 3 requests but:
 
1043
        # - urllib does two multiple (4 ranges, then 2 ranges) then a single
 
1044
        #   range,
 
1045
        # - pycurl does two multiple (4 ranges, 4 ranges) then a single range
 
1046
        self.assertEqual(3, server.GET_request_nb)
 
1047
        # Finally the client have tried a single range request and stays in
 
1048
        # that mode
 
1049
        self.assertEqual('single', t._range_hint)
 
1050
 
 
1051
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
 
1052
    """Errors out when range specifiers exceed the limit"""
 
1053
 
 
1054
    def get_multiple_ranges(self, file, file_size, ranges):
 
1055
        """Refuses the multiple ranges request"""
 
1056
        tcs = self.server.test_case_server
 
1057
        if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
 
1058
            file.close()
 
1059
            # Emulate apache behavior
 
1060
            self.send_error(400, "Bad Request")
 
1061
            return
 
1062
        return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
 
1063
            self, file, file_size, ranges)
 
1064
 
 
1065
 
 
1066
class LimitedRangeHTTPServer(http_server.HttpServer):
 
1067
    """An HttpServer erroring out on requests with too much range specifiers"""
 
1068
 
 
1069
    def __init__(self, request_handler=LimitedRangeRequestHandler,
 
1070
                 protocol_version=None,
 
1071
                 range_limit=None):
 
1072
        http_server.HttpServer.__init__(self, request_handler,
 
1073
                                        protocol_version=protocol_version)
 
1074
        self.range_limit = range_limit
 
1075
 
 
1076
 
 
1077
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
 
1078
    """Tests readv requests against a server erroring out on too much ranges."""
 
1079
 
 
1080
    # Requests with more range specifiers will error out
729
1081
    range_limit = 3
730
1082
 
731
1083
    def create_transport_readonly_server(self):
732
 
        # Requests with more range specifiers will error out
733
 
        return LimitedRangeHTTPServer(range_limit=self.range_limit)
 
1084
        return LimitedRangeHTTPServer(range_limit=self.range_limit,
 
1085
                                      protocol_version=self._protocol_version)
734
1086
 
735
1087
    def get_transport(self):
736
1088
        return self._transport(self.get_readonly_server().get_url())
737
1089
 
738
1090
    def setUp(self):
739
 
        TestCaseWithWebserver.setUp(self)
 
1091
        http_utils.TestCaseWithWebserver.setUp(self)
740
1092
        # We need to manipulate ranges that correspond to real chunks in the
741
1093
        # response, so we build a content appropriately.
742
 
        filler = ''.join(['abcdefghij' for _ in range(102)])
 
1094
        filler = ''.join(['abcdefghij' for x in range(102)])
743
1095
        content = ''.join(['%04d' % v + filler for v in range(16)])
744
1096
        self.build_tree_contents([('a', content)],)
745
1097
 
750
1102
        self.assertEqual(l[1], (1024, '0001'))
751
1103
        self.assertEqual(1, self.get_readonly_server().GET_request_nb)
752
1104
 
753
 
    def test_a_lot_of_ranges(self):
 
1105
    def test_more_ranges(self):
754
1106
        t = self.get_transport()
755
1107
        l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
756
1108
        self.assertEqual(l[0], (0, '0000'))
758
1110
        self.assertEqual(l[2], (4096, '0004'))
759
1111
        self.assertEqual(l[3], (8192, '0008'))
760
1112
        # The server will refuse to serve the first request (too much ranges),
761
 
        # a second request will succeeds.
 
1113
        # a second request will succeed.
762
1114
        self.assertEqual(2, self.get_readonly_server().GET_request_nb)
763
1115
 
764
1116
 
765
 
class TestLimitedRangeRequestServer_urllib(TestLimitedRangeRequestServer,
766
 
                                          TestCaseWithWebserver):
767
 
    """Tests limited range requests server for urllib implementation"""
768
 
 
769
 
    _transport = HttpTransport_urllib
770
 
 
771
 
 
772
 
class TestLimitedRangeRequestServer_pycurl(TestWithTransport_pycurl,
773
 
                                          TestLimitedRangeRequestServer,
774
 
                                          TestCaseWithWebserver):
775
 
    """Tests limited range requests server for pycurl implementation"""
776
 
 
777
 
 
778
 
 
779
 
class TestHttpProxyWhiteBox(TestCase):
 
1117
class TestHttpProxyWhiteBox(tests.TestCase):
780
1118
    """Whitebox test proxy http authorization.
781
1119
 
782
1120
    Only the urllib implementation is tested here.
783
1121
    """
784
1122
 
785
1123
    def setUp(self):
786
 
        TestCase.setUp(self)
 
1124
        tests.TestCase.setUp(self)
787
1125
        self._old_env = {}
788
1126
 
789
1127
    def tearDown(self):
790
1128
        self._restore_env()
 
1129
        tests.TestCase.tearDown(self)
791
1130
 
792
1131
    def _install_env(self, env):
793
1132
        for name, value in env.iteritems():
798
1137
            osutils.set_or_unset_env(name, value)
799
1138
 
800
1139
    def _proxied_request(self):
801
 
        handler = ProxyHandler(PasswordManager())
802
 
        request = Request('GET','http://baz/buzzle')
 
1140
        handler = _urllib2_wrappers.ProxyHandler()
 
1141
        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
803
1142
        handler.set_proxy(request, 'http')
804
1143
        return request
805
1144
 
814
1153
        self.assertRaises(errors.InvalidURL, self._proxied_request)
815
1154
 
816
1155
 
817
 
class TestProxyHttpServer(object):
 
1156
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
818
1157
    """Tests proxy server.
819
1158
 
820
 
    This MUST be used by daughter classes that also inherit from
821
 
    TestCaseWithTwoWebservers.
822
 
 
823
 
    We can't inherit directly from TestCaseWithTwoWebservers or
824
 
    the test framework will try to create an instance which
825
 
    cannot run, its implementation being incomplete.
826
 
 
827
1159
    Be aware that we do not setup a real proxy here. Instead, we
828
1160
    check that the *connection* goes through the proxy by serving
829
1161
    different content (the faked proxy server append '-proxied'
833
1165
    # FIXME: We don't have an https server available, so we don't
834
1166
    # test https connections.
835
1167
 
836
 
    # FIXME: Once the test suite is better fitted to test
837
 
    # authorization schemes, test proxy authorizations too (see
838
 
    # bug #83954).
839
 
 
840
1168
    def setUp(self):
841
 
        TestCaseWithTwoWebservers.setUp(self)
 
1169
        super(TestProxyHttpServer, self).setUp()
842
1170
        self.build_tree_contents([('foo', 'contents of foo\n'),
843
1171
                                  ('foo-proxied', 'proxied contents of foo\n')])
844
1172
        # Let's setup some attributes for tests
845
1173
        self.server = self.get_readonly_server()
846
1174
        self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
847
 
        self.no_proxy_host = self.proxy_address
 
1175
        if self._testing_pycurl():
 
1176
            # Oh my ! pycurl does not check for the port as part of
 
1177
            # no_proxy :-( So we just test the host part
 
1178
            self.no_proxy_host = 'localhost'
 
1179
        else:
 
1180
            self.no_proxy_host = self.proxy_address
848
1181
        # The secondary server is the proxy
849
1182
        self.proxy = self.get_secondary_server()
850
1183
        self.proxy_url = self.proxy.get_url()
851
1184
        self._old_env = {}
852
1185
 
 
1186
    def _testing_pycurl(self):
 
1187
        return pycurl_present and self._transport == PyCurlTransport
 
1188
 
853
1189
    def create_transport_secondary_server(self):
854
1190
        """Creates an http server that will serve files with
855
1191
        '-proxied' appended to their names.
856
1192
        """
857
 
        return ProxyServer()
 
1193
        return http_utils.ProxyServer(protocol_version=self._protocol_version)
858
1194
 
859
1195
    def _install_env(self, env):
860
1196
        for name, value in env.iteritems():
869
1205
        url = self.server.get_url()
870
1206
        t = self._transport(url)
871
1207
        try:
872
 
            self.assertEqual(t.get('foo').read(), 'proxied contents of foo\n')
 
1208
            self.assertEqual('proxied contents of foo\n', t.get('foo').read())
873
1209
        finally:
874
1210
            self._restore_env()
875
1211
 
878
1214
        url = self.server.get_url()
879
1215
        t = self._transport(url)
880
1216
        try:
881
 
            self.assertEqual(t.get('foo').read(), 'contents of foo\n')
 
1217
            self.assertEqual('contents of foo\n', t.get('foo').read())
882
1218
        finally:
883
1219
            self._restore_env()
884
1220
 
886
1222
        self.proxied_in_env({'http_proxy': self.proxy_url})
887
1223
 
888
1224
    def test_HTTP_PROXY(self):
 
1225
        if self._testing_pycurl():
 
1226
            # pycurl does not check HTTP_PROXY for security reasons
 
1227
            # (for use in a CGI context that we do not care
 
1228
            # about. Should we ?)
 
1229
            raise tests.TestNotApplicable(
 
1230
                'pycurl does not check HTTP_PROXY for security reasons')
889
1231
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
890
1232
 
891
1233
    def test_all_proxy(self):
899
1241
                                 'no_proxy': self.no_proxy_host})
900
1242
 
901
1243
    def test_HTTP_PROXY_with_NO_PROXY(self):
 
1244
        if self._testing_pycurl():
 
1245
            raise tests.TestNotApplicable(
 
1246
                'pycurl does not check HTTP_PROXY for security reasons')
902
1247
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
903
1248
                                 'NO_PROXY': self.no_proxy_host})
904
1249
 
911
1256
                                 'NO_PROXY': self.no_proxy_host})
912
1257
 
913
1258
    def test_http_proxy_without_scheme(self):
914
 
        self.assertRaises(errors.InvalidURL,
915
 
                          self.proxied_in_env,
916
 
                          {'http_proxy': self.proxy_address})
917
 
 
918
 
 
919
 
class TestProxyHttpServer_urllib(TestProxyHttpServer,
920
 
                                 TestCaseWithTwoWebservers):
921
 
    """Tests proxy server for urllib implementation"""
922
 
 
923
 
    _transport = HttpTransport_urllib
924
 
 
925
 
 
926
 
class TestProxyHttpServer_pycurl(TestWithTransport_pycurl,
927
 
                                 TestProxyHttpServer,
928
 
                                 TestCaseWithTwoWebservers):
929
 
    """Tests proxy server for pycurl implementation"""
930
 
 
931
 
    def setUp(self):
932
 
        TestProxyHttpServer.setUp(self)
933
 
        # Oh my ! pycurl does not check for the port as part of
934
 
        # no_proxy :-( So we just test the host part
935
 
        self.no_proxy_host = 'localhost'
936
 
 
937
 
    def test_HTTP_PROXY(self):
938
 
        # pycurl do not check HTTP_PROXY for security reasons
939
 
        # (for use in a CGI context that we do not care
940
 
        # about. Should we ?)
941
 
        raise TestSkipped()
942
 
 
943
 
    def test_HTTP_PROXY_with_NO_PROXY(self):
944
 
        raise TestSkipped()
945
 
 
946
 
    def test_http_proxy_without_scheme(self):
947
 
        # pycurl *ignores* invalid proxy env variables. If that
948
 
        # ever change in the future, this test will fail
949
 
        # indicating that pycurl do not ignore anymore such
950
 
        # variables.
951
 
        self.not_proxied_in_env({'http_proxy': self.proxy_address})
952
 
 
953
 
 
954
 
class TestRanges(object):
955
 
    """Test the Range header in GET methods..
956
 
 
957
 
    This MUST be used by daughter classes that also inherit from
958
 
    TestCaseWithWebserver.
959
 
 
960
 
    We can't inherit directly from TestCaseWithWebserver or the
961
 
    test framework will try to create an instance which cannot
962
 
    run, its implementation being incomplete.
963
 
    """
964
 
 
965
 
    def setUp(self):
966
 
        TestCaseWithWebserver.setUp(self)
 
1259
        if self._testing_pycurl():
 
1260
            # pycurl *ignores* invalid proxy env variables. If that ever change
 
1261
            # in the future, this test will fail indicating that pycurl do not
 
1262
            # ignore anymore such variables.
 
1263
            self.not_proxied_in_env({'http_proxy': self.proxy_address})
 
1264
        else:
 
1265
            self.assertRaises(errors.InvalidURL,
 
1266
                              self.proxied_in_env,
 
1267
                              {'http_proxy': self.proxy_address})
 
1268
 
 
1269
 
 
1270
class TestRanges(http_utils.TestCaseWithWebserver):
 
1271
    """Test the Range header in GET methods."""
 
1272
 
 
1273
    def setUp(self):
 
1274
        http_utils.TestCaseWithWebserver.setUp(self)
967
1275
        self.build_tree_contents([('a', '0123456789')],)
968
1276
        server = self.get_readonly_server()
969
1277
        self.transport = self._transport(server.get_url())
970
1278
 
 
1279
    def create_transport_readonly_server(self):
 
1280
        return http_server.HttpServer(protocol_version=self._protocol_version)
 
1281
 
971
1282
    def _file_contents(self, relpath, ranges):
972
1283
        offsets = [ (start, end - start + 1) for start, end in ranges]
973
1284
        coalesce = self.transport._coalesce_offsets
981
1292
    def _file_tail(self, relpath, tail_amount):
982
1293
        code, data = self.transport._get(relpath, [], tail_amount)
983
1294
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
984
 
        data.seek(-tail_amount + 1, 2)
 
1295
        data.seek(-tail_amount, 2)
985
1296
        return data.read(tail_amount)
986
1297
 
987
1298
    def test_range_header(self):
988
1299
        # Valid ranges
989
1300
        map(self.assertEqual,['0', '234'],
990
1301
            list(self._file_contents('a', [(0,0), (2,4)])),)
991
 
        # Tail
 
1302
 
 
1303
    def test_range_header_tail(self):
992
1304
        self.assertEqual('789', self._file_tail('a', 3))
993
 
        # Syntactically invalid range
994
 
        self.assertListRaises(errors.InvalidRange,
 
1305
 
 
1306
    def test_syntactically_invalid_range_header(self):
 
1307
        self.assertListRaises(errors.InvalidHttpRange,
995
1308
                          self._file_contents, 'a', [(4, 3)])
996
 
        # Semantically invalid range
997
 
        self.assertListRaises(errors.InvalidRange,
 
1309
 
 
1310
    def test_semantically_invalid_range_header(self):
 
1311
        self.assertListRaises(errors.InvalidHttpRange,
998
1312
                          self._file_contents, 'a', [(42, 128)])
999
1313
 
1000
1314
 
1001
 
class TestRanges_urllib(TestRanges, TestCaseWithWebserver):
1002
 
    """Test the Range header in GET methods for urllib implementation"""
1003
 
 
1004
 
    _transport = HttpTransport_urllib
1005
 
 
1006
 
 
1007
 
class TestRanges_pycurl(TestWithTransport_pycurl,
1008
 
                        TestRanges,
1009
 
                        TestCaseWithWebserver):
1010
 
    """Test the Range header in GET methods for pycurl implementation"""
1011
 
 
1012
 
 
1013
 
class TestHTTPRedirections(object):
1014
 
    """Test redirection between http servers.
1015
 
 
1016
 
    This MUST be used by daughter classes that also inherit from
1017
 
    TestCaseWithRedirectedWebserver.
1018
 
 
1019
 
    We can't inherit directly from TestCaseWithTwoWebservers or the
1020
 
    test framework will try to create an instance which cannot
1021
 
    run, its implementation being incomplete. 
1022
 
    """
 
1315
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
 
1316
    """Test redirection between http servers."""
1023
1317
 
1024
1318
    def create_transport_secondary_server(self):
1025
1319
        """Create the secondary server redirecting to the primary server"""
1026
1320
        new = self.get_readonly_server()
1027
1321
 
1028
 
        redirecting = HTTPServerRedirecting()
 
1322
        redirecting = http_utils.HTTPServerRedirecting(
 
1323
            protocol_version=self._protocol_version)
1029
1324
        redirecting.redirect_to(new.host, new.port)
1030
1325
        return redirecting
1031
1326
 
1035
1330
                                  ('bundle',
1036
1331
                                  '# Bazaar revision bundle v0.9\n#\n')
1037
1332
                                  ],)
1038
 
 
 
1333
        # The requests to the old server will be redirected to the new server
1039
1334
        self.old_transport = self._transport(self.old_server.get_url())
1040
1335
 
1041
1336
    def test_redirected(self):
1046
1341
    def test_read_redirected_bundle_from_url(self):
1047
1342
        from bzrlib.bundle import read_bundle_from_url
1048
1343
        url = self.old_transport.abspath('bundle')
1049
 
        bundle = read_bundle_from_url(url)
 
1344
        bundle = self.applyDeprecated(deprecated_in((1, 12, 0)),
 
1345
                read_bundle_from_url, url)
1050
1346
        # If read_bundle_from_url was successful we get an empty bundle
1051
1347
        self.assertEqual([], bundle.revisions)
1052
1348
 
1053
1349
 
1054
 
class TestHTTPRedirections_urllib(TestHTTPRedirections,
1055
 
                                  TestCaseWithRedirectedWebserver):
1056
 
    """Tests redirections for urllib implementation"""
1057
 
 
1058
 
    _transport = HttpTransport_urllib
1059
 
 
1060
 
 
1061
 
 
1062
 
class TestHTTPRedirections_pycurl(TestWithTransport_pycurl,
1063
 
                                  TestHTTPRedirections,
1064
 
                                  TestCaseWithRedirectedWebserver):
1065
 
    """Tests redirections for pycurl implementation"""
1066
 
 
1067
 
 
1068
 
class RedirectedRequest(Request):
1069
 
    """Request following redirections"""
1070
 
 
1071
 
    init_orig = Request.__init__
 
1350
class RedirectedRequest(_urllib2_wrappers.Request):
 
1351
    """Request following redirections. """
 
1352
 
 
1353
    init_orig = _urllib2_wrappers.Request.__init__
1072
1354
 
1073
1355
    def __init__(self, method, url, *args, **kwargs):
1074
 
        RedirectedRequest.init_orig(self, method, url, args, kwargs)
 
1356
        """Constructor.
 
1357
 
 
1358
        """
 
1359
        # Since the tests using this class will replace
 
1360
        # _urllib2_wrappers.Request, we can't just call the base class __init__
 
1361
        # or we'll loop.
 
1362
        RedirectedRequest.init_orig(self, method, url, *args, **kwargs)
1075
1363
        self.follow_redirections = True
1076
1364
 
1077
1365
 
1078
 
class TestHTTPSilentRedirections_urllib(TestCaseWithRedirectedWebserver):
1079
 
    """Test redirections provided by urllib.
 
1366
class TestHTTPSilentRedirections(http_utils.TestCaseWithRedirectedWebserver):
 
1367
    """Test redirections.
1080
1368
 
1081
1369
    http implementations do not redirect silently anymore (they
1082
1370
    do not redirect at all in fact). The mechanism is still in
1089
1377
    -- vila 20070212
1090
1378
    """
1091
1379
 
1092
 
    _transport = HttpTransport_urllib
1093
 
 
1094
1380
    def setUp(self):
1095
 
        super(TestHTTPSilentRedirections_urllib, self).setUp()
 
1381
        if pycurl_present and self._transport == PyCurlTransport:
 
1382
            raise tests.TestNotApplicable(
 
1383
                "pycurl doesn't redirect silently annymore")
 
1384
        super(TestHTTPSilentRedirections, self).setUp()
1096
1385
        self.setup_redirected_request()
1097
1386
        self.addCleanup(self.cleanup_redirected_request)
1098
1387
        self.build_tree_contents([('a','a'),
1119
1408
 
1120
1409
    def create_transport_secondary_server(self):
1121
1410
        """Create the secondary server, redirections are defined in the tests"""
1122
 
        return HTTPServerRedirecting()
 
1411
        return http_utils.HTTPServerRedirecting(
 
1412
            protocol_version=self._protocol_version)
1123
1413
 
1124
1414
    def test_one_redirection(self):
1125
1415
        t = self.old_transport
1141
1431
                                       self.old_server.port)
1142
1432
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1143
1433
                                       self.new_server.port)
1144
 
        self.old_server.redirections = \
1145
 
            [('/1(.*)', r'%s/2\1' % (old_prefix), 302),
1146
 
             ('/2(.*)', r'%s/3\1' % (old_prefix), 303),
1147
 
             ('/3(.*)', r'%s/4\1' % (old_prefix), 307),
1148
 
             ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
1149
 
             ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
1150
 
             ]
 
1434
        self.old_server.redirections = [
 
1435
            ('/1(.*)', r'%s/2\1' % (old_prefix), 302),
 
1436
            ('/2(.*)', r'%s/3\1' % (old_prefix), 303),
 
1437
            ('/3(.*)', r'%s/4\1' % (old_prefix), 307),
 
1438
            ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
 
1439
            ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
 
1440
            ]
1151
1441
        self.assertEquals('redirected 5 times',t._perform(req).read())
1152
1442
 
1153
1443
 
1154
 
class TestDoCatchRedirections(TestCaseWithRedirectedWebserver):
1155
 
    """Test transport.do_catching_redirections.
1156
 
 
1157
 
    We arbitrarily choose to use urllib transports
1158
 
    """
1159
 
 
1160
 
    _transport = HttpTransport_urllib
 
1444
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
 
1445
    """Test transport.do_catching_redirections."""
1161
1446
 
1162
1447
    def setUp(self):
1163
1448
        super(TestDoCatchRedirections, self).setUp()
1173
1458
 
1174
1459
        # We use None for redirected so that we fail if redirected
1175
1460
        self.assertEquals('0123456789',
1176
 
                          do_catching_redirections(self.get_a, t, None).read())
 
1461
                          transport.do_catching_redirections(
 
1462
                self.get_a, t, None).read())
1177
1463
 
1178
1464
    def test_one_redirection(self):
1179
1465
        self.redirections = 0
1184
1470
            return self._transport(dir)
1185
1471
 
1186
1472
        self.assertEquals('0123456789',
1187
 
                          do_catching_redirections(self.get_a,
1188
 
                                                   self.old_transport,
1189
 
                                                   redirected
1190
 
                                                   ).read())
 
1473
                          transport.do_catching_redirections(
 
1474
                self.get_a, self.old_transport, redirected).read())
1191
1475
        self.assertEquals(1, self.redirections)
1192
1476
 
1193
1477
    def test_redirection_loop(self):
1198
1482
            # a/a/a
1199
1483
            return self.old_transport.clone(exception.target)
1200
1484
 
1201
 
        self.assertRaises(errors.TooManyRedirections, do_catching_redirections,
 
1485
        self.assertRaises(errors.TooManyRedirections,
 
1486
                          transport.do_catching_redirections,
1202
1487
                          self.get_a, self.old_transport, redirected)
1203
1488
 
1204
1489
 
1205
 
class TestAuth(object):
1206
 
    """Test some authentication scheme specified by daughter class.
 
1490
class TestAuth(http_utils.TestCaseWithWebserver):
 
1491
    """Test authentication scheme"""
1207
1492
 
1208
 
    This MUST be used by daughter classes that also inherit from
1209
 
    either TestCaseWithWebserver or TestCaseWithTwoWebservers.
1210
 
    """
 
1493
    _auth_header = 'Authorization'
 
1494
    _password_prompt_prefix = ''
 
1495
    _username_prompt_prefix = ''
 
1496
    # Set by load_tests
 
1497
    _auth_server = None
1211
1498
 
1212
1499
    def setUp(self):
1213
 
        """Set up the test environment
1214
 
 
1215
 
        Daughter classes should set up their own environment
1216
 
        (including self.server) and explicitely call this
1217
 
        method. This is needed because we want to reuse the same
1218
 
        tests for proxy and no-proxy accesses which have
1219
 
        different ways of setting self.server.
1220
 
        """
 
1500
        super(TestAuth, self).setUp()
 
1501
        self.server = self.get_readonly_server()
1221
1502
        self.build_tree_contents([('a', 'contents of a\n'),
1222
1503
                                  ('b', 'contents of b\n'),])
1223
 
        self.old_factory = ui.ui_factory
1224
 
        # The following has the unfortunate side-effect of hiding any ouput
1225
 
        # during the tests (including pdb prompts). Feel free to comment them
1226
 
        # for debugging purposes but leave them in place, there are needed to
1227
 
        # run the tests without any console
1228
 
        self.old_stdout = sys.stdout
1229
 
        sys.stdout = StringIOWrapper()
1230
 
        self.addCleanup(self.restoreUIFactory)
1231
 
 
1232
 
    def restoreUIFactory(self):
1233
 
        ui.ui_factory = self.old_factory
1234
 
        sys.stdout = self.old_stdout
1235
 
 
1236
 
    def get_user_url(self, user=None, password=None):
 
1504
 
 
1505
    def create_transport_readonly_server(self):
 
1506
        return self._auth_server(protocol_version=self._protocol_version)
 
1507
 
 
1508
    def _testing_pycurl(self):
 
1509
        return pycurl_present and self._transport == PyCurlTransport
 
1510
 
 
1511
    def get_user_url(self, user, password):
1237
1512
        """Build an url embedding user and password"""
1238
1513
        url = '%s://' % self.server._url_protocol
1239
1514
        if user is not None:
1244
1519
        url += '%s:%s/' % (self.server.host, self.server.port)
1245
1520
        return url
1246
1521
 
 
1522
    def get_user_transport(self, user, password):
 
1523
        return self._transport(self.get_user_url(user, password))
 
1524
 
1247
1525
    def test_no_user(self):
1248
1526
        self.server.add_user('joe', 'foo')
1249
 
        t = self.get_user_transport()
 
1527
        t = self.get_user_transport(None, None)
1250
1528
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1251
1529
        # Only one 'Authentication Required' error should occur
1252
1530
        self.assertEqual(1, self.server.auth_required_errors)
1282
1560
        # initial 'who are you' and 'this is not you, who are you')
1283
1561
        self.assertEqual(2, self.server.auth_required_errors)
1284
1562
 
 
1563
    def test_prompt_for_username(self):
 
1564
        if self._testing_pycurl():
 
1565
            raise tests.TestNotApplicable(
 
1566
                'pycurl cannot prompt, it handles auth by embedding'
 
1567
                ' user:pass in urls only')
 
1568
 
 
1569
        self.server.add_user('joe', 'foo')
 
1570
        t = self.get_user_transport(None, None)
 
1571
        stdout = tests.StringIOWrapper()
 
1572
        stderr = tests.StringIOWrapper()
 
1573
        ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
 
1574
                                            stdout=stdout, stderr=stderr)
 
1575
        self.assertEqual('contents of a\n',t.get('a').read())
 
1576
        # stdin should be empty
 
1577
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
1578
        stderr.seek(0)
 
1579
        expected_prompt = self._expected_username_prompt(t._unqualified_scheme)
 
1580
        self.assertEquals(expected_prompt, stderr.read(len(expected_prompt)))
 
1581
        self.assertEquals('', stdout.getvalue())
 
1582
        self._check_password_prompt(t._unqualified_scheme, 'joe',
 
1583
                                    stderr.readline())
 
1584
 
1285
1585
    def test_prompt_for_password(self):
 
1586
        if self._testing_pycurl():
 
1587
            raise tests.TestNotApplicable(
 
1588
                'pycurl cannot prompt, it handles auth by embedding'
 
1589
                ' user:pass in urls only')
 
1590
 
1286
1591
        self.server.add_user('joe', 'foo')
1287
1592
        t = self.get_user_transport('joe', None)
1288
 
        ui.ui_factory = TestUIFactory(stdin='foo\n')
1289
 
        self.assertEqual('contents of a\n',t.get('a').read())
 
1593
        stdout = tests.StringIOWrapper()
 
1594
        stderr = tests.StringIOWrapper()
 
1595
        ui.ui_factory = tests.TestUIFactory(stdin='foo\n',
 
1596
                                            stdout=stdout, stderr=stderr)
 
1597
        self.assertEqual('contents of a\n', t.get('a').read())
1290
1598
        # stdin should be empty
1291
1599
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
1600
        self._check_password_prompt(t._unqualified_scheme, 'joe',
 
1601
                                    stderr.getvalue())
 
1602
        self.assertEquals('', stdout.getvalue())
1292
1603
        # And we shouldn't prompt again for a different request
1293
1604
        # against the same transport.
1294
1605
        self.assertEqual('contents of b\n',t.get('b').read())
1298
1609
        # Only one 'Authentication Required' error should occur
1299
1610
        self.assertEqual(1, self.server.auth_required_errors)
1300
1611
 
1301
 
 
1302
 
class TestHTTPAuth(TestAuth):
1303
 
    """Test HTTP authentication schemes.
1304
 
 
1305
 
    Daughter classes MUST inherit from TestCaseWithWebserver too.
1306
 
    """
1307
 
 
1308
 
    _auth_header = 'Authorization'
1309
 
 
1310
 
    def setUp(self):
1311
 
        TestCaseWithWebserver.setUp(self)
1312
 
        self.server = self.get_readonly_server()
1313
 
        TestAuth.setUp(self)
1314
 
 
1315
 
    def get_user_transport(self, user=None, password=None):
1316
 
        return self._transport(self.get_user_url(user, password))
 
1612
    def _check_password_prompt(self, scheme, user, actual_prompt):
 
1613
        expected_prompt = (self._password_prompt_prefix
 
1614
                           + ("%s %s@%s:%d, Realm: '%s' password: "
 
1615
                              % (scheme.upper(),
 
1616
                                 user, self.server.host, self.server.port,
 
1617
                                 self.server.auth_realm)))
 
1618
        self.assertEquals(expected_prompt, actual_prompt)
 
1619
 
 
1620
    def _expected_username_prompt(self, scheme):
 
1621
        return (self._username_prompt_prefix
 
1622
                + "%s %s:%d, Realm: '%s' username: " % (scheme.upper(),
 
1623
                                 self.server.host, self.server.port,
 
1624
                                 self.server.auth_realm))
 
1625
 
 
1626
    def test_no_prompt_for_password_when_using_auth_config(self):
 
1627
        if self._testing_pycurl():
 
1628
            raise tests.TestNotApplicable(
 
1629
                'pycurl does not support authentication.conf'
 
1630
                ' since it cannot prompt')
 
1631
 
 
1632
        user =' joe'
 
1633
        password = 'foo'
 
1634
        stdin_content = 'bar\n'  # Not the right password
 
1635
        self.server.add_user(user, password)
 
1636
        t = self.get_user_transport(user, None)
 
1637
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
 
1638
                                            stdout=tests.StringIOWrapper())
 
1639
        # Create a minimal config file with the right password
 
1640
        conf = config.AuthenticationConfig()
 
1641
        conf._get_config().update(
 
1642
            {'httptest': {'scheme': 'http', 'port': self.server.port,
 
1643
                          'user': user, 'password': password}})
 
1644
        conf._save()
 
1645
        # Issue a request to the server to connect
 
1646
        self.assertEqual('contents of a\n',t.get('a').read())
 
1647
        # stdin should have  been left untouched
 
1648
        self.assertEqual(stdin_content, ui.ui_factory.stdin.readline())
 
1649
        # Only one 'Authentication Required' error should occur
 
1650
        self.assertEqual(1, self.server.auth_required_errors)
 
1651
 
 
1652
    def test_user_from_auth_conf(self):
 
1653
        if self._testing_pycurl():
 
1654
            raise tests.TestNotApplicable(
 
1655
                'pycurl does not support authentication.conf')
 
1656
        user = 'joe'
 
1657
        password = 'foo'
 
1658
        self.server.add_user(user, password)
 
1659
        # Create a minimal config file with the right password
 
1660
        conf = config.AuthenticationConfig()
 
1661
        conf._get_config().update(
 
1662
            {'httptest': {'scheme': 'http', 'port': self.server.port,
 
1663
                          'user': user, 'password': password}})
 
1664
        conf._save()
 
1665
        t = self.get_user_transport(None, None)
 
1666
        # Issue a request to the server to connect
 
1667
        self.assertEqual('contents of a\n', t.get('a').read())
 
1668
        # Only one 'Authentication Required' error should occur
 
1669
        self.assertEqual(1, self.server.auth_required_errors)
 
1670
 
 
1671
    def test_changing_nonce(self):
 
1672
        if self._auth_server not in (http_utils.HTTPDigestAuthServer,
 
1673
                                     http_utils.ProxyDigestAuthServer):
 
1674
            raise tests.TestNotApplicable('HTTP/proxy auth digest only test')
 
1675
        if self._testing_pycurl():
 
1676
            raise tests.KnownFailure(
 
1677
                'pycurl does not handle a nonce change')
 
1678
        self.server.add_user('joe', 'foo')
 
1679
        t = self.get_user_transport('joe', 'foo')
 
1680
        self.assertEqual('contents of a\n', t.get('a').read())
 
1681
        self.assertEqual('contents of b\n', t.get('b').read())
 
1682
        # Only one 'Authentication Required' error should have
 
1683
        # occured so far
 
1684
        self.assertEqual(1, self.server.auth_required_errors)
 
1685
        # The server invalidates the current nonce
 
1686
        self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
 
1687
        self.assertEqual('contents of a\n', t.get('a').read())
 
1688
        # Two 'Authentication Required' errors should occur (the
 
1689
        # initial 'who are you' and a second 'who are you' with the new nonce)
 
1690
        self.assertEqual(2, self.server.auth_required_errors)
 
1691
 
1317
1692
 
1318
1693
 
1319
1694
class TestProxyAuth(TestAuth):
1320
 
    """Test proxy authentication schemes.
 
1695
    """Test proxy authentication schemes."""
1321
1696
 
1322
 
    Daughter classes MUST also inherit from TestCaseWithWebserver.
1323
 
    """
1324
1697
    _auth_header = 'Proxy-authorization'
 
1698
    _password_prompt_prefix = 'Proxy '
 
1699
    _username_prompt_prefix = 'Proxy '
1325
1700
 
1326
1701
    def setUp(self):
1327
 
        TestCaseWithWebserver.setUp(self)
1328
 
        self.server = self.get_readonly_server()
 
1702
        super(TestProxyAuth, self).setUp()
1329
1703
        self._old_env = {}
1330
1704
        self.addCleanup(self._restore_env)
1331
 
        TestAuth.setUp(self)
1332
1705
        # Override the contents to avoid false positives
1333
1706
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
1334
1707
                                  ('b', 'not proxied contents of b\n'),
1336
1709
                                  ('b-proxied', 'contents of b\n'),
1337
1710
                                  ])
1338
1711
 
1339
 
    def get_user_transport(self, user=None, password=None):
 
1712
    def get_user_transport(self, user, password):
1340
1713
        self._install_env({'all_proxy': self.get_user_url(user, password)})
1341
1714
        return self._transport(self.server.get_url())
1342
1715
 
1348
1721
        for name, value in self._old_env.iteritems():
1349
1722
            osutils.set_or_unset_env(name, value)
1350
1723
 
1351
 
 
1352
 
class TestHTTPBasicAuth(TestHTTPAuth, TestCaseWithWebserver):
1353
 
    """Test http basic authentication scheme"""
1354
 
 
1355
 
    _transport = HttpTransport_urllib
1356
 
 
1357
 
    def create_transport_readonly_server(self):
1358
 
        return HTTPBasicAuthServer()
1359
 
 
1360
 
 
1361
 
class TestHTTPProxyBasicAuth(TestProxyAuth, TestCaseWithWebserver):
1362
 
    """Test proxy basic authentication scheme"""
1363
 
 
1364
 
    _transport = HttpTransport_urllib
1365
 
 
1366
 
    def create_transport_readonly_server(self):
1367
 
        return ProxyBasicAuthServer()
1368
 
 
1369
 
 
1370
 
class TestDigestAuth(object):
1371
 
    """Digest Authentication specific tests"""
1372
 
 
1373
 
    def test_changing_nonce(self):
1374
 
        self.server.add_user('joe', 'foo')
1375
 
        t = self.get_user_transport('joe', 'foo')
1376
 
        self.assertEqual('contents of a\n', t.get('a').read())
1377
 
        self.assertEqual('contents of b\n', t.get('b').read())
1378
 
        # Only one 'Authentication Required' error should have
1379
 
        # occured so far
1380
 
        self.assertEqual(1, self.server.auth_required_errors)
1381
 
        # The server invalidates the current nonce
1382
 
        self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1383
 
        self.assertEqual('contents of a\n', t.get('a').read())
1384
 
        # Two 'Authentication Required' errors should occur (the
1385
 
        # initial 'who are you' and a second 'who are you' with the new nonce)
1386
 
        self.assertEqual(2, self.server.auth_required_errors)
1387
 
 
1388
 
 
1389
 
class TestHTTPDigestAuth(TestHTTPAuth, TestDigestAuth, TestCaseWithWebserver):
1390
 
    """Test http digest authentication scheme"""
1391
 
 
1392
 
    _transport = HttpTransport_urllib
1393
 
 
1394
 
    def create_transport_readonly_server(self):
1395
 
        return HTTPDigestAuthServer()
1396
 
 
1397
 
 
1398
 
class TestHTTPProxyDigestAuth(TestProxyAuth, TestDigestAuth,
1399
 
                              TestCaseWithWebserver):
1400
 
    """Test proxy digest authentication scheme"""
1401
 
 
1402
 
    _transport = HttpTransport_urllib
1403
 
 
1404
 
    def create_transport_readonly_server(self):
1405
 
        return ProxyDigestAuthServer()
1406
 
 
 
1724
    def test_empty_pass(self):
 
1725
        if self._testing_pycurl():
 
1726
            import pycurl
 
1727
            if pycurl.version_info()[1] < '7.16.0':
 
1728
                raise tests.KnownFailure(
 
1729
                    'pycurl < 7.16.0 does not handle empty proxy passwords')
 
1730
        super(TestProxyAuth, self).test_empty_pass()
 
1731
 
 
1732
 
 
1733
class SampleSocket(object):
 
1734
    """A socket-like object for use in testing the HTTP request handler."""
 
1735
 
 
1736
    def __init__(self, socket_read_content):
 
1737
        """Constructs a sample socket.
 
1738
 
 
1739
        :param socket_read_content: a byte sequence
 
1740
        """
 
1741
        # Use plain python StringIO so we can monkey-patch the close method to
 
1742
        # not discard the contents.
 
1743
        from StringIO import StringIO
 
1744
        self.readfile = StringIO(socket_read_content)
 
1745
        self.writefile = StringIO()
 
1746
        self.writefile.close = lambda: None
 
1747
 
 
1748
    def makefile(self, mode='r', bufsize=None):
 
1749
        if 'r' in mode:
 
1750
            return self.readfile
 
1751
        else:
 
1752
            return self.writefile
 
1753
 
 
1754
 
 
1755
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
 
1756
 
 
1757
    def setUp(self):
 
1758
        super(SmartHTTPTunnellingTest, self).setUp()
 
1759
        # We use the VFS layer as part of HTTP tunnelling tests.
 
1760
        self._captureVar('BZR_NO_SMART_VFS', None)
 
1761
        self.transport_readonly_server = http_utils.HTTPServerWithSmarts
 
1762
 
 
1763
    def create_transport_readonly_server(self):
 
1764
        return http_utils.HTTPServerWithSmarts(
 
1765
            protocol_version=self._protocol_version)
 
1766
 
 
1767
    def test_open_bzrdir(self):
 
1768
        branch = self.make_branch('relpath')
 
1769
        http_server = self.get_readonly_server()
 
1770
        url = http_server.get_url() + 'relpath'
 
1771
        bd = bzrdir.BzrDir.open(url)
 
1772
        self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
 
1773
 
 
1774
    def test_bulk_data(self):
 
1775
        # We should be able to send and receive bulk data in a single message.
 
1776
        # The 'readv' command in the smart protocol both sends and receives
 
1777
        # bulk data, so we use that.
 
1778
        self.build_tree(['data-file'])
 
1779
        http_server = self.get_readonly_server()
 
1780
        http_transport = self._transport(http_server.get_url())
 
1781
        medium = http_transport.get_smart_medium()
 
1782
        # Since we provide the medium, the url below will be mostly ignored
 
1783
        # during the test, as long as the path is '/'.
 
1784
        remote_transport = remote.RemoteTransport('bzr://fake_host/',
 
1785
                                                  medium=medium)
 
1786
        self.assertEqual(
 
1787
            [(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
 
1788
 
 
1789
    def test_http_send_smart_request(self):
 
1790
 
 
1791
        post_body = 'hello\n'
 
1792
        expected_reply_body = 'ok\x012\n'
 
1793
 
 
1794
        http_server = self.get_readonly_server()
 
1795
        http_transport = self._transport(http_server.get_url())
 
1796
        medium = http_transport.get_smart_medium()
 
1797
        response = medium.send_http_smart_request(post_body)
 
1798
        reply_body = response.read()
 
1799
        self.assertEqual(expected_reply_body, reply_body)
 
1800
 
 
1801
    def test_smart_http_server_post_request_handler(self):
 
1802
        httpd = self.get_readonly_server()._get_httpd()
 
1803
 
 
1804
        socket = SampleSocket(
 
1805
            'POST /.bzr/smart %s \r\n' % self._protocol_version
 
1806
            # HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
 
1807
            # for 1.0)
 
1808
            + 'Content-Length: 6\r\n'
 
1809
            '\r\n'
 
1810
            'hello\n')
 
1811
        # Beware: the ('localhost', 80) below is the
 
1812
        # client_address parameter, but we don't have one because
 
1813
        # we have defined a socket which is not bound to an
 
1814
        # address. The test framework never uses this client
 
1815
        # address, so far...
 
1816
        request_handler = http_utils.SmartRequestHandler(socket,
 
1817
                                                         ('localhost', 80),
 
1818
                                                         httpd)
 
1819
        response = socket.writefile.getvalue()
 
1820
        self.assertStartsWith(response, '%s 200 ' % self._protocol_version)
 
1821
        # This includes the end of the HTTP headers, and all the body.
 
1822
        expected_end_of_response = '\r\n\r\nok\x012\n'
 
1823
        self.assertEndsWith(response, expected_end_of_response)
 
1824
 
 
1825
 
 
1826
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
 
1827
    """No smart server here request handler."""
 
1828
 
 
1829
    def do_POST(self):
 
1830
        self.send_error(403, "Forbidden")
 
1831
 
 
1832
 
 
1833
class SmartClientAgainstNotSmartServer(TestSpecificRequestHandler):
 
1834
    """Test smart client behaviour against an http server without smarts."""
 
1835
 
 
1836
    _req_handler_class = ForbiddenRequestHandler
 
1837
 
 
1838
    def test_probe_smart_server(self):
 
1839
        """Test error handling against server refusing smart requests."""
 
1840
        server = self.get_readonly_server()
 
1841
        t = self._transport(server.get_url())
 
1842
        # No need to build a valid smart request here, the server will not even
 
1843
        # try to interpret it.
 
1844
        self.assertRaises(errors.SmartProtocolError,
 
1845
                          t.get_smart_medium().send_http_smart_request,
 
1846
                          'whatever')
 
1847
 
 
1848
class Test_redirected_to(tests.TestCase):
 
1849
 
 
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())
 
1857
 
 
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
 
1865
        # overkill).
 
1866
        self.assertEquals(t._get_connection(), r._get_connection())
 
1867
 
 
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))
 
1873
 
 
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))
 
1879
 
 
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))
 
1885
 
 
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)
 
1892
 
 
1893
 
 
1894
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
 
1895
    """Request handler for a unique and pre-defined request.
 
1896
 
 
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
 
1899
    correct responses.
 
1900
 
 
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
 
1903
    line.
 
1904
    """
 
1905
 
 
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
 
1922
 
 
1923
        # We set the bytes written *before* issuing the write, the client is
 
1924
        # supposed to consume every produced byte *before* checking that value.
 
1925
 
 
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)
 
1932
 
 
1933
 
 
1934
class ActivityServerMixin(object):
 
1935
 
 
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
 
1941
        self.bytes_read = 0
 
1942
        self.bytes_written = 0
 
1943
        self.canned_response = None
 
1944
 
 
1945
 
 
1946
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
 
1947
    pass
 
1948
 
 
1949
 
 
1950
if tests.HTTPSServerFeature.available():
 
1951
    from bzrlib.tests import https_server
 
1952
    class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
 
1953
        pass
 
1954
 
 
1955
 
 
1956
class TestActivity(tests.TestCase):
 
1957
    """Test socket activity reporting.
 
1958
 
 
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.
 
1961
    """
 
1962
 
 
1963
    def setUp(self):
 
1964
        tests.TestCase.setUp(self)
 
1965
        self.server = self._activity_server(self._protocol_version)
 
1966
        self.server.setUp()
 
1967
        self.activities = {}
 
1968
        def report_activity(t, bytes, direction):
 
1969
            count = self.activities.get(direction, 0)
 
1970
            count += bytes
 
1971
            self.activities[direction] = count
 
1972
 
 
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
 
1978
 
 
1979
    def tearDown(self):
 
1980
        self._transport._report_activity = self.orig_report_activity
 
1981
        self.server.tearDown()
 
1982
        tests.TestCase.tearDown(self)
 
1983
 
 
1984
    def get_transport(self):
 
1985
        return self._transport(self.server.get_url())
 
1986
 
 
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')
 
1992
 
 
1993
    def test_get(self):
 
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
 
2001
Connection: close\r
 
2002
Content-Type: text/plain; charset=UTF-8\r
 
2003
\r
 
2004
Bazaar-NG meta directory, format 1
 
2005
'''
 
2006
        t = self.get_transport()
 
2007
        self.assertEqual('Bazaar-NG meta directory, format 1\n',
 
2008
                         t.get('foo/bar').read())
 
2009
        self.assertActivitiesMatch()
 
2010
 
 
2011
    def test_has(self):
 
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
 
2018
\r
 
2019
'''
 
2020
        t = self.get_transport()
 
2021
        self.assertTrue(t.has('foo/bar'))
 
2022
        self.assertActivitiesMatch()
 
2023
 
 
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
 
2032
Connection: close\r
 
2033
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
 
2034
\r
 
2035
\r
 
2036
--418470f848b63279b\r
 
2037
Content-type: text/plain; charset=UTF-8\r
 
2038
Content-range: bytes 0-254/93890\r
 
2039
\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
 
2045
\r
 
2046
--418470f848b63279b\r
 
2047
Content-type: text/plain; charset=UTF-8\r
 
2048
Content-range: bytes 1000-2049/93890\r
 
2049
\r
 
2050
40-fd4ec249b6b139ab
 
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
 
2071
mbp@source\r
 
2072
--418470f848b63279b--\r
 
2073
'''
 
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()
 
2080
 
 
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
 
2089
Connection: close\r
 
2090
Content-Type: text/plain; charset=UTF-8\r
 
2091
\r
 
2092
lalala whatever as long as itsssss
 
2093
'''
 
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()