/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
# FIXME: This test should be repeated for each available http client
18
 
# implementation; at the moment we have urllib and pycurl.
 
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
33
38
    errors,
34
39
    osutils,
35
40
    tests,
 
41
    transport,
36
42
    ui,
37
43
    urlutils,
38
44
    )
39
45
from bzrlib.tests import (
40
 
    TestCase,
41
 
    TestUIFactory,
42
 
    TestSkipped,
43
 
    StringIOWrapper,
44
 
    )
45
 
from bzrlib.tests.http_server import (
46
 
    HttpServer,
47
 
    HttpServer_PyCurl,
48
 
    HttpServer_urllib,
49
 
    )
50
 
from bzrlib.tests.http_utils import (
51
 
    BadProtocolRequestHandler,
52
 
    BadStatusRequestHandler,
53
 
    ForbiddenRequestHandler,
54
 
    HTTPBasicAuthServer,
55
 
    HTTPDigestAuthServer,
56
 
    HTTPServerRedirecting,
57
 
    InvalidStatusRequestHandler,
58
 
    LimitedRangeHTTPServer,
59
 
    NoRangeRequestHandler,
60
 
    ProxyBasicAuthServer,
61
 
    ProxyDigestAuthServer,
62
 
    ProxyServer,
63
 
    SingleRangeRequestHandler,
64
 
    SingleOnlyRangeRequestHandler,
65
 
    TestCaseWithRedirectedWebserver,
66
 
    TestCaseWithTwoWebservers,
67
 
    TestCaseWithWebserver,
68
 
    WallRequestHandler,
 
46
    http_server,
 
47
    http_utils,
69
48
    )
70
49
from bzrlib.transport import (
71
 
    _CoalescedOffset,
72
 
    do_catching_redirections,
73
 
    get_transport,
74
 
    Transport,
 
50
    http,
 
51
    remote,
75
52
    )
76
53
from bzrlib.transport.http import (
77
 
    extract_auth,
78
 
    HttpTransportBase,
 
54
    _urllib,
79
55
    _urllib2_wrappers,
80
56
    )
81
 
from bzrlib.transport.http._urllib import HttpTransport_urllib
82
 
from bzrlib.transport.http._urllib2_wrappers import (
83
 
    ProxyHandler,
84
 
    Request,
85
 
    )
 
57
 
 
58
 
 
59
try:
 
60
    from bzrlib.transport.http._pycurl import PyCurlTransport
 
61
    pycurl_present = True
 
62
except errors.DependencyNotPresent:
 
63
    pycurl_present = False
 
64
 
 
65
 
 
66
class TransportAdapter(tests.TestScenarioApplier):
 
67
    """Generate the same test for each transport implementation."""
 
68
 
 
69
    def __init__(self):
 
70
        transport_scenarios = [
 
71
            ('urllib', dict(_transport=_urllib.HttpTransport_urllib,
 
72
                            _server=http_server.HttpServer_urllib,
 
73
                            _qualified_prefix='http+urllib',)),
 
74
            ]
 
75
        if pycurl_present:
 
76
            transport_scenarios.append(
 
77
                ('pycurl', dict(_transport=PyCurlTransport,
 
78
                                _server=http_server.HttpServer_PyCurl,
 
79
                                _qualified_prefix='http+pycurl',)))
 
80
        self.scenarios = transport_scenarios
 
81
 
 
82
 
 
83
class TransportProtocolAdapter(TransportAdapter):
 
84
    """Generate the same test for each protocol implementation.
 
85
 
 
86
    In addition to the transport adaptatation that we inherit from.
 
87
    """
 
88
 
 
89
    def __init__(self):
 
90
        super(TransportProtocolAdapter, self).__init__()
 
91
        protocol_scenarios = [
 
92
            ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
 
93
            ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
 
94
            ]
 
95
        self.scenarios = tests.multiply_scenarios(self.scenarios,
 
96
                                                  protocol_scenarios)
 
97
 
 
98
 
 
99
class TransportProtocolAuthenticationAdapter(TransportProtocolAdapter):
 
100
    """Generate the same test for each authentication scheme implementation.
 
101
 
 
102
    In addition to the protocol adaptatation that we inherit from.
 
103
    """
 
104
 
 
105
    def __init__(self):
 
106
        super(TransportProtocolAuthenticationAdapter, self).__init__()
 
107
        auth_scheme_scenarios = [
 
108
            ('basic', dict(_auth_scheme='basic')),
 
109
            ('digest', dict(_auth_scheme='digest')),
 
110
            ]
 
111
 
 
112
        self.scenarios = tests.multiply_scenarios(self.scenarios,
 
113
                                                  auth_scheme_scenarios)
 
114
 
 
115
def load_tests(standard_tests, module, loader):
 
116
    """Multiply tests for http clients and protocol versions."""
 
117
    # one for each transport
 
118
    t_adapter = TransportAdapter()
 
119
    t_classes= (TestHttpTransportRegistration,
 
120
                TestHttpTransportUrls,
 
121
                )
 
122
    is_testing_for_transports = tests.condition_isinstance(t_classes)
 
123
 
 
124
    # multiplied by one for each protocol version
 
125
    tp_adapter = TransportProtocolAdapter()
 
126
    tp_classes= (SmartHTTPTunnellingTest,
 
127
                 TestDoCatchRedirections,
 
128
                 TestHTTPConnections,
 
129
                 TestHTTPRedirections,
 
130
                 TestHTTPSilentRedirections,
 
131
                 TestLimitedRangeRequestServer,
 
132
                 TestPost,
 
133
                 TestProxyHttpServer,
 
134
                 TestRanges,
 
135
                 TestSpecificRequestHandler,
 
136
                 )
 
137
    is_also_testing_for_protocols = tests.condition_isinstance(tp_classes)
 
138
 
 
139
    # multiplied by one for each authentication scheme
 
140
    tpa_adapter = TransportProtocolAuthenticationAdapter()
 
141
    tpa_classes = (TestAuth,
 
142
                   )
 
143
    is_also_testing_for_authentication = tests.condition_isinstance(
 
144
        tpa_classes)
 
145
 
 
146
    result = loader.suiteClass()
 
147
    for test_class in tests.iter_suite_tests(standard_tests):
 
148
        # Each test class is either standalone or testing for some combination
 
149
        # of transport, protocol version, authentication scheme. Use the right
 
150
        # adpater (or none) depending on the class.
 
151
        if is_testing_for_transports(test_class):
 
152
            result.addTests(t_adapter.adapt(test_class))
 
153
        elif is_also_testing_for_protocols(test_class):
 
154
            result.addTests(tp_adapter.adapt(test_class))
 
155
        elif is_also_testing_for_authentication(test_class):
 
156
            result.addTests(tpa_adapter.adapt(test_class))
 
157
        else:
 
158
            result.addTest(test_class)
 
159
    return result
86
160
 
87
161
 
88
162
class FakeManager(object):
151
225
        self.port = None
152
226
 
153
227
 
 
228
class TestHTTPServer(tests.TestCase):
 
229
    """Test the HTTP servers implementations."""
 
230
 
 
231
    def test_invalid_protocol(self):
 
232
        class BogusRequestHandler(http_server.TestingHTTPRequestHandler):
 
233
 
 
234
            protocol_version = 'HTTP/0.1'
 
235
 
 
236
        server = http_server.HttpServer(BogusRequestHandler)
 
237
        try:
 
238
            self.assertRaises(httplib.UnknownProtocol,server.setUp)
 
239
        except:
 
240
            server.tearDown()
 
241
            self.fail('HTTP Server creation did not raise UnknownProtocol')
 
242
 
 
243
    def test_force_invalid_protocol(self):
 
244
        server = http_server.HttpServer(protocol_version='HTTP/0.1')
 
245
        try:
 
246
            self.assertRaises(httplib.UnknownProtocol,server.setUp)
 
247
        except:
 
248
            server.tearDown()
 
249
            self.fail('HTTP Server creation did not raise UnknownProtocol')
 
250
 
 
251
    def test_server_start_and_stop(self):
 
252
        server = http_server.HttpServer()
 
253
        server.setUp()
 
254
        self.assertTrue(server._http_running)
 
255
        server.tearDown()
 
256
        self.assertFalse(server._http_running)
 
257
 
 
258
    def test_create_http_server_one_zero(self):
 
259
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
 
260
 
 
261
            protocol_version = 'HTTP/1.0'
 
262
 
 
263
        server = http_server.HttpServer(RequestHandlerOneZero)
 
264
        server.setUp()
 
265
        self.addCleanup(server.tearDown)
 
266
        self.assertIsInstance(server._httpd, http_server.TestingHTTPServer)
 
267
 
 
268
    def test_create_http_server_one_one(self):
 
269
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
 
270
 
 
271
            protocol_version = 'HTTP/1.1'
 
272
 
 
273
        server = http_server.HttpServer(RequestHandlerOneOne)
 
274
        server.setUp()
 
275
        self.addCleanup(server.tearDown)
 
276
        self.assertIsInstance(server._httpd,
 
277
                              http_server.TestingThreadingHTTPServer)
 
278
 
 
279
    def test_create_http_server_force_one_one(self):
 
280
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
 
281
 
 
282
            protocol_version = 'HTTP/1.0'
 
283
 
 
284
        server = http_server.HttpServer(RequestHandlerOneZero,
 
285
                                        protocol_version='HTTP/1.1')
 
286
        server.setUp()
 
287
        self.addCleanup(server.tearDown)
 
288
        self.assertIsInstance(server._httpd,
 
289
                              http_server.TestingThreadingHTTPServer)
 
290
 
 
291
    def test_create_http_server_force_one_zero(self):
 
292
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
 
293
 
 
294
            protocol_version = 'HTTP/1.1'
 
295
 
 
296
        server = http_server.HttpServer(RequestHandlerOneOne,
 
297
                                        protocol_version='HTTP/1.0')
 
298
        server.setUp()
 
299
        self.addCleanup(server.tearDown)
 
300
        self.assertIsInstance(server._httpd,
 
301
                              http_server.TestingHTTPServer)
 
302
 
 
303
 
154
304
class TestWithTransport_pycurl(object):
155
305
    """Test case to inherit from if pycurl is present"""
156
306
 
171
321
 
172
322
    def test_url_parsing(self):
173
323
        f = FakeManager()
174
 
        url = extract_auth('http://example.com', f)
 
324
        url = http.extract_auth('http://example.com', f)
175
325
        self.assertEquals('http://example.com', url)
176
326
        self.assertEquals(0, len(f.credentials))
177
 
        url = extract_auth('http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
 
327
        url = http.extract_auth(
 
328
            'http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
178
329
        self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
179
330
        self.assertEquals(1, len(f.credentials))
180
331
        self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
181
332
                          f.credentials[0])
182
333
 
183
334
 
184
 
class TestHttpTransportUrls(object):
185
 
    """Test the http urls.
186
 
 
187
 
    This MUST be used by daughter classes that also inherit from
188
 
    TestCase.
189
 
 
190
 
    We can't inherit directly from TestCase or the
191
 
    test framework will try to create an instance which cannot
192
 
    run, its implementation being incomplete.
193
 
    """
 
335
class TestHttpTransportUrls(tests.TestCase):
 
336
    """Test the http urls."""
194
337
 
195
338
    def test_abs_url(self):
196
339
        """Construction of absolute http URLs"""
227
370
            server.tearDown()
228
371
 
229
372
 
230
 
class TestHttpUrls_urllib(TestHttpTransportUrls, tests.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
 
                          tests.TestCase):
240
 
    """Test http urls with pycurl"""
241
 
 
242
 
    _server = HttpServer_PyCurl
243
 
    _qualified_prefix = 'http+pycurl'
 
373
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
244
374
 
245
375
    # TODO: This should really be moved into another pycurl
246
376
    # specific test. When https tests will be implemented, take
256
386
            import pycurl
257
387
        except ImportError:
258
388
            raise tests.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
 
    """
 
389
 
 
390
        version_info_orig = pycurl.version_info
 
391
        try:
 
392
            # Now that we have pycurl imported, we can fake its version_info
 
393
            # This was taken from a windows pycurl without SSL
 
394
            # (thanks to bialix)
 
395
            pycurl.version_info = lambda : (2,
 
396
                                            '7.13.2',
 
397
                                            462082,
 
398
                                            'i386-pc-win32',
 
399
                                            2576,
 
400
                                            None,
 
401
                                            0,
 
402
                                            None,
 
403
                                            ('ftp', 'gopher', 'telnet',
 
404
                                             'dict', 'ldap', 'http', 'file'),
 
405
                                            None,
 
406
                                            0,
 
407
                                            None)
 
408
            self.assertRaises(errors.DependencyNotPresent, self._transport,
 
409
                              'https://launchpad.net')
 
410
        finally:
 
411
            # Restore the right function
 
412
            pycurl.version_info = version_info_orig
 
413
 
 
414
 
 
415
class TestHTTPConnections(http_utils.TestCaseWithWebserver):
 
416
    """Test the http connections."""
288
417
 
289
418
    def setUp(self):
290
 
        TestCaseWithWebserver.setUp(self)
291
 
        self.build_tree(['xxx', 'foo/', 'foo/bar'], line_endings='binary',
 
419
        http_utils.TestCaseWithWebserver.setUp(self)
 
420
        self.build_tree(['foo/', 'foo/bar'], line_endings='binary',
292
421
                        transport=self.get_transport())
293
422
 
294
423
    def test_http_has(self):
340
469
            socket.setdefaulttimeout(default_timeout)
341
470
 
342
471
 
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
472
class TestHttpTransportRegistration(tests.TestCase):
357
473
    """Test registrations of various http implementations"""
358
474
 
359
475
    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):
 
476
        t = transport.get_transport('%s://foo.com/' % self._qualified_prefix)
 
477
        self.assertIsInstance(t, transport.Transport)
 
478
        self.assertIsInstance(t, self._transport)
 
479
 
 
480
 
 
481
class TestPost(tests.TestCase):
 
482
 
 
483
    def test_post_body_is_received(self):
369
484
        server = RecordingServer(expect_body_tail='end-of-body')
370
485
        server.setUp()
371
486
        self.addCleanup(server.tearDown)
 
487
        scheme = self._qualified_prefix
372
488
        url = '%s://%s:%s/' % (scheme, server.host, server.port)
373
 
        try:
374
 
            http_transport = get_transport(url)
375
 
        except errors.UnsupportedProtocol:
376
 
            raise tests.TestSkipped('%s not available' % scheme)
 
489
        http_transport = self._transport(url)
377
490
        code, response = http_transport._post('abc def end-of-body')
378
491
        self.assertTrue(
379
492
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
385
498
            server.received_bytes.endswith('\r\n\r\nabc def end-of-body'))
386
499
 
387
500
 
388
 
class TestPost_urllib(tests.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, tests.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
501
class TestRangeHeader(tests.TestCase):
405
502
    """Test range_header method"""
406
503
 
407
504
    def check_header(self, value, ranges=[], tail=0):
408
505
        offsets = [ (start, end - start + 1) for start, end in ranges]
409
 
        coalesce = Transport._coalesce_offsets
 
506
        coalesce = transport.Transport._coalesce_offsets
410
507
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
411
 
        range_header = HttpTransportBase._range_header
 
508
        range_header = http.HttpTransportBase._range_header
412
509
        self.assertEqual(value, range_header(coalesced, tail))
413
510
 
414
511
    def test_range_header_single(self):
429
526
                          tail=50)
430
527
 
431
528
 
432
 
class TestWallServer(object):
 
529
class TestSpecificRequestHandler(http_utils.TestCaseWithWebserver):
 
530
    """Tests a specific request handler.
 
531
 
 
532
    Daughter classes are expected to override _req_handler_class
 
533
    """
 
534
 
 
535
    # Provide a useful default
 
536
    _req_handler_class = http_server.TestingHTTPRequestHandler
 
537
 
 
538
    def create_transport_readonly_server(self):
 
539
        return http_server.HttpServer(self._req_handler_class,
 
540
                                      protocol_version=self._protocol_version)
 
541
 
 
542
    def _testing_pycurl(self):
 
543
        return pycurl_present and self._transport == PyCurlTransport
 
544
 
 
545
 
 
546
class WallRequestHandler(http_server.TestingHTTPRequestHandler):
 
547
    """Whatever request comes in, close the connection"""
 
548
 
 
549
    def handle_one_request(self):
 
550
        """Handle a single HTTP request, by abruptly closing the connection"""
 
551
        self.close_connection = 1
 
552
 
 
553
 
 
554
class TestWallServer(TestSpecificRequestHandler):
433
555
    """Tests exceptions during the connection phase"""
434
556
 
435
 
    def create_transport_readonly_server(self):
436
 
        return HttpServer(WallRequestHandler)
 
557
    _req_handler_class = WallRequestHandler
437
558
 
438
559
    def test_http_has(self):
439
560
        server = self.get_readonly_server()
453
574
                          t.get, 'foo/bar')
454
575
 
455
576
 
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):
 
577
class BadStatusRequestHandler(http_server.TestingHTTPRequestHandler):
 
578
    """Whatever request comes in, returns a bad status"""
 
579
 
 
580
    def parse_request(self):
 
581
        """Fakes handling a single HTTP request, returns a bad status"""
 
582
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
 
583
        self.send_response(0, "Bad status")
 
584
        self.close_connection = 1
 
585
        return False
 
586
 
 
587
 
 
588
class TestBadStatusServer(TestSpecificRequestHandler):
469
589
    """Tests bad status from server."""
470
590
 
471
 
    def create_transport_readonly_server(self):
472
 
        return HttpServer(BadStatusRequestHandler)
 
591
    _req_handler_class = BadStatusRequestHandler
473
592
 
474
593
    def test_http_has(self):
475
594
        server = self.get_readonly_server()
483
602
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
484
603
 
485
604
 
486
 
class TestBadStatusServer_urllib(TestBadStatusServer, TestCaseWithWebserver):
487
 
    """Tests bad status server for urllib implementation"""
488
 
 
489
 
    _transport = HttpTransport_urllib
490
 
 
491
 
 
492
 
class TestBadStatusServer_pycurl(TestWithTransport_pycurl,
493
 
                                 TestBadStatusServer,
494
 
                                 TestCaseWithWebserver):
495
 
    """Tests bad status server for pycurl implementation"""
 
605
class InvalidStatusRequestHandler(http_server.TestingHTTPRequestHandler):
 
606
    """Whatever request comes in, returns an invalid status"""
 
607
 
 
608
    def parse_request(self):
 
609
        """Fakes handling a single HTTP request, returns a bad status"""
 
610
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
 
611
        self.wfile.write("Invalid status line\r\n")
 
612
        return False
496
613
 
497
614
 
498
615
class TestInvalidStatusServer(TestBadStatusServer):
501
618
    Both implementations raises the same error as for a bad status.
502
619
    """
503
620
 
504
 
    def create_transport_readonly_server(self):
505
 
        return HttpServer(InvalidStatusRequestHandler)
506
 
 
507
 
 
508
 
class TestInvalidStatusServer_urllib(TestInvalidStatusServer,
509
 
                                     TestCaseWithWebserver):
510
 
    """Tests invalid status server for urllib implementation"""
511
 
 
512
 
    _transport = HttpTransport_urllib
513
 
 
514
 
 
515
 
class TestInvalidStatusServer_pycurl(TestWithTransport_pycurl,
516
 
                                     TestInvalidStatusServer,
517
 
                                     TestCaseWithWebserver):
518
 
    """Tests invalid status server for pycurl implementation"""
519
 
 
520
 
 
521
 
class TestBadProtocolServer(object):
 
621
    _req_handler_class = InvalidStatusRequestHandler
 
622
 
 
623
    def test_http_has(self):
 
624
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
 
625
            raise tests.KnownFailure(
 
626
                'pycurl hangs if the server send back garbage')
 
627
        super(TestInvalidStatusServer, self).test_http_has()
 
628
 
 
629
    def test_http_get(self):
 
630
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
 
631
            raise tests.KnownFailure(
 
632
                'pycurl hangs if the server send back garbage')
 
633
        super(TestInvalidStatusServer, self).test_http_get()
 
634
 
 
635
 
 
636
class BadProtocolRequestHandler(http_server.TestingHTTPRequestHandler):
 
637
    """Whatever request comes in, returns a bad protocol version"""
 
638
 
 
639
    def parse_request(self):
 
640
        """Fakes handling a single HTTP request, returns a bad status"""
 
641
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
 
642
        # Returns an invalid protocol version, but curl just
 
643
        # ignores it and those cannot be tested.
 
644
        self.wfile.write("%s %d %s\r\n" % ('HTTP/0.0',
 
645
                                           404,
 
646
                                           'Look at my protocol version'))
 
647
        return False
 
648
 
 
649
 
 
650
class TestBadProtocolServer(TestSpecificRequestHandler):
522
651
    """Tests bad protocol from server."""
523
652
 
524
 
    def create_transport_readonly_server(self):
525
 
        return HttpServer(BadProtocolRequestHandler)
 
653
    _req_handler_class = BadProtocolRequestHandler
 
654
 
 
655
    def setUp(self):
 
656
        if pycurl_present and self._transport == PyCurlTransport:
 
657
            raise tests.TestNotApplicable(
 
658
                "pycurl doesn't check the protocol version")
 
659
        super(TestBadProtocolServer, self).setUp()
526
660
 
527
661
    def test_http_has(self):
528
662
        server = self.get_readonly_server()
535
669
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
536
670
 
537
671
 
538
 
class TestBadProtocolServer_urllib(TestBadProtocolServer,
539
 
                                   TestCaseWithWebserver):
540
 
    """Tests bad protocol server for urllib implementation"""
541
 
 
542
 
    _transport = HttpTransport_urllib
543
 
 
544
 
# curl don't check the protocol version
545
 
#class TestBadProtocolServer_pycurl(TestWithTransport_pycurl,
546
 
#                                   TestBadProtocolServer,
547
 
#                                   TestCaseWithWebserver):
548
 
#    """Tests bad protocol server for pycurl implementation"""
549
 
 
550
 
 
551
 
class TestForbiddenServer(object):
 
672
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
 
673
    """Whatever request comes in, returns a 403 code"""
 
674
 
 
675
    def parse_request(self):
 
676
        """Handle a single HTTP request, by replying we cannot handle it"""
 
677
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
 
678
        self.send_error(403)
 
679
        return False
 
680
 
 
681
 
 
682
class TestForbiddenServer(TestSpecificRequestHandler):
552
683
    """Tests forbidden server"""
553
684
 
554
 
    def create_transport_readonly_server(self):
555
 
        return HttpServer(ForbiddenRequestHandler)
 
685
    _req_handler_class = ForbiddenRequestHandler
556
686
 
557
687
    def test_http_has(self):
558
688
        server = self.get_readonly_server()
565
695
        self.assertRaises(errors.TransportError, t.get, 'foo/bar')
566
696
 
567
697
 
568
 
class TestForbiddenServer_urllib(TestForbiddenServer, TestCaseWithWebserver):
569
 
    """Tests forbidden server for urllib implementation"""
570
 
 
571
 
    _transport = HttpTransport_urllib
572
 
 
573
 
 
574
 
class TestForbiddenServer_pycurl(TestWithTransport_pycurl,
575
 
                                 TestForbiddenServer,
576
 
                                 TestCaseWithWebserver):
577
 
    """Tests forbidden server for pycurl implementation"""
578
 
 
579
 
 
580
698
class TestRecordingServer(tests.TestCase):
581
699
 
582
700
    def test_create(self):
608
726
        self.assertEqual('abc', server.received_bytes)
609
727
 
610
728
 
611
 
class TestRangeRequestServer(object):
 
729
class TestRangeRequestServer(TestSpecificRequestHandler):
612
730
    """Tests readv requests against server.
613
731
 
614
 
    This MUST be used by daughter classes that also inherit from
615
 
    TestCaseWithWebserver.
616
 
 
617
 
    We can't inherit directly from TestCaseWithWebserver or the
618
 
    test framework will try to create an instance which cannot
619
 
    run, its implementation being incomplete.
 
732
    We test against default "normal" server.
620
733
    """
621
734
 
622
735
    def setUp(self):
623
 
        TestCaseWithWebserver.setUp(self)
 
736
        super(TestRangeRequestServer, self).setUp()
624
737
        self.build_tree_contents([('a', '0123456789')],)
625
738
 
626
739
    def test_readv(self):
674
787
        t = self._transport(server.get_url())
675
788
        # force transport to issue multiple requests by limiting the number of
676
789
        # bytes by request. Note that this apply to coalesced offsets only, a
677
 
        # single range ill keep its size even if bigger than the limit.
 
790
        # single range will keep its size even if bigger than the limit.
678
791
        t._get_max_size = 2
679
792
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
680
793
        self.assertEqual(l[0], (0, '0'))
684
797
        # The server should have issued 3 requests
685
798
        self.assertEqual(3, server.GET_request_nb)
686
799
 
 
800
    def test_complete_readv_leave_pipe_clean(self):
 
801
        server = self.get_readonly_server()
 
802
        t = self._transport(server.get_url())
 
803
        # force transport to issue multiple requests
 
804
        t._get_max_size = 2
 
805
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
 
806
        # The server should have issued 3 requests
 
807
        self.assertEqual(3, server.GET_request_nb)
 
808
        self.assertEqual('0123456789', t.get_bytes('a'))
 
809
        self.assertEqual(4, server.GET_request_nb)
 
810
 
 
811
    def test_incomplete_readv_leave_pipe_clean(self):
 
812
        server = self.get_readonly_server()
 
813
        t = self._transport(server.get_url())
 
814
        # force transport to issue multiple requests
 
815
        t._get_max_size = 2
 
816
        # Don't collapse readv results into a list so that we leave unread
 
817
        # bytes on the socket
 
818
        ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
 
819
        self.assertEqual((0, '0'), ireadv.next())
 
820
        # The server should have issued one request so far 
 
821
        self.assertEqual(1, server.GET_request_nb)
 
822
        self.assertEqual('0123456789', t.get_bytes('a'))
 
823
        # get_bytes issued an additional request, the readv pending ones are
 
824
        # lost
 
825
        self.assertEqual(2, server.GET_request_nb)
 
826
 
 
827
 
 
828
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
 
829
    """Always reply to range request as if they were single.
 
830
 
 
831
    Don't be explicit about it, just to annoy the clients.
 
832
    """
 
833
 
 
834
    def get_multiple_ranges(self, file, file_size, ranges):
 
835
        """Answer as if it was a single range request and ignores the rest"""
 
836
        (start, end) = ranges[0]
 
837
        return self.get_single_range(file, file_size, start, end)
 
838
 
687
839
 
688
840
class TestSingleRangeRequestServer(TestRangeRequestServer):
689
841
    """Test readv against a server which accept only single range requests"""
690
842
 
691
 
    def create_transport_readonly_server(self):
692
 
        return HttpServer(SingleRangeRequestHandler)
693
 
 
694
 
 
695
 
class TestSingleRangeRequestServer_urllib(TestSingleRangeRequestServer,
696
 
                                          TestCaseWithWebserver):
697
 
    """Tests single range requests accepting server for urllib implementation"""
698
 
 
699
 
    _transport = HttpTransport_urllib
700
 
 
701
 
 
702
 
class TestSingleRangeRequestServer_pycurl(TestWithTransport_pycurl,
703
 
                                          TestSingleRangeRequestServer,
704
 
                                          TestCaseWithWebserver):
705
 
    """Tests single range requests accepting server for pycurl implementation"""
 
843
    _req_handler_class = SingleRangeRequestHandler
 
844
 
 
845
 
 
846
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
 
847
    """Only reply to simple range requests, errors out on multiple"""
 
848
 
 
849
    def get_multiple_ranges(self, file, file_size, ranges):
 
850
        """Refuses the multiple ranges request"""
 
851
        if len(ranges) > 1:
 
852
            file.close()
 
853
            self.send_error(416, "Requested range not satisfiable")
 
854
            return
 
855
        (start, end) = ranges[0]
 
856
        return self.get_single_range(file, file_size, start, end)
706
857
 
707
858
 
708
859
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
709
860
    """Test readv against a server which only accept single range requests"""
710
861
 
711
 
    def create_transport_readonly_server(self):
712
 
        return HttpServer(SingleOnlyRangeRequestHandler)
713
 
 
714
 
 
715
 
class TestSingleOnlyRangeRequestServer_urllib(TestSingleOnlyRangeRequestServer,
716
 
                                              TestCaseWithWebserver):
717
 
    """Tests single range requests accepting server for urllib implementation"""
718
 
 
719
 
    _transport = HttpTransport_urllib
720
 
 
721
 
 
722
 
class TestSingleOnlyRangeRequestServer_pycurl(TestWithTransport_pycurl,
723
 
                                              TestSingleOnlyRangeRequestServer,
724
 
                                              TestCaseWithWebserver):
725
 
    """Tests single range requests accepting server for pycurl implementation"""
 
862
    _req_handler_class = SingleOnlyRangeRequestHandler
 
863
 
 
864
 
 
865
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
 
866
    """Ignore range requests without notice"""
 
867
 
 
868
    def do_GET(self):
 
869
        # Update the statistics
 
870
        self.server.test_case_server.GET_request_nb += 1
 
871
        # Just bypass the range handling done by TestingHTTPRequestHandler
 
872
        return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
726
873
 
727
874
 
728
875
class TestNoRangeRequestServer(TestRangeRequestServer):
729
876
    """Test readv against a server which do not accept range requests"""
730
877
 
731
 
    def create_transport_readonly_server(self):
732
 
        return HttpServer(NoRangeRequestHandler)
733
 
 
734
 
 
735
 
class TestNoRangeRequestServer_urllib(TestNoRangeRequestServer,
736
 
                                      TestCaseWithWebserver):
737
 
    """Tests range requests refusing server for urllib implementation"""
738
 
 
739
 
    _transport = HttpTransport_urllib
740
 
 
741
 
 
742
 
class TestNoRangeRequestServer_pycurl(TestWithTransport_pycurl,
743
 
                                      TestNoRangeRequestServer,
744
 
                                      TestCaseWithWebserver):
745
 
    """Tests range requests refusing server for pycurl implementation"""
746
 
 
747
 
 
748
 
class TestLimitedRangeRequestServer(object):
749
 
    """Tests readv requests against server that errors out on too much ranges.
750
 
 
751
 
    This MUST be used by daughter classes that also inherit from
752
 
    TestCaseWithWebserver.
753
 
 
754
 
    We can't inherit directly from TestCaseWithWebserver or the
755
 
    test framework will try to create an instance which cannot
756
 
    run, its implementation being incomplete.
757
 
    """
758
 
 
 
878
    _req_handler_class = NoRangeRequestHandler
 
879
 
 
880
 
 
881
class MultipleRangeWithoutContentLengthRequestHandler(
 
882
    http_server.TestingHTTPRequestHandler):
 
883
    """Reply to multiple range requests without content length header."""
 
884
 
 
885
    def get_multiple_ranges(self, file, file_size, ranges):
 
886
        self.send_response(206)
 
887
        self.send_header('Accept-Ranges', 'bytes')
 
888
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
 
889
        self.send_header("Content-Type",
 
890
                         "multipart/byteranges; boundary=%s" % boundary)
 
891
        self.end_headers()
 
892
        for (start, end) in ranges:
 
893
            self.wfile.write("--%s\r\n" % boundary)
 
894
            self.send_header("Content-type", 'application/octet-stream')
 
895
            self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
 
896
                                                                  end,
 
897
                                                                  file_size))
 
898
            self.end_headers()
 
899
            self.send_range_content(file, start, end - start + 1)
 
900
        # Final boundary
 
901
        self.wfile.write("--%s\r\n" % boundary)
 
902
 
 
903
 
 
904
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
 
905
 
 
906
    _req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
 
907
 
 
908
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
 
909
    """Errors out when range specifiers exceed the limit"""
 
910
 
 
911
    def get_multiple_ranges(self, file, file_size, ranges):
 
912
        """Refuses the multiple ranges request"""
 
913
        tcs = self.server.test_case_server
 
914
        if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
 
915
            file.close()
 
916
            # Emulate apache behavior
 
917
            self.send_error(400, "Bad Request")
 
918
            return
 
919
        return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
 
920
            self, file, file_size, ranges)
 
921
 
 
922
 
 
923
class LimitedRangeHTTPServer(http_server.HttpServer):
 
924
    """An HttpServer erroring out on requests with too much range specifiers"""
 
925
 
 
926
    def __init__(self, request_handler=LimitedRangeRequestHandler,
 
927
                 protocol_version=None,
 
928
                 range_limit=None):
 
929
        http_server.HttpServer.__init__(self, request_handler,
 
930
                                        protocol_version=protocol_version)
 
931
        self.range_limit = range_limit
 
932
 
 
933
 
 
934
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
 
935
    """Tests readv requests against a server erroring out on too much ranges."""
 
936
 
 
937
    # Requests with more range specifiers will error out
759
938
    range_limit = 3
760
939
 
761
940
    def create_transport_readonly_server(self):
762
 
        # Requests with more range specifiers will error out
763
 
        return LimitedRangeHTTPServer(range_limit=self.range_limit)
 
941
        return LimitedRangeHTTPServer(range_limit=self.range_limit,
 
942
                                      protocol_version=self._protocol_version)
764
943
 
765
944
    def get_transport(self):
766
945
        return self._transport(self.get_readonly_server().get_url())
767
946
 
768
947
    def setUp(self):
769
 
        TestCaseWithWebserver.setUp(self)
 
948
        http_utils.TestCaseWithWebserver.setUp(self)
770
949
        # We need to manipulate ranges that correspond to real chunks in the
771
950
        # response, so we build a content appropriately.
772
951
        filler = ''.join(['abcdefghij' for x in range(102)])
792
971
        self.assertEqual(2, self.get_readonly_server().GET_request_nb)
793
972
 
794
973
 
795
 
class TestLimitedRangeRequestServer_urllib(TestLimitedRangeRequestServer,
796
 
                                          TestCaseWithWebserver):
797
 
    """Tests limited range requests server for urllib implementation"""
798
 
 
799
 
    _transport = HttpTransport_urllib
800
 
 
801
 
 
802
 
class TestLimitedRangeRequestServer_pycurl(TestWithTransport_pycurl,
803
 
                                          TestLimitedRangeRequestServer,
804
 
                                          TestCaseWithWebserver):
805
 
    """Tests limited range requests server for pycurl implementation"""
806
 
 
807
 
 
808
 
 
809
974
class TestHttpProxyWhiteBox(tests.TestCase):
810
975
    """Whitebox test proxy http authorization.
811
976
 
828
993
            osutils.set_or_unset_env(name, value)
829
994
 
830
995
    def _proxied_request(self):
831
 
        handler = ProxyHandler()
832
 
        request = Request('GET','http://baz/buzzle')
 
996
        handler = _urllib2_wrappers.ProxyHandler()
 
997
        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
833
998
        handler.set_proxy(request, 'http')
834
999
        return request
835
1000
 
844
1009
        self.assertRaises(errors.InvalidURL, self._proxied_request)
845
1010
 
846
1011
 
847
 
class TestProxyHttpServer(object):
 
1012
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
848
1013
    """Tests proxy server.
849
1014
 
850
 
    This MUST be used by daughter classes that also inherit from
851
 
    TestCaseWithTwoWebservers.
852
 
 
853
 
    We can't inherit directly from TestCaseWithTwoWebservers or
854
 
    the test framework will try to create an instance which
855
 
    cannot run, its implementation being incomplete.
856
 
 
857
1015
    Be aware that we do not setup a real proxy here. Instead, we
858
1016
    check that the *connection* goes through the proxy by serving
859
1017
    different content (the faked proxy server append '-proxied'
864
1022
    # test https connections.
865
1023
 
866
1024
    def setUp(self):
867
 
        TestCaseWithTwoWebservers.setUp(self)
 
1025
        super(TestProxyHttpServer, self).setUp()
868
1026
        self.build_tree_contents([('foo', 'contents of foo\n'),
869
1027
                                  ('foo-proxied', 'proxied contents of foo\n')])
870
1028
        # Let's setup some attributes for tests
871
1029
        self.server = self.get_readonly_server()
872
1030
        self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
873
 
        self.no_proxy_host = self.proxy_address
 
1031
        if self._testing_pycurl():
 
1032
            # Oh my ! pycurl does not check for the port as part of
 
1033
            # no_proxy :-( So we just test the host part
 
1034
            self.no_proxy_host = 'localhost'
 
1035
        else:
 
1036
            self.no_proxy_host = self.proxy_address
874
1037
        # The secondary server is the proxy
875
1038
        self.proxy = self.get_secondary_server()
876
1039
        self.proxy_url = self.proxy.get_url()
877
1040
        self._old_env = {}
878
1041
 
 
1042
    def _testing_pycurl(self):
 
1043
        return pycurl_present and self._transport == PyCurlTransport
 
1044
 
879
1045
    def create_transport_secondary_server(self):
880
1046
        """Creates an http server that will serve files with
881
1047
        '-proxied' appended to their names.
882
1048
        """
883
 
        return ProxyServer()
 
1049
        return http_utils.ProxyServer(protocol_version=self._protocol_version)
884
1050
 
885
1051
    def _install_env(self, env):
886
1052
        for name, value in env.iteritems():
912
1078
        self.proxied_in_env({'http_proxy': self.proxy_url})
913
1079
 
914
1080
    def test_HTTP_PROXY(self):
 
1081
        if self._testing_pycurl():
 
1082
            # pycurl does not check HTTP_PROXY for security reasons
 
1083
            # (for use in a CGI context that we do not care
 
1084
            # about. Should we ?)
 
1085
            raise tests.TestNotApplicable(
 
1086
                'pycurl does not check HTTP_PROXY for security reasons')
915
1087
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
916
1088
 
917
1089
    def test_all_proxy(self):
925
1097
                                 'no_proxy': self.no_proxy_host})
926
1098
 
927
1099
    def test_HTTP_PROXY_with_NO_PROXY(self):
 
1100
        if self._testing_pycurl():
 
1101
            raise tests.TestNotApplicable(
 
1102
                'pycurl does not check HTTP_PROXY for security reasons')
928
1103
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
929
1104
                                 'NO_PROXY': self.no_proxy_host})
930
1105
 
937
1112
                                 'NO_PROXY': self.no_proxy_host})
938
1113
 
939
1114
    def test_http_proxy_without_scheme(self):
940
 
        self.assertRaises(errors.InvalidURL,
941
 
                          self.proxied_in_env,
942
 
                          {'http_proxy': self.proxy_address})
943
 
 
944
 
 
945
 
class TestProxyHttpServer_urllib(TestProxyHttpServer,
946
 
                                 TestCaseWithTwoWebservers):
947
 
    """Tests proxy server for urllib implementation"""
948
 
 
949
 
    _transport = HttpTransport_urllib
950
 
 
951
 
 
952
 
class TestProxyHttpServer_pycurl(TestWithTransport_pycurl,
953
 
                                 TestProxyHttpServer,
954
 
                                 TestCaseWithTwoWebservers):
955
 
    """Tests proxy server for pycurl implementation"""
956
 
 
957
 
    def setUp(self):
958
 
        TestProxyHttpServer.setUp(self)
959
 
        # Oh my ! pycurl does not check for the port as part of
960
 
        # no_proxy :-( So we just test the host part
961
 
        self.no_proxy_host = 'localhost'
962
 
 
963
 
    def test_HTTP_PROXY(self):
964
 
        # pycurl does not check HTTP_PROXY for security reasons
965
 
        # (for use in a CGI context that we do not care
966
 
        # about. Should we ?)
967
 
        raise tests.TestNotApplicable(
968
 
            'pycurl does not check HTTP_PROXY for security reasons')
969
 
 
970
 
    def test_HTTP_PROXY_with_NO_PROXY(self):
971
 
        raise tests.TestNotApplicable(
972
 
            'pycurl does not check HTTP_PROXY for security reasons')
973
 
 
974
 
    def test_http_proxy_without_scheme(self):
975
 
        # pycurl *ignores* invalid proxy env variables. If that
976
 
        # ever change in the future, this test will fail
977
 
        # indicating that pycurl do not ignore anymore such
978
 
        # variables.
979
 
        self.not_proxied_in_env({'http_proxy': self.proxy_address})
980
 
 
981
 
 
982
 
class TestRanges(object):
983
 
    """Test the Range header in GET methods..
984
 
 
985
 
    This MUST be used by daughter classes that also inherit from
986
 
    TestCaseWithWebserver.
987
 
 
988
 
    We can't inherit directly from TestCaseWithWebserver or the
989
 
    test framework will try to create an instance which cannot
990
 
    run, its implementation being incomplete.
991
 
    """
992
 
 
993
 
    def setUp(self):
994
 
        TestCaseWithWebserver.setUp(self)
 
1115
        if self._testing_pycurl():
 
1116
            # pycurl *ignores* invalid proxy env variables. If that ever change
 
1117
            # in the future, this test will fail indicating that pycurl do not
 
1118
            # ignore anymore such variables.
 
1119
            self.not_proxied_in_env({'http_proxy': self.proxy_address})
 
1120
        else:
 
1121
            self.assertRaises(errors.InvalidURL,
 
1122
                              self.proxied_in_env,
 
1123
                              {'http_proxy': self.proxy_address})
 
1124
 
 
1125
 
 
1126
class TestRanges(http_utils.TestCaseWithWebserver):
 
1127
    """Test the Range header in GET methods."""
 
1128
 
 
1129
    def setUp(self):
 
1130
        http_utils.TestCaseWithWebserver.setUp(self)
995
1131
        self.build_tree_contents([('a', '0123456789')],)
996
1132
        server = self.get_readonly_server()
997
1133
        self.transport = self._transport(server.get_url())
998
1134
 
 
1135
    def create_transport_readonly_server(self):
 
1136
        return http_server.HttpServer(protocol_version=self._protocol_version)
 
1137
 
999
1138
    def _file_contents(self, relpath, ranges):
1000
1139
        offsets = [ (start, end - start + 1) for start, end in ranges]
1001
1140
        coalesce = self.transport._coalesce_offsets
1016
1155
        # Valid ranges
1017
1156
        map(self.assertEqual,['0', '234'],
1018
1157
            list(self._file_contents('a', [(0,0), (2,4)])),)
1019
 
        # Tail
 
1158
 
 
1159
    def test_range_header_tail(self):
1020
1160
        self.assertEqual('789', self._file_tail('a', 3))
1021
 
        # Syntactically invalid range
 
1161
 
 
1162
    def test_syntactically_invalid_range_header(self):
1022
1163
        self.assertListRaises(errors.InvalidHttpRange,
1023
1164
                          self._file_contents, 'a', [(4, 3)])
1024
 
        # Semantically invalid range
 
1165
 
 
1166
    def test_semantically_invalid_range_header(self):
1025
1167
        self.assertListRaises(errors.InvalidHttpRange,
1026
1168
                          self._file_contents, 'a', [(42, 128)])
1027
1169
 
1028
1170
 
1029
 
class TestRanges_urllib(TestRanges, TestCaseWithWebserver):
1030
 
    """Test the Range header in GET methods for urllib implementation"""
1031
 
 
1032
 
    _transport = HttpTransport_urllib
1033
 
 
1034
 
 
1035
 
class TestRanges_pycurl(TestWithTransport_pycurl,
1036
 
                        TestRanges,
1037
 
                        TestCaseWithWebserver):
1038
 
    """Test the Range header in GET methods for pycurl implementation"""
1039
 
 
1040
 
 
1041
 
class TestHTTPRedirections(object):
1042
 
    """Test redirection between http servers.
1043
 
 
1044
 
    This MUST be used by daughter classes that also inherit from
1045
 
    TestCaseWithRedirectedWebserver.
1046
 
 
1047
 
    We can't inherit directly from TestCaseWithTwoWebservers or the
1048
 
    test framework will try to create an instance which cannot
1049
 
    run, its implementation being incomplete. 
1050
 
    """
 
1171
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
 
1172
    """Test redirection between http servers."""
1051
1173
 
1052
1174
    def create_transport_secondary_server(self):
1053
1175
        """Create the secondary server redirecting to the primary server"""
1054
1176
        new = self.get_readonly_server()
1055
1177
 
1056
 
        redirecting = HTTPServerRedirecting()
 
1178
        redirecting = http_utils.HTTPServerRedirecting(
 
1179
            protocol_version=self._protocol_version)
1057
1180
        redirecting.redirect_to(new.host, new.port)
1058
1181
        return redirecting
1059
1182
 
1079
1202
        self.assertEqual([], bundle.revisions)
1080
1203
 
1081
1204
 
1082
 
class TestHTTPRedirections_urllib(TestHTTPRedirections,
1083
 
                                  TestCaseWithRedirectedWebserver):
1084
 
    """Tests redirections for urllib implementation"""
1085
 
 
1086
 
    _transport = HttpTransport_urllib
1087
 
 
1088
 
 
1089
 
 
1090
 
class TestHTTPRedirections_pycurl(TestWithTransport_pycurl,
1091
 
                                  TestHTTPRedirections,
1092
 
                                  TestCaseWithRedirectedWebserver):
1093
 
    """Tests redirections for pycurl implementation"""
1094
 
 
1095
 
 
1096
 
class RedirectedRequest(Request):
1097
 
    """Request following redirections"""
1098
 
 
1099
 
    init_orig = Request.__init__
 
1205
class RedirectedRequest(_urllib2_wrappers.Request):
 
1206
    """Request following redirections. """
 
1207
 
 
1208
    init_orig = _urllib2_wrappers.Request.__init__
1100
1209
 
1101
1210
    def __init__(self, method, url, *args, **kwargs):
 
1211
        """Constructor.
 
1212
 
 
1213
        """
 
1214
        # Since the tests using this class will replace
 
1215
        # _urllib2_wrappers.Request, we can't just call the base class __init__
 
1216
        # or we'll loop.
1102
1217
        RedirectedRequest.init_orig(self, method, url, args, kwargs)
1103
1218
        self.follow_redirections = True
1104
1219
 
1105
1220
 
1106
 
class TestHTTPSilentRedirections_urllib(TestCaseWithRedirectedWebserver):
1107
 
    """Test redirections provided by urllib.
 
1221
class TestHTTPSilentRedirections(http_utils.TestCaseWithRedirectedWebserver):
 
1222
    """Test redirections.
1108
1223
 
1109
1224
    http implementations do not redirect silently anymore (they
1110
1225
    do not redirect at all in fact). The mechanism is still in
1117
1232
    -- vila 20070212
1118
1233
    """
1119
1234
 
1120
 
    _transport = HttpTransport_urllib
1121
 
 
1122
1235
    def setUp(self):
1123
 
        super(TestHTTPSilentRedirections_urllib, self).setUp()
 
1236
        if pycurl_present and self._transport == PyCurlTransport:
 
1237
            raise tests.TestNotApplicable(
 
1238
                "pycurl doesn't redirect silently annymore")
 
1239
        super(TestHTTPSilentRedirections, self).setUp()
1124
1240
        self.setup_redirected_request()
1125
1241
        self.addCleanup(self.cleanup_redirected_request)
1126
1242
        self.build_tree_contents([('a','a'),
1147
1263
 
1148
1264
    def create_transport_secondary_server(self):
1149
1265
        """Create the secondary server, redirections are defined in the tests"""
1150
 
        return HTTPServerRedirecting()
 
1266
        return http_utils.HTTPServerRedirecting(
 
1267
            protocol_version=self._protocol_version)
1151
1268
 
1152
1269
    def test_one_redirection(self):
1153
1270
        t = self.old_transport
1169
1286
                                       self.old_server.port)
1170
1287
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1171
1288
                                       self.new_server.port)
1172
 
        self.old_server.redirections = \
1173
 
            [('/1(.*)', r'%s/2\1' % (old_prefix), 302),
1174
 
             ('/2(.*)', r'%s/3\1' % (old_prefix), 303),
1175
 
             ('/3(.*)', r'%s/4\1' % (old_prefix), 307),
1176
 
             ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
1177
 
             ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
1178
 
             ]
 
1289
        self.old_server.redirections = [
 
1290
            ('/1(.*)', r'%s/2\1' % (old_prefix), 302),
 
1291
            ('/2(.*)', r'%s/3\1' % (old_prefix), 303),
 
1292
            ('/3(.*)', r'%s/4\1' % (old_prefix), 307),
 
1293
            ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
 
1294
            ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
 
1295
            ]
1179
1296
        self.assertEquals('redirected 5 times',t._perform(req).read())
1180
1297
 
1181
1298
 
1182
 
class TestDoCatchRedirections(TestCaseWithRedirectedWebserver):
1183
 
    """Test transport.do_catching_redirections.
1184
 
 
1185
 
    We arbitrarily choose to use urllib transports
1186
 
    """
1187
 
 
1188
 
    _transport = HttpTransport_urllib
 
1299
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
 
1300
    """Test transport.do_catching_redirections."""
1189
1301
 
1190
1302
    def setUp(self):
1191
1303
        super(TestDoCatchRedirections, self).setUp()
1201
1313
 
1202
1314
        # We use None for redirected so that we fail if redirected
1203
1315
        self.assertEquals('0123456789',
1204
 
                          do_catching_redirections(self.get_a, t, None).read())
 
1316
                          transport.do_catching_redirections(
 
1317
                self.get_a, t, None).read())
1205
1318
 
1206
1319
    def test_one_redirection(self):
1207
1320
        self.redirections = 0
1212
1325
            return self._transport(dir)
1213
1326
 
1214
1327
        self.assertEquals('0123456789',
1215
 
                          do_catching_redirections(self.get_a,
1216
 
                                                   self.old_transport,
1217
 
                                                   redirected
1218
 
                                                   ).read())
 
1328
                          transport.do_catching_redirections(
 
1329
                self.get_a, self.old_transport, redirected).read())
1219
1330
        self.assertEquals(1, self.redirections)
1220
1331
 
1221
1332
    def test_redirection_loop(self):
1226
1337
            # a/a/a
1227
1338
            return self.old_transport.clone(exception.target)
1228
1339
 
1229
 
        self.assertRaises(errors.TooManyRedirections, do_catching_redirections,
 
1340
        self.assertRaises(errors.TooManyRedirections,
 
1341
                          transport.do_catching_redirections,
1230
1342
                          self.get_a, self.old_transport, redirected)
1231
1343
 
1232
1344
 
1233
 
class TestAuth(object):
1234
 
    """Test some authentication scheme specified by daughter class.
1235
 
 
1236
 
    This MUST be used by daughter classes that also inherit from
1237
 
    either TestCaseWithWebserver or TestCaseWithTwoWebservers.
1238
 
    """
1239
 
 
 
1345
class TestAuth(http_utils.TestCaseWithWebserver):
 
1346
    """Test authentication scheme"""
 
1347
 
 
1348
    _auth_header = 'Authorization'
1240
1349
    _password_prompt_prefix = ''
1241
1350
 
1242
1351
    def setUp(self):
1243
 
        """Set up the test environment
1244
 
 
1245
 
        Daughter classes should set up their own environment
1246
 
        (including self.server) and explicitely call this
1247
 
        method. This is needed because we want to reuse the same
1248
 
        tests for proxy and no-proxy accesses which have
1249
 
        different ways of setting self.server.
1250
 
        """
 
1352
        super(TestAuth, self).setUp()
 
1353
        self.server = self.get_readonly_server()
1251
1354
        self.build_tree_contents([('a', 'contents of a\n'),
1252
1355
                                  ('b', 'contents of b\n'),])
1253
1356
 
 
1357
    def create_transport_readonly_server(self):
 
1358
        if self._auth_scheme == 'basic':
 
1359
            server = http_utils.HTTPBasicAuthServer(
 
1360
                protocol_version=self._protocol_version)
 
1361
        else:
 
1362
            if self._auth_scheme != 'digest':
 
1363
                raise AssertionError('Unknown auth scheme: %r'
 
1364
                                     % self._auth_scheme)
 
1365
            server = http_utils.HTTPDigestAuthServer(
 
1366
                protocol_version=self._protocol_version)
 
1367
        return server
 
1368
 
 
1369
    def _testing_pycurl(self):
 
1370
        return pycurl_present and self._transport == PyCurlTransport
 
1371
 
1254
1372
    def get_user_url(self, user=None, password=None):
1255
1373
        """Build an url embedding user and password"""
1256
1374
        url = '%s://' % self.server._url_protocol
1262
1380
        url += '%s:%s/' % (self.server.host, self.server.port)
1263
1381
        return url
1264
1382
 
 
1383
    def get_user_transport(self, user=None, password=None):
 
1384
        return self._transport(self.get_user_url(user, password))
 
1385
 
1265
1386
    def test_no_user(self):
1266
1387
        self.server.add_user('joe', 'foo')
1267
1388
        t = self.get_user_transport()
1301
1422
        self.assertEqual(2, self.server.auth_required_errors)
1302
1423
 
1303
1424
    def test_prompt_for_password(self):
 
1425
        if self._testing_pycurl():
 
1426
            raise tests.TestNotApplicable(
 
1427
                'pycurl cannot prompt, it handles auth by embedding'
 
1428
                ' user:pass in urls only')
 
1429
 
1304
1430
        self.server.add_user('joe', 'foo')
1305
1431
        t = self.get_user_transport('joe', None)
1306
1432
        stdout = tests.StringIOWrapper()
1328
1454
        self.assertEquals(expected_prompt, actual_prompt)
1329
1455
 
1330
1456
    def test_no_prompt_for_password_when_using_auth_config(self):
 
1457
        if self._testing_pycurl():
 
1458
            raise tests.TestNotApplicable(
 
1459
                'pycurl does not support authentication.conf'
 
1460
                ' since it cannot prompt')
 
1461
 
1331
1462
        user =' joe'
1332
1463
        password = 'foo'
1333
1464
        stdin_content = 'bar\n'  # Not the right password
1348
1479
        # Only one 'Authentication Required' error should occur
1349
1480
        self.assertEqual(1, self.server.auth_required_errors)
1350
1481
 
1351
 
 
1352
 
 
1353
 
class TestHTTPAuth(TestAuth):
1354
 
    """Test HTTP authentication schemes.
1355
 
 
1356
 
    Daughter classes MUST inherit from TestCaseWithWebserver too.
1357
 
    """
1358
 
 
1359
 
    _auth_header = 'Authorization'
1360
 
 
1361
 
    def setUp(self):
1362
 
        TestCaseWithWebserver.setUp(self)
1363
 
        self.server = self.get_readonly_server()
1364
 
        TestAuth.setUp(self)
1365
 
 
1366
 
    def get_user_transport(self, user=None, password=None):
1367
 
        return self._transport(self.get_user_url(user, password))
 
1482
    def test_changing_nonce(self):
 
1483
        if self._auth_scheme != 'digest':
 
1484
            raise tests.TestNotApplicable('HTTP auth digest only test')
 
1485
        if self._testing_pycurl():
 
1486
            raise tests.KnownFailure(
 
1487
                'pycurl does not handle a nonce change')
 
1488
        self.server.add_user('joe', 'foo')
 
1489
        t = self.get_user_transport('joe', 'foo')
 
1490
        self.assertEqual('contents of a\n', t.get('a').read())
 
1491
        self.assertEqual('contents of b\n', t.get('b').read())
 
1492
        # Only one 'Authentication Required' error should have
 
1493
        # occured so far
 
1494
        self.assertEqual(1, self.server.auth_required_errors)
 
1495
        # The server invalidates the current nonce
 
1496
        self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
 
1497
        self.assertEqual('contents of a\n', t.get('a').read())
 
1498
        # Two 'Authentication Required' errors should occur (the
 
1499
        # initial 'who are you' and a second 'who are you' with the new nonce)
 
1500
        self.assertEqual(2, self.server.auth_required_errors)
 
1501
 
1368
1502
 
1369
1503
 
1370
1504
class TestProxyAuth(TestAuth):
1371
 
    """Test proxy authentication schemes.
 
1505
    """Test proxy authentication schemes."""
1372
1506
 
1373
 
    Daughter classes MUST also inherit from TestCaseWithWebserver.
1374
 
    """
1375
1507
    _auth_header = 'Proxy-authorization'
1376
 
    _password_prompt_prefix = 'Proxy '
1377
 
 
 
1508
    _password_prompt_prefix='Proxy '
1378
1509
 
1379
1510
    def setUp(self):
1380
 
        TestCaseWithWebserver.setUp(self)
1381
 
        self.server = self.get_readonly_server()
 
1511
        super(TestProxyAuth, self).setUp()
1382
1512
        self._old_env = {}
1383
1513
        self.addCleanup(self._restore_env)
1384
 
        TestAuth.setUp(self)
1385
1514
        # Override the contents to avoid false positives
1386
1515
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
1387
1516
                                  ('b', 'not proxied contents of b\n'),
1389
1518
                                  ('b-proxied', 'contents of b\n'),
1390
1519
                                  ])
1391
1520
 
 
1521
    def create_transport_readonly_server(self):
 
1522
        if self._auth_scheme == 'basic':
 
1523
            server = http_utils.ProxyBasicAuthServer(
 
1524
                protocol_version=self._protocol_version)
 
1525
        else:
 
1526
            if self._auth_scheme != 'digest':
 
1527
                raise AssertionError('Unknown auth scheme: %r'
 
1528
                                     % self._auth_scheme)
 
1529
            server = http_utils.ProxyDigestAuthServer(
 
1530
                protocol_version=self._protocol_version)
 
1531
        return server
 
1532
 
1392
1533
    def get_user_transport(self, user=None, password=None):
1393
1534
        self._install_env({'all_proxy': self.get_user_url(user, password)})
1394
1535
        return self._transport(self.server.get_url())
1401
1542
        for name, value in self._old_env.iteritems():
1402
1543
            osutils.set_or_unset_env(name, value)
1403
1544
 
1404
 
 
1405
 
class TestHTTPBasicAuth(TestHTTPAuth, TestCaseWithWebserver):
1406
 
    """Test http basic authentication scheme"""
1407
 
 
1408
 
    _transport = HttpTransport_urllib
1409
 
 
1410
 
    def create_transport_readonly_server(self):
1411
 
        return HTTPBasicAuthServer()
1412
 
 
1413
 
 
1414
 
class TestHTTPProxyBasicAuth(TestProxyAuth, TestCaseWithWebserver):
1415
 
    """Test proxy basic authentication scheme"""
1416
 
 
1417
 
    _transport = HttpTransport_urllib
1418
 
 
1419
 
    def create_transport_readonly_server(self):
1420
 
        return ProxyBasicAuthServer()
1421
 
 
1422
 
 
1423
 
class TestDigestAuth(object):
1424
 
    """Digest Authentication specific tests"""
1425
 
 
1426
 
    def test_changing_nonce(self):
1427
 
        self.server.add_user('joe', 'foo')
1428
 
        t = self.get_user_transport('joe', 'foo')
1429
 
        self.assertEqual('contents of a\n', t.get('a').read())
1430
 
        self.assertEqual('contents of b\n', t.get('b').read())
1431
 
        # Only one 'Authentication Required' error should have
1432
 
        # occured so far
1433
 
        self.assertEqual(1, self.server.auth_required_errors)
1434
 
        # The server invalidates the current nonce
1435
 
        self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1436
 
        self.assertEqual('contents of a\n', t.get('a').read())
1437
 
        # Two 'Authentication Required' errors should occur (the
1438
 
        # initial 'who are you' and a second 'who are you' with the new nonce)
1439
 
        self.assertEqual(2, self.server.auth_required_errors)
1440
 
 
1441
 
 
1442
 
class TestHTTPDigestAuth(TestHTTPAuth, TestDigestAuth, TestCaseWithWebserver):
1443
 
    """Test http digest authentication scheme"""
1444
 
 
1445
 
    _transport = HttpTransport_urllib
1446
 
 
1447
 
    def create_transport_readonly_server(self):
1448
 
        return HTTPDigestAuthServer()
1449
 
 
1450
 
 
1451
 
class TestHTTPProxyDigestAuth(TestProxyAuth, TestDigestAuth,
1452
 
                              TestCaseWithWebserver):
1453
 
    """Test proxy digest authentication scheme"""
1454
 
 
1455
 
    _transport = HttpTransport_urllib
1456
 
 
1457
 
    def create_transport_readonly_server(self):
1458
 
        return ProxyDigestAuthServer()
 
1545
    def test_empty_pass(self):
 
1546
        if self._testing_pycurl():
 
1547
            import pycurl
 
1548
            if pycurl.version_info()[1] < '7.16.0':
 
1549
                raise tests.KnownFailure(
 
1550
                    'pycurl < 7.16.0 does not handle empty proxy passwords')
 
1551
        super(TestProxyAuth, self).test_empty_pass()
 
1552
 
 
1553
 
 
1554
class SampleSocket(object):
 
1555
    """A socket-like object for use in testing the HTTP request handler."""
 
1556
 
 
1557
    def __init__(self, socket_read_content):
 
1558
        """Constructs a sample socket.
 
1559
 
 
1560
        :param socket_read_content: a byte sequence
 
1561
        """
 
1562
        # Use plain python StringIO so we can monkey-patch the close method to
 
1563
        # not discard the contents.
 
1564
        from StringIO import StringIO
 
1565
        self.readfile = StringIO(socket_read_content)
 
1566
        self.writefile = StringIO()
 
1567
        self.writefile.close = lambda: None
 
1568
 
 
1569
    def makefile(self, mode='r', bufsize=None):
 
1570
        if 'r' in mode:
 
1571
            return self.readfile
 
1572
        else:
 
1573
            return self.writefile
 
1574
 
 
1575
 
 
1576
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
 
1577
 
 
1578
    def setUp(self):
 
1579
        super(SmartHTTPTunnellingTest, self).setUp()
 
1580
        # We use the VFS layer as part of HTTP tunnelling tests.
 
1581
        self._captureVar('BZR_NO_SMART_VFS', None)
 
1582
        self.transport_readonly_server = http_utils.HTTPServerWithSmarts
 
1583
 
 
1584
    def create_transport_readonly_server(self):
 
1585
        return http_utils.HTTPServerWithSmarts(
 
1586
            protocol_version=self._protocol_version)
 
1587
 
 
1588
    def test_bulk_data(self):
 
1589
        # We should be able to send and receive bulk data in a single message.
 
1590
        # The 'readv' command in the smart protocol both sends and receives
 
1591
        # bulk data, so we use that.
 
1592
        self.build_tree(['data-file'])
 
1593
        http_server = self.get_readonly_server()
 
1594
        http_transport = self._transport(http_server.get_url())
 
1595
        medium = http_transport.get_smart_medium()
 
1596
        # Since we provide the medium, the url below will be mostly ignored
 
1597
        # during the test, as long as the path is '/'.
 
1598
        remote_transport = remote.RemoteTransport('bzr://fake_host/',
 
1599
                                                  medium=medium)
 
1600
        self.assertEqual(
 
1601
            [(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
 
1602
 
 
1603
    def test_http_send_smart_request(self):
 
1604
 
 
1605
        post_body = 'hello\n'
 
1606
        expected_reply_body = 'ok\x012\n'
 
1607
 
 
1608
        http_server = self.get_readonly_server()
 
1609
        http_transport = self._transport(http_server.get_url())
 
1610
        medium = http_transport.get_smart_medium()
 
1611
        response = medium.send_http_smart_request(post_body)
 
1612
        reply_body = response.read()
 
1613
        self.assertEqual(expected_reply_body, reply_body)
 
1614
 
 
1615
    def test_smart_http_server_post_request_handler(self):
 
1616
        httpd = self.get_readonly_server()._get_httpd()
 
1617
 
 
1618
        socket = SampleSocket(
 
1619
            'POST /.bzr/smart %s \r\n' % self._protocol_version
 
1620
            # HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
 
1621
            # for 1.0)
 
1622
            + 'Content-Length: 6\r\n'
 
1623
            '\r\n'
 
1624
            'hello\n')
 
1625
        # Beware: the ('localhost', 80) below is the
 
1626
        # client_address parameter, but we don't have one because
 
1627
        # we have defined a socket which is not bound to an
 
1628
        # address. The test framework never uses this client
 
1629
        # address, so far...
 
1630
        request_handler = http_utils.SmartRequestHandler(socket,
 
1631
                                                         ('localhost', 80),
 
1632
                                                         httpd)
 
1633
        response = socket.writefile.getvalue()
 
1634
        self.assertStartsWith(response, '%s 200 ' % self._protocol_version)
 
1635
        # This includes the end of the HTTP headers, and all the body.
 
1636
        expected_end_of_response = '\r\n\r\nok\x012\n'
 
1637
        self.assertEndsWith(response, expected_end_of_response)
 
1638
 
1459
1639