/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
3995.2.2 by Martin Pool
Cope with read_bundle_from_url deprecation in test_http
1
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
1540.3.24 by Martin Pool
Add new protocol 'http+pycurl' that always uses PyCurl.
2
#
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
1540.3.24 by Martin Pool
Add new protocol 'http+pycurl' that always uses PyCurl.
7
#
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
1540.3.24 by Martin Pool
Add new protocol 'http+pycurl' that always uses PyCurl.
12
#
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
13
# You should have received a copy of the GNU General Public License
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
1185.16.68 by Martin Pool
- http url fixes suggested by Robey Pointer, and tests
16
3111.1.30 by Vincent Ladeuil
Update NEWS. Some cosmetic changes.
17
"""Tests for HTTP implementations.
3111.1.10 by Vincent Ladeuil
Finish http parameterization, 24 auth tests failing for pycurl (not
18
3111.1.30 by Vincent Ladeuil
Update NEWS. Some cosmetic changes.
19
This module defines a load_tests() method that parametrize tests classes for
20
transport implementation, http protocol versions and authentication schemes.
3111.1.10 by Vincent Ladeuil
Finish http parameterization, 24 auth tests failing for pycurl (not
21
"""
1540.3.3 by Martin Pool
Review updates of pycurl transport
22
1540.3.22 by Martin Pool
[patch] Add TestCase.assertIsInstance
23
# TODO: Should be renamed to bzrlib.transport.http.tests?
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
24
# TODO: What about renaming to bzrlib.tests.transport.http ?
1540.3.22 by Martin Pool
[patch] Add TestCase.assertIsInstance
25
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
26
from cStringIO import StringIO
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
27
import httplib
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
28
import os
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
29
import select
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
30
import SimpleHTTPServer
2000.2.2 by John Arbash Meinel
Update the urllib.has test.
31
import socket
2420.1.20 by Vincent Ladeuil
Fix test failure on pqm.
32
import sys
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
33
import threading
2000.2.2 by John Arbash Meinel
Update the urllib.has test.
34
1553.1.2 by James Henstridge
Add a test to make sure the user-agent header is being sent correctly.
35
import bzrlib
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
36
from bzrlib import (
3606.4.1 by Andrew Bennetts
Fix NotImplementedError when probing for smart protocol via HTTP.
37
    bzrdir,
2900.2.6 by Vincent Ladeuil
Make http aware of authentication config.
38
    config,
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
39
    errors,
40
    osutils,
3606.4.1 by Andrew Bennetts
Fix NotImplementedError when probing for smart protocol via HTTP.
41
    remote as _mod_remote,
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
42
    tests,
3111.1.10 by Vincent Ladeuil
Finish http parameterization, 24 auth tests failing for pycurl (not
43
    transport,
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
44
    ui,
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
45
    urlutils,
46
    )
3995.2.2 by Martin Pool
Cope with read_bundle_from_url deprecation in test_http
47
from bzrlib.symbol_versioning import (
48
    deprecated_in,
49
    )
3102.1.1 by Vincent Ladeuil
Rename bzrlib/test/HTTPTestUtils.py to bzrlib/tests/http_utils.py and fix
50
from bzrlib.tests import (
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
51
    http_server,
3111.1.7 by Vincent Ladeuil
Further refactoring.
52
    http_utils,
3102.1.1 by Vincent Ladeuil
Rename bzrlib/test/HTTPTestUtils.py to bzrlib/tests/http_utils.py and fix
53
    )
3111.1.25 by Vincent Ladeuil
Fix the smart server failing test and use it against protocol combinations.
54
from bzrlib.transport import (
55
    http,
56
    remote,
57
    )
2004.3.3 by vila
Better (but still incomplete) design for bogus servers.
58
from bzrlib.transport.http import (
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
59
    _urllib,
2164.2.29 by Vincent Ladeuil
Test the http redirection at the request level even if it's not
60
    _urllib2_wrappers,
2004.3.3 by vila
Better (but still incomplete) design for bogus servers.
61
    )
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
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."""
3945.1.7 by Vincent Ladeuil
Test against https.
73
    result = loader.suiteClass()
74
    adapter = tests.TestScenarioApplier()
75
    remaining_tests = standard_tests
76
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
77
    # one for each transport
3945.1.7 by Vincent Ladeuil
Test against https.
78
    t_tests, remaining_tests = tests.split_suite_by_condition(
79
        remaining_tests, tests.condition_isinstance((
80
                TestHttpTransportRegistration,
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
81
                TestHttpTransportUrls,
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
82
                Test_redirected_to,
3945.1.7 by Vincent Ladeuil
Test against https.
83
                )))
84
    transport_scenarios = [
85
        ('urllib', dict(_transport=_urllib.HttpTransport_urllib,
86
                        _server=http_server.HttpServer_urllib,
87
                        _qualified_prefix='http+urllib',)),
88
        ]
89
    if pycurl_present:
90
        transport_scenarios.append(
91
            ('pycurl', dict(_transport=PyCurlTransport,
92
                            _server=http_server.HttpServer_PyCurl,
93
                            _qualified_prefix='http+pycurl',)))
94
    adapter.scenarios = transport_scenarios
95
    tests.adapt_tests(t_tests, adapter, result)
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
96
97
    # multiplied by one for each protocol version
3945.1.7 by Vincent Ladeuil
Test against https.
98
    tp_tests, remaining_tests = tests.split_suite_by_condition(
99
        remaining_tests, tests.condition_isinstance((
100
                SmartHTTPTunnellingTest,
101
                TestDoCatchRedirections,
102
                TestHTTPConnections,
103
                TestHTTPRedirections,
104
                TestHTTPSilentRedirections,
105
                TestLimitedRangeRequestServer,
106
                TestPost,
107
                TestProxyHttpServer,
108
                TestRanges,
109
                TestSpecificRequestHandler,
110
                )))
111
    protocol_scenarios = [
112
            ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
113
            ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
114
            ]
115
    tp_scenarios = tests.multiply_scenarios(adapter.scenarios,
116
                                            protocol_scenarios)
117
    adapter.scenarios = tp_scenarios
118
    tests.adapt_tests(tp_tests, adapter, result)
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
119
120
    # multiplied by one for each authentication scheme
3945.1.7 by Vincent Ladeuil
Test against https.
121
    tpa_tests, remaining_tests = tests.split_suite_by_condition(
122
        remaining_tests, tests.condition_isinstance((
123
                TestAuth,
124
                )))
125
    auth_scheme_scenarios = [
126
        ('basic', dict(_auth_scheme='basic')),
127
        ('digest', dict(_auth_scheme='digest')),
128
        ]
129
    adapter.scenarios = tests.multiply_scenarios(adapter.scenarios,
130
                                                 auth_scheme_scenarios)
131
    tests.adapt_tests(tpa_tests, adapter, result)
132
133
    tpact_tests, remaining_tests = tests.split_suite_by_condition(
134
        remaining_tests, tests.condition_isinstance((
135
                TestActivity,
136
                )))
137
    activity_scenarios = [
138
        ('http', dict(_activity_server=ActivityHTTPServer)),
139
        ]
140
    if tests.HTTPSServerFeature.available():
141
        activity_scenarios.append(
142
            ('https', dict(_activity_server=ActivityHTTPSServer,)))
143
    adapter.scenarios = tests.multiply_scenarios(tp_scenarios,
144
                                                 activity_scenarios)
145
    tests.adapt_tests(tpact_tests, adapter, result)
146
147
    # No parametrization for the remaining tests
148
    result.addTests(remaining_tests)
149
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
150
    return result
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
151
1786.1.8 by John Arbash Meinel
[merge] Johan Rydberg test updates
152
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
153
class FakeManager(object):
1786.1.8 by John Arbash Meinel
[merge] Johan Rydberg test updates
154
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
155
    def __init__(self):
156
        self.credentials = []
2004.3.1 by vila
Test ConnectionError exceptions.
157
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
158
    def add_password(self, realm, host, username, password):
159
        self.credentials.append([realm, host, username, password])
160
1553.1.2 by James Henstridge
Add a test to make sure the user-agent header is being sent correctly.
161
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
162
class RecordingServer(object):
163
    """A fake HTTP server.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
164
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
165
    It records the bytes sent to it, and replies with a 200.
166
    """
167
168
    def __init__(self, expect_body_tail=None):
169
        """Constructor.
170
171
        :type expect_body_tail: str
172
        :param expect_body_tail: a reply won't be sent until this string is
173
            received.
174
        """
175
        self._expect_body_tail = expect_body_tail
176
        self.host = None
177
        self.port = None
178
        self.received_bytes = ''
179
180
    def setUp(self):
181
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
182
        self._sock.bind(('127.0.0.1', 0))
183
        self.host, self.port = self._sock.getsockname()
184
        self._ready = threading.Event()
185
        self._thread = threading.Thread(target=self._accept_read_and_reply)
186
        self._thread.setDaemon(True)
187
        self._thread.start()
188
        self._ready.wait(5)
189
190
    def _accept_read_and_reply(self):
191
        self._sock.listen(1)
192
        self._ready.set()
193
        self._sock.settimeout(5)
194
        try:
195
            conn, address = self._sock.accept()
196
            # On win32, the accepted connection will be non-blocking to start
197
            # with because we're using settimeout.
198
            conn.setblocking(True)
199
            while not self.received_bytes.endswith(self._expect_body_tail):
200
                self.received_bytes += conn.recv(4096)
201
            conn.sendall('HTTP/1.1 200 OK\r\n')
202
        except socket.timeout:
203
            # Make sure the client isn't stuck waiting for us to e.g. accept.
204
            self._sock.close()
205
        except socket.error:
206
            # The client may have already closed the socket.
207
            pass
208
209
    def tearDown(self):
210
        try:
211
            self._sock.close()
212
        except socket.error:
213
            # We might have already closed it.  We don't care.
214
            pass
215
        self.host = None
216
        self.port = None
217
218
4050.2.2 by Vincent Ladeuil
Ensures all auth handlers correctly parse all auth headers.
219
class TestAuthHeader(tests.TestCase):
220
221
    def parse_header(self, header):
222
        ah =  _urllib2_wrappers.AbstractAuthHandler()
223
        return ah._parse_auth_header(header)
224
225
    def test_empty_header(self):
226
        scheme, remainder = self.parse_header('')
227
        self.assertEquals('', scheme)
228
        self.assertIs(None, remainder)
229
230
    def test_negotiate_header(self):
231
        scheme, remainder = self.parse_header('Negotiate')
232
        self.assertEquals('negotiate', scheme)
233
        self.assertIs(None, remainder)
234
235
    def test_basic_header(self):
236
        scheme, remainder = self.parse_header(
237
            'Basic realm="Thou should not pass"')
238
        self.assertEquals('basic', scheme)
239
        self.assertEquals('realm="Thou should not pass"', remainder)
240
241
    def test_digest_header(self):
242
        scheme, remainder = self.parse_header(
243
            'Digest realm="Thou should not pass"')
244
        self.assertEquals('digest', scheme)
245
        self.assertEquals('realm="Thou should not pass"', remainder)
246
247
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
248
class TestHTTPServer(tests.TestCase):
249
    """Test the HTTP servers implementations."""
250
251
    def test_invalid_protocol(self):
252
        class BogusRequestHandler(http_server.TestingHTTPRequestHandler):
253
254
            protocol_version = 'HTTP/0.1'
255
256
        server = http_server.HttpServer(BogusRequestHandler)
257
        try:
258
            self.assertRaises(httplib.UnknownProtocol,server.setUp)
259
        except:
260
            server.tearDown()
261
            self.fail('HTTP Server creation did not raise UnknownProtocol')
262
3111.1.17 by Vincent Ladeuil
Add tests for the protocol version parameter.
263
    def test_force_invalid_protocol(self):
264
        server = http_server.HttpServer(protocol_version='HTTP/0.1')
265
        try:
266
            self.assertRaises(httplib.UnknownProtocol,server.setUp)
267
        except:
268
            server.tearDown()
269
            self.fail('HTTP Server creation did not raise UnknownProtocol')
270
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
271
    def test_server_start_and_stop(self):
272
        server = http_server.HttpServer()
273
        server.setUp()
274
        self.assertTrue(server._http_running)
275
        server.tearDown()
276
        self.assertFalse(server._http_running)
277
278
    def test_create_http_server_one_zero(self):
279
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
280
281
            protocol_version = 'HTTP/1.0'
282
283
        server = http_server.HttpServer(RequestHandlerOneZero)
284
        server.setUp()
3111.1.17 by Vincent Ladeuil
Add tests for the protocol version parameter.
285
        self.addCleanup(server.tearDown)
286
        self.assertIsInstance(server._httpd, http_server.TestingHTTPServer)
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
287
288
    def test_create_http_server_one_one(self):
289
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
290
291
            protocol_version = 'HTTP/1.1'
292
293
        server = http_server.HttpServer(RequestHandlerOneOne)
294
        server.setUp()
3111.1.17 by Vincent Ladeuil
Add tests for the protocol version parameter.
295
        self.addCleanup(server.tearDown)
296
        self.assertIsInstance(server._httpd,
297
                              http_server.TestingThreadingHTTPServer)
298
299
    def test_create_http_server_force_one_one(self):
300
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
301
302
            protocol_version = 'HTTP/1.0'
303
304
        server = http_server.HttpServer(RequestHandlerOneZero,
305
                                        protocol_version='HTTP/1.1')
306
        server.setUp()
307
        self.addCleanup(server.tearDown)
308
        self.assertIsInstance(server._httpd,
309
                              http_server.TestingThreadingHTTPServer)
310
311
    def test_create_http_server_force_one_zero(self):
312
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
313
314
            protocol_version = 'HTTP/1.1'
315
316
        server = http_server.HttpServer(RequestHandlerOneOne,
317
                                        protocol_version='HTTP/1.0')
318
        server.setUp()
319
        self.addCleanup(server.tearDown)
320
        self.assertIsInstance(server._httpd,
321
                              http_server.TestingHTTPServer)
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
322
323
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
324
class TestWithTransport_pycurl(object):
325
    """Test case to inherit from if pycurl is present"""
326
327
    def _get_pycurl_maybe(self):
328
        try:
329
            from bzrlib.transport.http._pycurl import PyCurlTransport
330
            return PyCurlTransport
331
        except errors.DependencyNotPresent:
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
332
            raise tests.TestSkipped('pycurl not present')
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
333
334
    _transport = property(_get_pycurl_maybe)
335
336
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
337
class TestHttpUrls(tests.TestCase):
1786.1.8 by John Arbash Meinel
[merge] Johan Rydberg test updates
338
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
339
    # TODO: This should be moved to authorization tests once they
340
    # are written.
2004.1.40 by v.ladeuil+lp at free
Fix the race condition again and correct some small typos to be in
341
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
342
    def test_url_parsing(self):
343
        f = FakeManager()
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
344
        url = http.extract_auth('http://example.com', f)
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
345
        self.assertEquals('http://example.com', url)
346
        self.assertEquals(0, len(f.credentials))
3111.1.30 by Vincent Ladeuil
Update NEWS. Some cosmetic changes.
347
        url = http.extract_auth(
348
            'http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
1185.50.94 by John Arbash Meinel
Updated web page url to http://bazaar-vcs.org
349
        self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
350
        self.assertEquals(1, len(f.credentials))
2004.3.1 by vila
Test ConnectionError exceptions.
351
        self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
352
                          f.credentials[0])
353
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
354
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
355
class TestHttpTransportUrls(tests.TestCase):
356
    """Test the http urls."""
357
358
    def test_abs_url(self):
359
        """Construction of absolute http URLs"""
360
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
361
        eq = self.assertEqualDiff
362
        eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
363
        eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
364
        eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
365
        eq(t.abspath('.bzr/1//2/./3'),
366
           'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
367
368
    def test_invalid_http_urls(self):
369
        """Trap invalid construction of urls"""
370
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
371
        self.assertRaises(errors.InvalidURL,
372
                          self._transport,
373
                          'http://http://bazaar-vcs.org/bzr/bzr.dev/')
374
375
    def test_http_root_urls(self):
376
        """Construction of URLs from server root"""
377
        t = self._transport('http://bzr.ozlabs.org/')
378
        eq = self.assertEqualDiff
379
        eq(t.abspath('.bzr/tree-version'),
380
           'http://bzr.ozlabs.org/.bzr/tree-version')
381
382
    def test_http_impl_urls(self):
383
        """There are servers which ask for particular clients to connect"""
384
        server = self._server()
385
        try:
386
            server.setUp()
387
            url = server.get_url()
388
            self.assertTrue(url.startswith('%s://' % self._qualified_prefix))
389
        finally:
390
            server.tearDown()
391
392
3111.1.9 by Vincent Ladeuil
Most refactoring regarding parameterization for urllib/pycurl and custom
393
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
394
395
    # TODO: This should really be moved into another pycurl
396
    # specific test. When https tests will be implemented, take
397
    # this one into account.
398
    def test_pycurl_without_https_support(self):
399
        """Test that pycurl without SSL do not fail with a traceback.
400
401
        For the purpose of the test, we force pycurl to ignore
402
        https by supplying a fake version_info that do not
403
        support it.
404
        """
405
        try:
406
            import pycurl
407
        except ImportError:
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
408
            raise tests.TestSkipped('pycurl not present')
3111.1.14 by Vincent Ladeuil
Fix test leakage.
409
410
        version_info_orig = pycurl.version_info
411
        try:
412
            # Now that we have pycurl imported, we can fake its version_info
413
            # This was taken from a windows pycurl without SSL
414
            # (thanks to bialix)
415
            pycurl.version_info = lambda : (2,
416
                                            '7.13.2',
417
                                            462082,
418
                                            'i386-pc-win32',
419
                                            2576,
420
                                            None,
421
                                            0,
422
                                            None,
423
                                            ('ftp', 'gopher', 'telnet',
424
                                             'dict', 'ldap', 'http', 'file'),
425
                                            None,
426
                                            0,
427
                                            None)
428
            self.assertRaises(errors.DependencyNotPresent, self._transport,
429
                              'https://launchpad.net')
430
        finally:
431
            # Restore the right function
432
            pycurl.version_info = version_info_orig
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
433
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
434
435
class TestHTTPConnections(http_utils.TestCaseWithWebserver):
436
    """Test the http connections."""
437
438
    def setUp(self):
439
        http_utils.TestCaseWithWebserver.setUp(self)
440
        self.build_tree(['foo/', 'foo/bar'], line_endings='binary',
441
                        transport=self.get_transport())
442
443
    def test_http_has(self):
444
        server = self.get_readonly_server()
445
        t = self._transport(server.get_url())
446
        self.assertEqual(t.has('foo/bar'), True)
447
        self.assertEqual(len(server.logs), 1)
448
        self.assertContainsRe(server.logs[0],
449
            r'"HEAD /foo/bar HTTP/1.." (200|302) - "-" "bzr/')
450
451
    def test_http_has_not_found(self):
452
        server = self.get_readonly_server()
453
        t = self._transport(server.get_url())
454
        self.assertEqual(t.has('not-found'), False)
455
        self.assertContainsRe(server.logs[1],
456
            r'"HEAD /not-found HTTP/1.." 404 - "-" "bzr/')
457
458
    def test_http_get(self):
459
        server = self.get_readonly_server()
460
        t = self._transport(server.get_url())
461
        fp = t.get('foo/bar')
462
        self.assertEqualDiff(
463
            fp.read(),
464
            'contents of foo/bar\n')
465
        self.assertEqual(len(server.logs), 1)
466
        self.assertTrue(server.logs[0].find(
467
            '"GET /foo/bar HTTP/1.1" 200 - "-" "bzr/%s'
468
            % bzrlib.__version__) > -1)
469
470
    def test_has_on_bogus_host(self):
471
        # Get a free address and don't 'accept' on it, so that we
472
        # can be sure there is no http handler there, but set a
473
        # reasonable timeout to not slow down tests too much.
474
        default_timeout = socket.getdefaulttimeout()
475
        try:
476
            socket.setdefaulttimeout(2)
477
            s = socket.socket()
478
            s.bind(('localhost', 0))
479
            t = self._transport('http://%s:%s/' % s.getsockname())
480
            self.assertRaises(errors.ConnectionError, t.has, 'foo/bar')
481
        finally:
482
            socket.setdefaulttimeout(default_timeout)
483
484
485
class TestHttpTransportRegistration(tests.TestCase):
486
    """Test registrations of various http implementations"""
487
488
    def test_http_registered(self):
489
        t = transport.get_transport('%s://foo.com/' % self._qualified_prefix)
490
        self.assertIsInstance(t, transport.Transport)
491
        self.assertIsInstance(t, self._transport)
492
493
494
class TestPost(tests.TestCase):
495
496
    def test_post_body_is_received(self):
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
497
        server = RecordingServer(expect_body_tail='end-of-body')
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
498
        server.setUp()
499
        self.addCleanup(server.tearDown)
500
        scheme = self._qualified_prefix
501
        url = '%s://%s:%s/' % (scheme, server.host, server.port)
502
        http_transport = self._transport(url)
503
        code, response = http_transport._post('abc def end-of-body')
504
        self.assertTrue(
505
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
506
        self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
507
        # The transport should not be assuming that the server can accept
508
        # chunked encoding the first time it connects, because HTTP/1.1, so we
509
        # check for the literal string.
510
        self.assertTrue(
511
            server.received_bytes.endswith('\r\n\r\nabc def end-of-body'))
512
513
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
514
class TestRangeHeader(tests.TestCase):
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
515
    """Test range_header method"""
516
517
    def check_header(self, value, ranges=[], tail=0):
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
518
        offsets = [ (start, end - start + 1) for start, end in ranges]
3111.1.10 by Vincent Ladeuil
Finish http parameterization, 24 auth tests failing for pycurl (not
519
        coalesce = transport.Transport._coalesce_offsets
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
520
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
521
        range_header = http.HttpTransportBase._range_header
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
522
        self.assertEqual(value, range_header(coalesced, tail))
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
523
524
    def test_range_header_single(self):
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
525
        self.check_header('0-9', ranges=[(0,9)])
526
        self.check_header('100-109', ranges=[(100,109)])
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
527
528
    def test_range_header_tail(self):
1786.1.36 by John Arbash Meinel
pycurl expects us to just set the range of bytes, not including bytes=
529
        self.check_header('-10', tail=10)
530
        self.check_header('-50', tail=50)
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
531
532
    def test_range_header_multi(self):
1786.1.36 by John Arbash Meinel
pycurl expects us to just set the range of bytes, not including bytes=
533
        self.check_header('0-9,100-200,300-5000',
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
534
                          ranges=[(0,9), (100, 200), (300,5000)])
535
536
    def test_range_header_mixed(self):
1786.1.36 by John Arbash Meinel
pycurl expects us to just set the range of bytes, not including bytes=
537
        self.check_header('0-9,300-5000,-50',
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
538
                          ranges=[(0,9), (300,5000)],
539
                          tail=50)
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
540
2004.1.15 by v.ladeuil+lp at free
Better design for bogus servers. Both urllib and pycurl pass tests.
541
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
542
class TestSpecificRequestHandler(http_utils.TestCaseWithWebserver):
543
    """Tests a specific request handler.
544
3111.1.31 by Vincent Ladeuil
Review feeback.
545
    Daughter classes are expected to override _req_handler_class
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
546
    """
547
548
    # Provide a useful default
549
    _req_handler_class = http_server.TestingHTTPRequestHandler
550
551
    def create_transport_readonly_server(self):
552
        return http_server.HttpServer(self._req_handler_class,
553
                                      protocol_version=self._protocol_version)
554
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
555
    def _testing_pycurl(self):
556
        return pycurl_present and self._transport == PyCurlTransport
557
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
558
559
class WallRequestHandler(http_server.TestingHTTPRequestHandler):
560
    """Whatever request comes in, close the connection"""
561
562
    def handle_one_request(self):
563
        """Handle a single HTTP request, by abruptly closing the connection"""
564
        self.close_connection = 1
565
566
567
class TestWallServer(TestSpecificRequestHandler):
568
    """Tests exceptions during the connection phase"""
569
570
    _req_handler_class = WallRequestHandler
571
572
    def test_http_has(self):
573
        server = self.get_readonly_server()
574
        t = self._transport(server.get_url())
575
        # Unfortunately httplib (see HTTPResponse._read_status
576
        # for details) make no distinction between a closed
577
        # socket and badly formatted status line, so we can't
578
        # just test for ConnectionError, we have to test
579
        # InvalidHttpResponse too.
580
        self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
581
                          t.has, 'foo/bar')
582
583
    def test_http_get(self):
584
        server = self.get_readonly_server()
585
        t = self._transport(server.get_url())
586
        self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
587
                          t.get, 'foo/bar')
588
589
590
class BadStatusRequestHandler(http_server.TestingHTTPRequestHandler):
591
    """Whatever request comes in, returns a bad status"""
592
593
    def parse_request(self):
594
        """Fakes handling a single HTTP request, returns a bad status"""
595
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
596
        self.send_response(0, "Bad status")
597
        self.close_connection = 1
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
598
        return False
599
600
601
class TestBadStatusServer(TestSpecificRequestHandler):
602
    """Tests bad status from server."""
603
604
    _req_handler_class = BadStatusRequestHandler
605
606
    def test_http_has(self):
607
        server = self.get_readonly_server()
608
        t = self._transport(server.get_url())
609
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
610
611
    def test_http_get(self):
612
        server = self.get_readonly_server()
613
        t = self._transport(server.get_url())
614
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
615
616
617
class InvalidStatusRequestHandler(http_server.TestingHTTPRequestHandler):
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
618
    """Whatever request comes in, returns an invalid status"""
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
619
620
    def parse_request(self):
621
        """Fakes handling a single HTTP request, returns a bad status"""
622
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
623
        self.wfile.write("Invalid status line\r\n")
624
        return False
625
626
627
class TestInvalidStatusServer(TestBadStatusServer):
628
    """Tests invalid status from server.
629
630
    Both implementations raises the same error as for a bad status.
631
    """
632
633
    _req_handler_class = InvalidStatusRequestHandler
634
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
635
    def test_http_has(self):
636
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
3111.1.22 by Vincent Ladeuil
Rework TestingHTTPServer classes, fix test bug.
637
            raise tests.KnownFailure(
638
                'pycurl hangs if the server send back garbage')
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
639
        super(TestInvalidStatusServer, self).test_http_has()
640
641
    def test_http_get(self):
642
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
3111.1.22 by Vincent Ladeuil
Rework TestingHTTPServer classes, fix test bug.
643
            raise tests.KnownFailure(
644
                'pycurl hangs if the server send back garbage')
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
645
        super(TestInvalidStatusServer, self).test_http_get()
646
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
647
648
class BadProtocolRequestHandler(http_server.TestingHTTPRequestHandler):
649
    """Whatever request comes in, returns a bad protocol version"""
650
651
    def parse_request(self):
652
        """Fakes handling a single HTTP request, returns a bad status"""
653
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
654
        # Returns an invalid protocol version, but curl just
655
        # ignores it and those cannot be tested.
656
        self.wfile.write("%s %d %s\r\n" % ('HTTP/0.0',
657
                                           404,
658
                                           'Look at my protocol version'))
659
        return False
660
661
662
class TestBadProtocolServer(TestSpecificRequestHandler):
663
    """Tests bad protocol from server."""
664
665
    _req_handler_class = BadProtocolRequestHandler
666
667
    def setUp(self):
668
        if pycurl_present and self._transport == PyCurlTransport:
669
            raise tests.TestNotApplicable(
670
                "pycurl doesn't check the protocol version")
671
        super(TestBadProtocolServer, self).setUp()
672
673
    def test_http_has(self):
674
        server = self.get_readonly_server()
675
        t = self._transport(server.get_url())
676
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
677
678
    def test_http_get(self):
679
        server = self.get_readonly_server()
680
        t = self._transport(server.get_url())
681
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
682
683
684
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
685
    """Whatever request comes in, returns a 403 code"""
686
687
    def parse_request(self):
688
        """Handle a single HTTP request, by replying we cannot handle it"""
689
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
690
        self.send_error(403)
691
        return False
692
693
694
class TestForbiddenServer(TestSpecificRequestHandler):
695
    """Tests forbidden server"""
696
697
    _req_handler_class = ForbiddenRequestHandler
698
699
    def test_http_has(self):
700
        server = self.get_readonly_server()
701
        t = self._transport(server.get_url())
702
        self.assertRaises(errors.TransportError, t.has, 'foo/bar')
703
704
    def test_http_get(self):
705
        server = self.get_readonly_server()
706
        t = self._transport(server.get_url())
707
        self.assertRaises(errors.TransportError, t.get, 'foo/bar')
708
709
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
710
class TestRecordingServer(tests.TestCase):
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
711
712
    def test_create(self):
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
713
        server = RecordingServer(expect_body_tail=None)
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
714
        self.assertEqual('', server.received_bytes)
715
        self.assertEqual(None, server.host)
716
        self.assertEqual(None, server.port)
717
718
    def test_setUp_and_tearDown(self):
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
719
        server = RecordingServer(expect_body_tail=None)
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
720
        server.setUp()
721
        try:
722
            self.assertNotEqual(None, server.host)
723
            self.assertNotEqual(None, server.port)
724
        finally:
725
            server.tearDown()
726
        self.assertEqual(None, server.host)
727
        self.assertEqual(None, server.port)
728
729
    def test_send_receive_bytes(self):
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
730
        server = RecordingServer(expect_body_tail='c')
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
731
        server.setUp()
732
        self.addCleanup(server.tearDown)
733
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
734
        sock.connect((server.host, server.port))
735
        sock.sendall('abc')
736
        self.assertEqual('HTTP/1.1 200 OK\r\n',
2091.1.1 by Martin Pool
Avoid MSG_WAITALL as it doesn't work on Windows
737
                         osutils.recv_all(sock, 4096))
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
738
        self.assertEqual('abc', server.received_bytes)
2004.1.29 by v.ladeuil+lp at free
New tests for http range requests handling.
739
740
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
741
class TestRangeRequestServer(TestSpecificRequestHandler):
742
    """Tests readv requests against server.
743
744
    We test against default "normal" server.
745
    """
746
747
    def setUp(self):
748
        super(TestRangeRequestServer, self).setUp()
749
        self.build_tree_contents([('a', '0123456789')],)
750
751
    def test_readv(self):
752
        server = self.get_readonly_server()
753
        t = self._transport(server.get_url())
754
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
755
        self.assertEqual(l[0], (0, '0'))
756
        self.assertEqual(l[1], (1, '1'))
757
        self.assertEqual(l[2], (3, '34'))
758
        self.assertEqual(l[3], (9, '9'))
759
760
    def test_readv_out_of_order(self):
761
        server = self.get_readonly_server()
762
        t = self._transport(server.get_url())
763
        l = list(t.readv('a', ((1, 1), (9, 1), (0, 1), (3, 2))))
764
        self.assertEqual(l[0], (1, '1'))
765
        self.assertEqual(l[1], (9, '9'))
766
        self.assertEqual(l[2], (0, '0'))
767
        self.assertEqual(l[3], (3, '34'))
768
769
    def test_readv_invalid_ranges(self):
770
        server = self.get_readonly_server()
771
        t = self._transport(server.get_url())
772
773
        # This is intentionally reading off the end of the file
774
        # since we are sure that it cannot get there
775
        self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
776
                              t.readv, 'a', [(1,1), (8,10)])
777
778
        # This is trying to seek past the end of the file, it should
779
        # also raise a special error
780
        self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
781
                              t.readv, 'a', [(12,2)])
782
783
    def test_readv_multiple_get_requests(self):
784
        server = self.get_readonly_server()
785
        t = self._transport(server.get_url())
786
        # force transport to issue multiple requests
787
        t._max_readv_combine = 1
788
        t._max_get_ranges = 1
789
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
790
        self.assertEqual(l[0], (0, '0'))
791
        self.assertEqual(l[1], (1, '1'))
792
        self.assertEqual(l[2], (3, '34'))
793
        self.assertEqual(l[3], (9, '9'))
794
        # The server should have issued 4 requests
795
        self.assertEqual(4, server.GET_request_nb)
796
797
    def test_readv_get_max_size(self):
798
        server = self.get_readonly_server()
799
        t = self._transport(server.get_url())
800
        # force transport to issue multiple requests by limiting the number of
801
        # bytes by request. Note that this apply to coalesced offsets only, a
3111.1.28 by Vincent Ladeuil
Fix the multi-ranges http server and add tests.
802
        # single range will keep its size even if bigger than the limit.
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
803
        t._get_max_size = 2
804
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
805
        self.assertEqual(l[0], (0, '0'))
806
        self.assertEqual(l[1], (1, '1'))
807
        self.assertEqual(l[2], (2, '2345'))
808
        self.assertEqual(l[3], (6, '6789'))
809
        # The server should have issued 3 requests
810
        self.assertEqual(3, server.GET_request_nb)
811
3111.1.28 by Vincent Ladeuil
Fix the multi-ranges http server and add tests.
812
    def test_complete_readv_leave_pipe_clean(self):
813
        server = self.get_readonly_server()
814
        t = self._transport(server.get_url())
815
        # force transport to issue multiple requests
816
        t._get_max_size = 2
817
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
818
        # The server should have issued 3 requests
819
        self.assertEqual(3, server.GET_request_nb)
820
        self.assertEqual('0123456789', t.get_bytes('a'))
821
        self.assertEqual(4, server.GET_request_nb)
822
823
    def test_incomplete_readv_leave_pipe_clean(self):
824
        server = self.get_readonly_server()
825
        t = self._transport(server.get_url())
826
        # force transport to issue multiple requests
827
        t._get_max_size = 2
828
        # Don't collapse readv results into a list so that we leave unread
829
        # bytes on the socket
830
        ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
831
        self.assertEqual((0, '0'), ireadv.next())
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
832
        # The server should have issued one request so far
3111.1.28 by Vincent Ladeuil
Fix the multi-ranges http server and add tests.
833
        self.assertEqual(1, server.GET_request_nb)
834
        self.assertEqual('0123456789', t.get_bytes('a'))
835
        # get_bytes issued an additional request, the readv pending ones are
836
        # lost
837
        self.assertEqual(2, server.GET_request_nb)
838
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
839
840
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
841
    """Always reply to range request as if they were single.
842
843
    Don't be explicit about it, just to annoy the clients.
844
    """
845
846
    def get_multiple_ranges(self, file, file_size, ranges):
847
        """Answer as if it was a single range request and ignores the rest"""
848
        (start, end) = ranges[0]
849
        return self.get_single_range(file, file_size, start, end)
850
851
852
class TestSingleRangeRequestServer(TestRangeRequestServer):
853
    """Test readv against a server which accept only single range requests"""
854
855
    _req_handler_class = SingleRangeRequestHandler
856
857
858
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
859
    """Only reply to simple range requests, errors out on multiple"""
860
861
    def get_multiple_ranges(self, file, file_size, ranges):
862
        """Refuses the multiple ranges request"""
863
        if len(ranges) > 1:
864
            file.close()
865
            self.send_error(416, "Requested range not satisfiable")
866
            return
867
        (start, end) = ranges[0]
868
        return self.get_single_range(file, file_size, start, end)
869
870
871
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
872
    """Test readv against a server which only accept single range requests"""
873
874
    _req_handler_class = SingleOnlyRangeRequestHandler
875
876
877
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
878
    """Ignore range requests without notice"""
879
880
    def do_GET(self):
881
        # Update the statistics
882
        self.server.test_case_server.GET_request_nb += 1
883
        # Just bypass the range handling done by TestingHTTPRequestHandler
884
        return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
885
886
887
class TestNoRangeRequestServer(TestRangeRequestServer):
888
    """Test readv against a server which do not accept range requests"""
889
890
    _req_handler_class = NoRangeRequestHandler
891
892
3111.1.28 by Vincent Ladeuil
Fix the multi-ranges http server and add tests.
893
class MultipleRangeWithoutContentLengthRequestHandler(
894
    http_server.TestingHTTPRequestHandler):
895
    """Reply to multiple range requests without content length header."""
896
897
    def get_multiple_ranges(self, file, file_size, ranges):
898
        self.send_response(206)
899
        self.send_header('Accept-Ranges', 'bytes')
900
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
901
        self.send_header("Content-Type",
902
                         "multipart/byteranges; boundary=%s" % boundary)
903
        self.end_headers()
904
        for (start, end) in ranges:
905
            self.wfile.write("--%s\r\n" % boundary)
906
            self.send_header("Content-type", 'application/octet-stream')
907
            self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
908
                                                                  end,
909
                                                                  file_size))
910
            self.end_headers()
911
            self.send_range_content(file, start, end - start + 1)
912
        # Final boundary
913
        self.wfile.write("--%s\r\n" % boundary)
914
915
916
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
917
918
    _req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
919
3146.3.2 by Vincent Ladeuil
Fix #179368 by keeping the current range hint on ShortReadvErrors.
920
921
class TruncatedMultipleRangeRequestHandler(
922
    http_server.TestingHTTPRequestHandler):
923
    """Reply to multiple range requests truncating the last ones.
924
925
    This server generates responses whose Content-Length describes all the
926
    ranges, but fail to include the last ones leading to client short reads.
927
    This has been observed randomly with lighttpd (bug #179368).
928
    """
929
930
    _truncated_ranges = 2
931
932
    def get_multiple_ranges(self, file, file_size, ranges):
933
        self.send_response(206)
934
        self.send_header('Accept-Ranges', 'bytes')
935
        boundary = 'tagada'
936
        self.send_header('Content-Type',
937
                         'multipart/byteranges; boundary=%s' % boundary)
938
        boundary_line = '--%s\r\n' % boundary
939
        # Calculate the Content-Length
940
        content_length = 0
941
        for (start, end) in ranges:
942
            content_length += len(boundary_line)
943
            content_length += self._header_line_length(
944
                'Content-type', 'application/octet-stream')
945
            content_length += self._header_line_length(
946
                'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
947
            content_length += len('\r\n') # end headers
948
            content_length += end - start # + 1
949
        content_length += len(boundary_line)
950
        self.send_header('Content-length', content_length)
951
        self.end_headers()
952
953
        # Send the multipart body
954
        cur = 0
955
        for (start, end) in ranges:
956
            self.wfile.write(boundary_line)
957
            self.send_header('Content-type', 'application/octet-stream')
958
            self.send_header('Content-Range', 'bytes %d-%d/%d'
959
                             % (start, end, file_size))
960
            self.end_headers()
961
            if cur + self._truncated_ranges >= len(ranges):
962
                # Abruptly ends the response and close the connection
963
                self.close_connection = 1
964
                return
965
            self.send_range_content(file, start, end - start + 1)
966
            cur += 1
967
        # No final boundary
968
        self.wfile.write(boundary_line)
969
970
971
class TestTruncatedMultipleRangeServer(TestSpecificRequestHandler):
972
973
    _req_handler_class = TruncatedMultipleRangeRequestHandler
974
975
    def setUp(self):
976
        super(TestTruncatedMultipleRangeServer, self).setUp()
977
        self.build_tree_contents([('a', '0123456789')],)
978
979
    def test_readv_with_short_reads(self):
980
        server = self.get_readonly_server()
981
        t = self._transport(server.get_url())
982
        # Force separate ranges for each offset
983
        t._bytes_to_read_before_seek = 0
984
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
985
        self.assertEqual((0, '0'), ireadv.next())
986
        self.assertEqual((2, '2'), ireadv.next())
987
        if not self._testing_pycurl():
988
            # Only one request have been issued so far (except for pycurl that
989
            # try to read the whole response at once)
990
            self.assertEqual(1, server.GET_request_nb)
991
        self.assertEqual((4, '45'), ireadv.next())
992
        self.assertEqual((9, '9'), ireadv.next())
993
        # Both implementations issue 3 requests but:
994
        # - urllib does two multiple (4 ranges, then 2 ranges) then a single
995
        #   range,
996
        # - pycurl does two multiple (4 ranges, 4 ranges) then a single range
997
        self.assertEqual(3, server.GET_request_nb)
998
        # Finally the client have tried a single range request and stays in
999
        # that mode
1000
        self.assertEqual('single', t._range_hint)
1001
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1002
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1003
    """Errors out when range specifiers exceed the limit"""
1004
1005
    def get_multiple_ranges(self, file, file_size, ranges):
1006
        """Refuses the multiple ranges request"""
1007
        tcs = self.server.test_case_server
1008
        if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
1009
            file.close()
1010
            # Emulate apache behavior
1011
            self.send_error(400, "Bad Request")
1012
            return
1013
        return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
1014
            self, file, file_size, ranges)
1015
1016
1017
class LimitedRangeHTTPServer(http_server.HttpServer):
1018
    """An HttpServer erroring out on requests with too much range specifiers"""
1019
1020
    def __init__(self, request_handler=LimitedRangeRequestHandler,
1021
                 protocol_version=None,
1022
                 range_limit=None):
1023
        http_server.HttpServer.__init__(self, request_handler,
1024
                                        protocol_version=protocol_version)
1025
        self.range_limit = range_limit
1026
1027
1028
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1029
    """Tests readv requests against a server erroring out on too much ranges."""
1030
3111.1.22 by Vincent Ladeuil
Rework TestingHTTPServer classes, fix test bug.
1031
    # Requests with more range specifiers will error out
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1032
    range_limit = 3
1033
1034
    def create_transport_readonly_server(self):
1035
        return LimitedRangeHTTPServer(range_limit=self.range_limit,
1036
                                      protocol_version=self._protocol_version)
1037
1038
    def get_transport(self):
1039
        return self._transport(self.get_readonly_server().get_url())
1040
1041
    def setUp(self):
1042
        http_utils.TestCaseWithWebserver.setUp(self)
1043
        # We need to manipulate ranges that correspond to real chunks in the
1044
        # response, so we build a content appropriately.
1045
        filler = ''.join(['abcdefghij' for x in range(102)])
1046
        content = ''.join(['%04d' % v + filler for v in range(16)])
1047
        self.build_tree_contents([('a', content)],)
1048
1049
    def test_few_ranges(self):
1050
        t = self.get_transport()
1051
        l = list(t.readv('a', ((0, 4), (1024, 4), )))
1052
        self.assertEqual(l[0], (0, '0000'))
1053
        self.assertEqual(l[1], (1024, '0001'))
1054
        self.assertEqual(1, self.get_readonly_server().GET_request_nb)
1055
1056
    def test_more_ranges(self):
1057
        t = self.get_transport()
1058
        l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
1059
        self.assertEqual(l[0], (0, '0000'))
1060
        self.assertEqual(l[1], (1024, '0001'))
1061
        self.assertEqual(l[2], (4096, '0004'))
1062
        self.assertEqual(l[3], (8192, '0008'))
1063
        # The server will refuse to serve the first request (too much ranges),
3199.1.2 by Vincent Ladeuil
Fix two more leaked log files.
1064
        # a second request will succeed.
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1065
        self.assertEqual(2, self.get_readonly_server().GET_request_nb)
1066
1067
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
1068
class TestHttpProxyWhiteBox(tests.TestCase):
2298.7.1 by Vincent Ladeuil
Fix bug #87765: proxy env variables without scheme should cause
1069
    """Whitebox test proxy http authorization.
1070
2420.1.3 by Vincent Ladeuil
Implement http proxy basic authentication.
1071
    Only the urllib implementation is tested here.
2298.7.1 by Vincent Ladeuil
Fix bug #87765: proxy env variables without scheme should cause
1072
    """
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
1073
1074
    def setUp(self):
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
1075
        tests.TestCase.setUp(self)
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
1076
        self._old_env = {}
1077
1078
    def tearDown(self):
1079
        self._restore_env()
3199.1.2 by Vincent Ladeuil
Fix two more leaked log files.
1080
        tests.TestCase.tearDown(self)
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
1081
1082
    def _install_env(self, env):
1083
        for name, value in env.iteritems():
2420.1.2 by Vincent Ladeuil
Define tests for http proxy basic authentication. They fail.
1084
            self._old_env[name] = osutils.set_or_unset_env(name, value)
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
1085
1086
    def _restore_env(self):
1087
        for name, value in self._old_env.iteritems():
1088
            osutils.set_or_unset_env(name, value)
1089
1090
    def _proxied_request(self):
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
1091
        handler = _urllib2_wrappers.ProxyHandler()
1092
        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
1093
        handler.set_proxy(request, 'http')
1094
        return request
1095
1096
    def test_empty_user(self):
1097
        self._install_env({'http_proxy': 'http://bar.com'})
1098
        request = self._proxied_request()
1099
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
1100
2298.7.1 by Vincent Ladeuil
Fix bug #87765: proxy env variables without scheme should cause
1101
    def test_invalid_proxy(self):
1102
        """A proxy env variable without scheme"""
1103
        self._install_env({'http_proxy': 'host:1234'})
1104
        self.assertRaises(errors.InvalidURL, self._proxied_request)
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
1105
1106
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1107
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
1108
    """Tests proxy server.
1109
1110
    Be aware that we do not setup a real proxy here. Instead, we
1111
    check that the *connection* goes through the proxy by serving
1112
    different content (the faked proxy server append '-proxied'
1113
    to the file names).
1114
    """
1115
1116
    # FIXME: We don't have an https server available, so we don't
1117
    # test https connections.
1118
1119
    def setUp(self):
1120
        super(TestProxyHttpServer, self).setUp()
1121
        self.build_tree_contents([('foo', 'contents of foo\n'),
1122
                                  ('foo-proxied', 'proxied contents of foo\n')])
1123
        # Let's setup some attributes for tests
1124
        self.server = self.get_readonly_server()
1125
        self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
1126
        if self._testing_pycurl():
1127
            # Oh my ! pycurl does not check for the port as part of
1128
            # no_proxy :-( So we just test the host part
1129
            self.no_proxy_host = 'localhost'
1130
        else:
1131
            self.no_proxy_host = self.proxy_address
1132
        # The secondary server is the proxy
1133
        self.proxy = self.get_secondary_server()
1134
        self.proxy_url = self.proxy.get_url()
1135
        self._old_env = {}
1136
1137
    def _testing_pycurl(self):
1138
        return pycurl_present and self._transport == PyCurlTransport
1139
1140
    def create_transport_secondary_server(self):
1141
        """Creates an http server that will serve files with
1142
        '-proxied' appended to their names.
1143
        """
1144
        return http_utils.ProxyServer(protocol_version=self._protocol_version)
1145
1146
    def _install_env(self, env):
1147
        for name, value in env.iteritems():
1148
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1149
1150
    def _restore_env(self):
1151
        for name, value in self._old_env.iteritems():
1152
            osutils.set_or_unset_env(name, value)
1153
1154
    def proxied_in_env(self, env):
1155
        self._install_env(env)
1156
        url = self.server.get_url()
1157
        t = self._transport(url)
1158
        try:
3734.2.8 by Vincent Ladeuil
Catch spurious exceptions (python-2.6) when SocketServer is shut down.
1159
            self.assertEqual('proxied contents of foo\n', t.get('foo').read())
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1160
        finally:
1161
            self._restore_env()
1162
1163
    def not_proxied_in_env(self, env):
1164
        self._install_env(env)
1165
        url = self.server.get_url()
1166
        t = self._transport(url)
1167
        try:
3734.2.8 by Vincent Ladeuil
Catch spurious exceptions (python-2.6) when SocketServer is shut down.
1168
            self.assertEqual('contents of foo\n', t.get('foo').read())
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1169
        finally:
1170
            self._restore_env()
1171
1172
    def test_http_proxy(self):
1173
        self.proxied_in_env({'http_proxy': self.proxy_url})
1174
1175
    def test_HTTP_PROXY(self):
1176
        if self._testing_pycurl():
1177
            # pycurl does not check HTTP_PROXY for security reasons
1178
            # (for use in a CGI context that we do not care
1179
            # about. Should we ?)
1180
            raise tests.TestNotApplicable(
1181
                'pycurl does not check HTTP_PROXY for security reasons')
1182
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
1183
1184
    def test_all_proxy(self):
1185
        self.proxied_in_env({'all_proxy': self.proxy_url})
1186
1187
    def test_ALL_PROXY(self):
1188
        self.proxied_in_env({'ALL_PROXY': self.proxy_url})
1189
1190
    def test_http_proxy_with_no_proxy(self):
1191
        self.not_proxied_in_env({'http_proxy': self.proxy_url,
1192
                                 'no_proxy': self.no_proxy_host})
1193
1194
    def test_HTTP_PROXY_with_NO_PROXY(self):
1195
        if self._testing_pycurl():
1196
            raise tests.TestNotApplicable(
1197
                'pycurl does not check HTTP_PROXY for security reasons')
1198
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
1199
                                 'NO_PROXY': self.no_proxy_host})
1200
1201
    def test_all_proxy_with_no_proxy(self):
1202
        self.not_proxied_in_env({'all_proxy': self.proxy_url,
1203
                                 'no_proxy': self.no_proxy_host})
1204
1205
    def test_ALL_PROXY_with_NO_PROXY(self):
1206
        self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
1207
                                 'NO_PROXY': self.no_proxy_host})
1208
1209
    def test_http_proxy_without_scheme(self):
1210
        if self._testing_pycurl():
1211
            # pycurl *ignores* invalid proxy env variables. If that ever change
1212
            # in the future, this test will fail indicating that pycurl do not
1213
            # ignore anymore such variables.
1214
            self.not_proxied_in_env({'http_proxy': self.proxy_address})
1215
        else:
1216
            self.assertRaises(errors.InvalidURL,
1217
                              self.proxied_in_env,
1218
                              {'http_proxy': self.proxy_address})
1219
1220
1221
class TestRanges(http_utils.TestCaseWithWebserver):
1222
    """Test the Range header in GET methods."""
1223
1224
    def setUp(self):
1225
        http_utils.TestCaseWithWebserver.setUp(self)
1226
        self.build_tree_contents([('a', '0123456789')],)
1227
        server = self.get_readonly_server()
1228
        self.transport = self._transport(server.get_url())
1229
3111.1.22 by Vincent Ladeuil
Rework TestingHTTPServer classes, fix test bug.
1230
    def create_transport_readonly_server(self):
1231
        return http_server.HttpServer(protocol_version=self._protocol_version)
1232
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1233
    def _file_contents(self, relpath, ranges):
1234
        offsets = [ (start, end - start + 1) for start, end in ranges]
1235
        coalesce = self.transport._coalesce_offsets
1236
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
1237
        code, data = self.transport._get(relpath, coalesced)
1238
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1239
        for start, end in ranges:
1240
            data.seek(start)
1241
            yield data.read(end - start + 1)
1242
1243
    def _file_tail(self, relpath, tail_amount):
1244
        code, data = self.transport._get(relpath, [], tail_amount)
1245
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1246
        data.seek(-tail_amount, 2)
1247
        return data.read(tail_amount)
1248
1249
    def test_range_header(self):
1250
        # Valid ranges
1251
        map(self.assertEqual,['0', '234'],
1252
            list(self._file_contents('a', [(0,0), (2,4)])),)
1253
1254
    def test_range_header_tail(self):
1255
        self.assertEqual('789', self._file_tail('a', 3))
1256
1257
    def test_syntactically_invalid_range_header(self):
1258
        self.assertListRaises(errors.InvalidHttpRange,
1259
                          self._file_contents, 'a', [(4, 3)])
1260
1261
    def test_semantically_invalid_range_header(self):
1262
        self.assertListRaises(errors.InvalidHttpRange,
1263
                          self._file_contents, 'a', [(42, 128)])
1264
1265
1266
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1267
    """Test redirection between http servers."""
1268
1269
    def create_transport_secondary_server(self):
1270
        """Create the secondary server redirecting to the primary server"""
1271
        new = self.get_readonly_server()
1272
1273
        redirecting = http_utils.HTTPServerRedirecting(
1274
            protocol_version=self._protocol_version)
1275
        redirecting.redirect_to(new.host, new.port)
1276
        return redirecting
1277
1278
    def setUp(self):
1279
        super(TestHTTPRedirections, self).setUp()
1280
        self.build_tree_contents([('a', '0123456789'),
1281
                                  ('bundle',
1282
                                  '# Bazaar revision bundle v0.9\n#\n')
1283
                                  ],)
3878.4.1 by Vincent Ladeuil
Fix bug #245964 by preserving decorators during redirections (when
1284
        # The requests to the old server will be redirected to the new server
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1285
        self.old_transport = self._transport(self.old_server.get_url())
1286
1287
    def test_redirected(self):
1288
        self.assertRaises(errors.RedirectRequested, self.old_transport.get, 'a')
1289
        t = self._transport(self.new_server.get_url())
1290
        self.assertEqual('0123456789', t.get('a').read())
1291
1292
    def test_read_redirected_bundle_from_url(self):
1293
        from bzrlib.bundle import read_bundle_from_url
1294
        url = self.old_transport.abspath('bundle')
3995.2.2 by Martin Pool
Cope with read_bundle_from_url deprecation in test_http
1295
        bundle = self.applyDeprecated(deprecated_in((1, 12, 0)),
1296
                read_bundle_from_url, url)
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1297
        # If read_bundle_from_url was successful we get an empty bundle
1298
        self.assertEqual([], bundle.revisions)
1299
1300
1301
class RedirectedRequest(_urllib2_wrappers.Request):
1302
    """Request following redirections. """
1303
1304
    init_orig = _urllib2_wrappers.Request.__init__
1305
1306
    def __init__(self, method, url, *args, **kwargs):
1307
        """Constructor.
1308
1309
        """
1310
        # Since the tests using this class will replace
1311
        # _urllib2_wrappers.Request, we can't just call the base class __init__
1312
        # or we'll loop.
1313
        RedirectedRequest.init_orig(self, method, url, args, kwargs)
1314
        self.follow_redirections = True
1315
1316
1317
class TestHTTPSilentRedirections(http_utils.TestCaseWithRedirectedWebserver):
1318
    """Test redirections.
1319
1320
    http implementations do not redirect silently anymore (they
1321
    do not redirect at all in fact). The mechanism is still in
1322
    place at the _urllib2_wrappers.Request level and these tests
1323
    exercise it.
1324
1325
    For the pycurl implementation
1326
    the redirection have been deleted as we may deprecate pycurl
1327
    and I have no place to keep a working implementation.
1328
    -- vila 20070212
1329
    """
1330
1331
    def setUp(self):
1332
        if pycurl_present and self._transport == PyCurlTransport:
1333
            raise tests.TestNotApplicable(
1334
                "pycurl doesn't redirect silently annymore")
1335
        super(TestHTTPSilentRedirections, self).setUp()
1336
        self.setup_redirected_request()
1337
        self.addCleanup(self.cleanup_redirected_request)
1338
        self.build_tree_contents([('a','a'),
1339
                                  ('1/',),
1340
                                  ('1/a', 'redirected once'),
1341
                                  ('2/',),
1342
                                  ('2/a', 'redirected twice'),
1343
                                  ('3/',),
1344
                                  ('3/a', 'redirected thrice'),
1345
                                  ('4/',),
1346
                                  ('4/a', 'redirected 4 times'),
1347
                                  ('5/',),
1348
                                  ('5/a', 'redirected 5 times'),
1349
                                  ],)
1350
1351
        self.old_transport = self._transport(self.old_server.get_url())
1352
1353
    def setup_redirected_request(self):
1354
        self.original_class = _urllib2_wrappers.Request
1355
        _urllib2_wrappers.Request = RedirectedRequest
1356
1357
    def cleanup_redirected_request(self):
1358
        _urllib2_wrappers.Request = self.original_class
1359
1360
    def create_transport_secondary_server(self):
1361
        """Create the secondary server, redirections are defined in the tests"""
1362
        return http_utils.HTTPServerRedirecting(
1363
            protocol_version=self._protocol_version)
1364
1365
    def test_one_redirection(self):
1366
        t = self.old_transport
1367
1368
        req = RedirectedRequest('GET', t.abspath('a'))
1369
        req.follow_redirections = True
1370
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1371
                                       self.new_server.port)
1372
        self.old_server.redirections = \
1373
            [('(.*)', r'%s/1\1' % (new_prefix), 301),]
1374
        self.assertEquals('redirected once',t._perform(req).read())
1375
1376
    def test_five_redirections(self):
1377
        t = self.old_transport
1378
1379
        req = RedirectedRequest('GET', t.abspath('a'))
1380
        req.follow_redirections = True
1381
        old_prefix = 'http://%s:%s' % (self.old_server.host,
1382
                                       self.old_server.port)
1383
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1384
                                       self.new_server.port)
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
1385
        self.old_server.redirections = [
1386
            ('/1(.*)', r'%s/2\1' % (old_prefix), 302),
1387
            ('/2(.*)', r'%s/3\1' % (old_prefix), 303),
1388
            ('/3(.*)', r'%s/4\1' % (old_prefix), 307),
1389
            ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
1390
            ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
1391
            ]
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1392
        self.assertEquals('redirected 5 times',t._perform(req).read())
1393
1394
1395
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
1396
    """Test transport.do_catching_redirections."""
1397
1398
    def setUp(self):
1399
        super(TestDoCatchRedirections, self).setUp()
1400
        self.build_tree_contents([('a', '0123456789'),],)
1401
1402
        self.old_transport = self._transport(self.old_server.get_url())
1403
1404
    def get_a(self, transport):
1405
        return transport.get('a')
1406
1407
    def test_no_redirection(self):
1408
        t = self._transport(self.new_server.get_url())
1409
1410
        # We use None for redirected so that we fail if redirected
1411
        self.assertEquals('0123456789',
1412
                          transport.do_catching_redirections(
1413
                self.get_a, t, None).read())
1414
1415
    def test_one_redirection(self):
1416
        self.redirections = 0
1417
1418
        def redirected(transport, exception, redirection_notice):
1419
            self.redirections += 1
1420
            dir, file = urlutils.split(exception.target)
1421
            return self._transport(dir)
1422
1423
        self.assertEquals('0123456789',
1424
                          transport.do_catching_redirections(
1425
                self.get_a, self.old_transport, redirected).read())
1426
        self.assertEquals(1, self.redirections)
1427
1428
    def test_redirection_loop(self):
1429
1430
        def redirected(transport, exception, redirection_notice):
1431
            # By using the redirected url as a base dir for the
1432
            # *old* transport, we create a loop: a => a/a =>
1433
            # a/a/a
1434
            return self.old_transport.clone(exception.target)
1435
1436
        self.assertRaises(errors.TooManyRedirections,
1437
                          transport.do_catching_redirections,
1438
                          self.get_a, self.old_transport, redirected)
1439
1440
1441
class TestAuth(http_utils.TestCaseWithWebserver):
1442
    """Test authentication scheme"""
1443
1444
    _auth_header = 'Authorization'
1445
    _password_prompt_prefix = ''
1446
1447
    def setUp(self):
1448
        super(TestAuth, self).setUp()
1449
        self.server = self.get_readonly_server()
1450
        self.build_tree_contents([('a', 'contents of a\n'),
1451
                                  ('b', 'contents of b\n'),])
1452
1453
    def create_transport_readonly_server(self):
1454
        if self._auth_scheme == 'basic':
1455
            server = http_utils.HTTPBasicAuthServer(
1456
                protocol_version=self._protocol_version)
1457
        else:
1458
            if self._auth_scheme != 'digest':
1459
                raise AssertionError('Unknown auth scheme: %r'
1460
                                     % self._auth_scheme)
1461
            server = http_utils.HTTPDigestAuthServer(
1462
                protocol_version=self._protocol_version)
1463
        return server
1464
1465
    def _testing_pycurl(self):
1466
        return pycurl_present and self._transport == PyCurlTransport
1467
3910.2.4 by Vincent Ladeuil
Fixed as per John's review.
1468
    def get_user_url(self, user, password):
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1469
        """Build an url embedding user and password"""
1470
        url = '%s://' % self.server._url_protocol
1471
        if user is not None:
1472
            url += user
1473
            if password is not None:
1474
                url += ':' + password
1475
            url += '@'
1476
        url += '%s:%s/' % (self.server.host, self.server.port)
1477
        return url
1478
3910.2.4 by Vincent Ladeuil
Fixed as per John's review.
1479
    def get_user_transport(self, user, password):
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1480
        return self._transport(self.get_user_url(user, password))
1481
1482
    def test_no_user(self):
1483
        self.server.add_user('joe', 'foo')
3910.2.4 by Vincent Ladeuil
Fixed as per John's review.
1484
        t = self.get_user_transport(None, None)
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1485
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1486
        # Only one 'Authentication Required' error should occur
1487
        self.assertEqual(1, self.server.auth_required_errors)
1488
1489
    def test_empty_pass(self):
1490
        self.server.add_user('joe', '')
1491
        t = self.get_user_transport('joe', '')
1492
        self.assertEqual('contents of a\n', t.get('a').read())
1493
        # Only one 'Authentication Required' error should occur
1494
        self.assertEqual(1, self.server.auth_required_errors)
1495
1496
    def test_user_pass(self):
1497
        self.server.add_user('joe', 'foo')
1498
        t = self.get_user_transport('joe', 'foo')
1499
        self.assertEqual('contents of a\n', t.get('a').read())
1500
        # Only one 'Authentication Required' error should occur
1501
        self.assertEqual(1, self.server.auth_required_errors)
1502
1503
    def test_unknown_user(self):
1504
        self.server.add_user('joe', 'foo')
1505
        t = self.get_user_transport('bill', 'foo')
1506
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1507
        # Two 'Authentication Required' errors should occur (the
1508
        # initial 'who are you' and 'I don't know you, who are
1509
        # you').
1510
        self.assertEqual(2, self.server.auth_required_errors)
1511
1512
    def test_wrong_pass(self):
1513
        self.server.add_user('joe', 'foo')
1514
        t = self.get_user_transport('joe', 'bar')
1515
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1516
        # Two 'Authentication Required' errors should occur (the
1517
        # initial 'who are you' and 'this is not you, who are you')
1518
        self.assertEqual(2, self.server.auth_required_errors)
1519
1520
    def test_prompt_for_password(self):
1521
        if self._testing_pycurl():
1522
            raise tests.TestNotApplicable(
1523
                'pycurl cannot prompt, it handles auth by embedding'
1524
                ' user:pass in urls only')
1525
1526
        self.server.add_user('joe', 'foo')
1527
        t = self.get_user_transport('joe', None)
1528
        stdout = tests.StringIOWrapper()
1529
        ui.ui_factory = tests.TestUIFactory(stdin='foo\n', stdout=stdout)
1530
        self.assertEqual('contents of a\n',t.get('a').read())
1531
        # stdin should be empty
1532
        self.assertEqual('', ui.ui_factory.stdin.readline())
1533
        self._check_password_prompt(t._unqualified_scheme, 'joe',
1534
                                    stdout.getvalue())
1535
        # And we shouldn't prompt again for a different request
1536
        # against the same transport.
1537
        self.assertEqual('contents of b\n',t.get('b').read())
1538
        t2 = t.clone()
1539
        # And neither against a clone
1540
        self.assertEqual('contents of b\n',t2.get('b').read())
1541
        # Only one 'Authentication Required' error should occur
1542
        self.assertEqual(1, self.server.auth_required_errors)
1543
1544
    def _check_password_prompt(self, scheme, user, actual_prompt):
1545
        expected_prompt = (self._password_prompt_prefix
1546
                           + ("%s %s@%s:%d, Realm: '%s' password: "
1547
                              % (scheme.upper(),
1548
                                 user, self.server.host, self.server.port,
1549
                                 self.server.auth_realm)))
1550
        self.assertEquals(expected_prompt, actual_prompt)
1551
1552
    def test_no_prompt_for_password_when_using_auth_config(self):
1553
        if self._testing_pycurl():
1554
            raise tests.TestNotApplicable(
1555
                'pycurl does not support authentication.conf'
1556
                ' since it cannot prompt')
1557
1558
        user =' joe'
1559
        password = 'foo'
1560
        stdin_content = 'bar\n'  # Not the right password
1561
        self.server.add_user(user, password)
1562
        t = self.get_user_transport(user, None)
1563
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1564
                                            stdout=tests.StringIOWrapper())
1565
        # Create a minimal config file with the right password
1566
        conf = config.AuthenticationConfig()
1567
        conf._get_config().update(
1568
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1569
                          'user': user, 'password': password}})
1570
        conf._save()
1571
        # Issue a request to the server to connect
1572
        self.assertEqual('contents of a\n',t.get('a').read())
1573
        # stdin should have  been left untouched
1574
        self.assertEqual(stdin_content, ui.ui_factory.stdin.readline())
1575
        # Only one 'Authentication Required' error should occur
1576
        self.assertEqual(1, self.server.auth_required_errors)
1577
3910.2.2 by Vincent Ladeuil
Fix bug #300347 by allowing querying authentication.conf if no
1578
    def test_user_from_auth_conf(self):
1579
        if self._testing_pycurl():
1580
            raise tests.TestNotApplicable(
1581
                'pycurl does not support authentication.conf')
3910.2.3 by Ben Jansen
Made tweaks requested by John Arbash Meinel.
1582
        user = 'joe'
3910.2.2 by Vincent Ladeuil
Fix bug #300347 by allowing querying authentication.conf if no
1583
        password = 'foo'
1584
        self.server.add_user(user, password)
1585
        # Create a minimal config file with the right password
1586
        conf = config.AuthenticationConfig()
1587
        conf._get_config().update(
1588
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1589
                          'user': user, 'password': password}})
1590
        conf._save()
3910.2.4 by Vincent Ladeuil
Fixed as per John's review.
1591
        t = self.get_user_transport(None, None)
3910.2.2 by Vincent Ladeuil
Fix bug #300347 by allowing querying authentication.conf if no
1592
        # Issue a request to the server to connect
3910.2.3 by Ben Jansen
Made tweaks requested by John Arbash Meinel.
1593
        self.assertEqual('contents of a\n', t.get('a').read())
3910.2.2 by Vincent Ladeuil
Fix bug #300347 by allowing querying authentication.conf if no
1594
        # Only one 'Authentication Required' error should occur
1595
        self.assertEqual(1, self.server.auth_required_errors)
1596
3111.1.26 by Vincent Ladeuil
Re-add a test lost in refactoring.
1597
    def test_changing_nonce(self):
1598
        if self._auth_scheme != 'digest':
1599
            raise tests.TestNotApplicable('HTTP auth digest only test')
1600
        if self._testing_pycurl():
1601
            raise tests.KnownFailure(
1602
                'pycurl does not handle a nonce change')
1603
        self.server.add_user('joe', 'foo')
1604
        t = self.get_user_transport('joe', 'foo')
1605
        self.assertEqual('contents of a\n', t.get('a').read())
1606
        self.assertEqual('contents of b\n', t.get('b').read())
1607
        # Only one 'Authentication Required' error should have
1608
        # occured so far
1609
        self.assertEqual(1, self.server.auth_required_errors)
1610
        # The server invalidates the current nonce
1611
        self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1612
        self.assertEqual('contents of a\n', t.get('a').read())
1613
        # Two 'Authentication Required' errors should occur (the
1614
        # initial 'who are you' and a second 'who are you' with the new nonce)
1615
        self.assertEqual(2, self.server.auth_required_errors)
1616
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1617
1618
1619
class TestProxyAuth(TestAuth):
1620
    """Test proxy authentication schemes."""
1621
1622
    _auth_header = 'Proxy-authorization'
1623
    _password_prompt_prefix='Proxy '
1624
1625
    def setUp(self):
1626
        super(TestProxyAuth, self).setUp()
1627
        self._old_env = {}
1628
        self.addCleanup(self._restore_env)
1629
        # Override the contents to avoid false positives
1630
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
1631
                                  ('b', 'not proxied contents of b\n'),
1632
                                  ('a-proxied', 'contents of a\n'),
1633
                                  ('b-proxied', 'contents of b\n'),
1634
                                  ])
1635
1636
    def create_transport_readonly_server(self):
1637
        if self._auth_scheme == 'basic':
1638
            server = http_utils.ProxyBasicAuthServer(
1639
                protocol_version=self._protocol_version)
1640
        else:
1641
            if self._auth_scheme != 'digest':
1642
                raise AssertionError('Unknown auth scheme: %r'
1643
                                     % self._auth_scheme)
1644
            server = http_utils.ProxyDigestAuthServer(
1645
                protocol_version=self._protocol_version)
1646
        return server
1647
3910.2.4 by Vincent Ladeuil
Fixed as per John's review.
1648
    def get_user_transport(self, user, password):
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1649
        self._install_env({'all_proxy': self.get_user_url(user, password)})
1650
        return self._transport(self.server.get_url())
1651
1652
    def _install_env(self, env):
1653
        for name, value in env.iteritems():
1654
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1655
1656
    def _restore_env(self):
1657
        for name, value in self._old_env.iteritems():
1658
            osutils.set_or_unset_env(name, value)
1659
1660
    def test_empty_pass(self):
1661
        if self._testing_pycurl():
1662
            import pycurl
1663
            if pycurl.version_info()[1] < '7.16.0':
1664
                raise tests.KnownFailure(
1665
                    'pycurl < 7.16.0 does not handle empty proxy passwords')
1666
        super(TestProxyAuth, self).test_empty_pass()
1667
3111.1.25 by Vincent Ladeuil
Fix the smart server failing test and use it against protocol combinations.
1668
1669
class SampleSocket(object):
1670
    """A socket-like object for use in testing the HTTP request handler."""
1671
1672
    def __init__(self, socket_read_content):
1673
        """Constructs a sample socket.
1674
1675
        :param socket_read_content: a byte sequence
1676
        """
1677
        # Use plain python StringIO so we can monkey-patch the close method to
1678
        # not discard the contents.
1679
        from StringIO import StringIO
1680
        self.readfile = StringIO(socket_read_content)
1681
        self.writefile = StringIO()
1682
        self.writefile.close = lambda: None
1683
1684
    def makefile(self, mode='r', bufsize=None):
1685
        if 'r' in mode:
1686
            return self.readfile
1687
        else:
1688
            return self.writefile
1689
1690
1691
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1692
1693
    def setUp(self):
1694
        super(SmartHTTPTunnellingTest, self).setUp()
1695
        # We use the VFS layer as part of HTTP tunnelling tests.
1696
        self._captureVar('BZR_NO_SMART_VFS', None)
1697
        self.transport_readonly_server = http_utils.HTTPServerWithSmarts
1698
1699
    def create_transport_readonly_server(self):
1700
        return http_utils.HTTPServerWithSmarts(
1701
            protocol_version=self._protocol_version)
1702
3606.4.1 by Andrew Bennetts
Fix NotImplementedError when probing for smart protocol via HTTP.
1703
    def test_open_bzrdir(self):
1704
        branch = self.make_branch('relpath')
1705
        http_server = self.get_readonly_server()
1706
        url = http_server.get_url() + 'relpath'
1707
        bd = bzrdir.BzrDir.open(url)
1708
        self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1709
3111.1.25 by Vincent Ladeuil
Fix the smart server failing test and use it against protocol combinations.
1710
    def test_bulk_data(self):
1711
        # We should be able to send and receive bulk data in a single message.
1712
        # The 'readv' command in the smart protocol both sends and receives
1713
        # bulk data, so we use that.
1714
        self.build_tree(['data-file'])
1715
        http_server = self.get_readonly_server()
1716
        http_transport = self._transport(http_server.get_url())
1717
        medium = http_transport.get_smart_medium()
1718
        # Since we provide the medium, the url below will be mostly ignored
1719
        # during the test, as long as the path is '/'.
1720
        remote_transport = remote.RemoteTransport('bzr://fake_host/',
1721
                                                  medium=medium)
1722
        self.assertEqual(
1723
            [(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
1724
1725
    def test_http_send_smart_request(self):
1726
1727
        post_body = 'hello\n'
3245.4.59 by Andrew Bennetts
Various tweaks in response to Martin's review.
1728
        expected_reply_body = 'ok\x012\n'
3111.1.25 by Vincent Ladeuil
Fix the smart server failing test and use it against protocol combinations.
1729
1730
        http_server = self.get_readonly_server()
1731
        http_transport = self._transport(http_server.get_url())
1732
        medium = http_transport.get_smart_medium()
1733
        response = medium.send_http_smart_request(post_body)
1734
        reply_body = response.read()
1735
        self.assertEqual(expected_reply_body, reply_body)
1736
1737
    def test_smart_http_server_post_request_handler(self):
1738
        httpd = self.get_readonly_server()._get_httpd()
1739
1740
        socket = SampleSocket(
1741
            'POST /.bzr/smart %s \r\n' % self._protocol_version
1742
            # HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
1743
            # for 1.0)
1744
            + 'Content-Length: 6\r\n'
1745
            '\r\n'
1746
            'hello\n')
1747
        # Beware: the ('localhost', 80) below is the
1748
        # client_address parameter, but we don't have one because
1749
        # we have defined a socket which is not bound to an
1750
        # address. The test framework never uses this client
1751
        # address, so far...
1752
        request_handler = http_utils.SmartRequestHandler(socket,
1753
                                                         ('localhost', 80),
1754
                                                         httpd)
1755
        response = socket.writefile.getvalue()
1756
        self.assertStartsWith(response, '%s 200 ' % self._protocol_version)
1757
        # This includes the end of the HTTP headers, and all the body.
3245.4.59 by Andrew Bennetts
Various tweaks in response to Martin's review.
1758
        expected_end_of_response = '\r\n\r\nok\x012\n'
3111.1.25 by Vincent Ladeuil
Fix the smart server failing test and use it against protocol combinations.
1759
        self.assertEndsWith(response, expected_end_of_response)
1760
1761
3430.3.4 by Vincent Ladeuil
Of course we can write tests !
1762
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
1763
    """No smart server here request handler."""
1764
1765
    def do_POST(self):
1766
        self.send_error(403, "Forbidden")
1767
1768
1769
class SmartClientAgainstNotSmartServer(TestSpecificRequestHandler):
1770
    """Test smart client behaviour against an http server without smarts."""
1771
1772
    _req_handler_class = ForbiddenRequestHandler
1773
1774
    def test_probe_smart_server(self):
1775
        """Test error handling against server refusing smart requests."""
1776
        server = self.get_readonly_server()
1777
        t = self._transport(server.get_url())
1778
        # No need to build a valid smart request here, the server will not even
1779
        # try to interpret it.
1780
        self.assertRaises(errors.SmartProtocolError,
3734.3.1 by Vincent Ladeuil
Fix SmartHTTPMedium refactoring related test.
1781
                          t.get_smart_medium().send_http_smart_request,
1782
                          'whatever')
3430.3.4 by Vincent Ladeuil
Of course we can write tests !
1783
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
1784
class Test_redirected_to(tests.TestCase):
1785
1786
    def test_redirected_to_subdir(self):
1787
        t = self._transport('http://www.example.com/foo')
3878.4.5 by Vincent Ladeuil
Don't use the exception as a parameter for _redirected_to.
1788
        r = t._redirected_to('http://www.example.com/foo',
1789
                             'http://www.example.com/foo/subdir')
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
1790
        self.assertIsInstance(r, type(t))
1791
        # Both transports share the some connection
1792
        self.assertEquals(t._get_connection(), r._get_connection())
1793
3878.4.3 by Vincent Ladeuil
Fix bug #303959 by returning a transport based on the same url
1794
    def test_redirected_to_self_with_slash(self):
1795
        t = self._transport('http://www.example.com/foo')
3878.4.5 by Vincent Ladeuil
Don't use the exception as a parameter for _redirected_to.
1796
        r = t._redirected_to('http://www.example.com/foo',
1797
                             'http://www.example.com/foo/')
3878.4.3 by Vincent Ladeuil
Fix bug #303959 by returning a transport based on the same url
1798
        self.assertIsInstance(r, type(t))
1799
        # Both transports share the some connection (one can argue that we
1800
        # should return the exact same transport here, but that seems
1801
        # overkill).
1802
        self.assertEquals(t._get_connection(), r._get_connection())
1803
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
1804
    def test_redirected_to_host(self):
1805
        t = self._transport('http://www.example.com/foo')
3878.4.5 by Vincent Ladeuil
Don't use the exception as a parameter for _redirected_to.
1806
        r = t._redirected_to('http://www.example.com/foo',
1807
                             'http://foo.example.com/foo/subdir')
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
1808
        self.assertIsInstance(r, type(t))
1809
1810
    def test_redirected_to_same_host_sibling_protocol(self):
1811
        t = self._transport('http://www.example.com/foo')
3878.4.5 by Vincent Ladeuil
Don't use the exception as a parameter for _redirected_to.
1812
        r = t._redirected_to('http://www.example.com/foo',
1813
                             'https://www.example.com/foo')
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
1814
        self.assertIsInstance(r, type(t))
1815
1816
    def test_redirected_to_same_host_different_protocol(self):
1817
        t = self._transport('http://www.example.com/foo')
3878.4.5 by Vincent Ladeuil
Don't use the exception as a parameter for _redirected_to.
1818
        r = t._redirected_to('http://www.example.com/foo',
1819
                             'ftp://www.example.com/foo')
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
1820
        self.assertNotEquals(type(r), type(t))
1821
1822
    def test_redirected_to_different_host_same_user(self):
1823
        t = self._transport('http://joe@www.example.com/foo')
3878.4.5 by Vincent Ladeuil
Don't use the exception as a parameter for _redirected_to.
1824
        r = t._redirected_to('http://www.example.com/foo',
1825
                             'https://foo.example.com/foo')
3878.4.2 by Vincent Ladeuil
Fix bug #265070 by providing a finer sieve for accepted redirections.
1826
        self.assertIsInstance(r, type(t))
1827
        self.assertEquals(t._user, r._user)
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1828
1829
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1830
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
1831
    """Request handler for a unique and pre-defined request.
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1832
1833
    The only thing we care about here is how many bytes travel on the wire. But
1834
    since we want to measure it for a real http client, we have to send it
1835
    correct responses.
1836
1837
    We expect to receive a *single* request nothing more (and we won't even
1838
    check what request it is, we just measure the bytes read until an empty
1839
    line.
1840
    """
1841
1842
    def handle_one_request(self):
1843
        tcs = self.server.test_case_server
1844
        requestline = self.rfile.readline()
1845
        headers = self.MessageClass(self.rfile, 0)
1846
        # We just read: the request, the headers, an empty line indicating the
1847
        # end of the headers.
1848
        bytes_read = len(requestline)
1849
        for line in headers.headers:
1850
            bytes_read += len(line)
1851
        bytes_read += len('\r\n')
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1852
        if requestline.startswith('POST'):
1853
            # The body should be a single line (or we don't know where it ends
1854
            # and we don't want to issue a blocking read)
1855
            body = self.rfile.readline()
1856
            bytes_read += len(body)
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1857
        tcs.bytes_read = bytes_read
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1858
1859
        # We set the bytes written *before* issuing the write, the client is
1860
        # supposed to consume every produced byte *before* checking that value.
3945.1.7 by Vincent Ladeuil
Test against https.
1861
1862
        # Doing the oppposite may lead to test failure: we may be interrupted
1863
        # after the write but before updating the value. The client can then
1864
        # continue and read the value *before* we can update it. And yes,
1865
        # this has been observed -- vila 20090129
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1866
        tcs.bytes_written = len(tcs.canned_response)
1867
        self.wfile.write(tcs.canned_response)
1868
1869
1870
class ActivityServerMixin(object):
1871
1872
    def __init__(self, protocol_version):
1873
        super(ActivityServerMixin, self).__init__(
1874
            request_handler=PredefinedRequestHandler,
1875
            protocol_version=protocol_version)
1876
        # Bytes read and written by the server
1877
        self.bytes_read = 0
1878
        self.bytes_written = 0
1879
        self.canned_response = None
1880
1881
1882
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
1883
    pass
1884
1885
1886
if tests.HTTPSServerFeature.available():
1887
    from bzrlib.tests import https_server
1888
    class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
1889
        pass
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1890
1891
1892
class TestActivity(tests.TestCase):
1893
    """Test socket activity reporting.
1894
1895
    We use a special purpose server to control the bytes sent and received and
1896
    be able to predict the activity on the client socket.
1897
    """
1898
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1899
    def setUp(self):
1900
        tests.TestCase.setUp(self)
1901
        self.server = self._activity_server(self._protocol_version)
1902
        self.server.setUp()
1903
        self.activities = {}
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1904
        def report_activity(t, bytes, direction):
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1905
            count = self.activities.get(direction, 0)
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1906
            count += bytes
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1907
            self.activities[direction] = count
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1908
1909
        # We override at class level because constructors may propagate the
1910
        # bound method and render instance overriding ineffective (an
1911
        # alternative would be be to define a specific ui factory instead...)
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1912
        self.orig_report_activity = self._transport._report_activity
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1913
        self._transport._report_activity = report_activity
1914
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1915
    def tearDown(self):
1916
        self._transport._report_activity = self.orig_report_activity
1917
        self.server.tearDown()
1918
        tests.TestCase.tearDown(self)
1919
1920
    def get_transport(self):
1921
        return self._transport(self.server.get_url())
1922
1923
    def assertActivitiesMatch(self):
1924
        self.assertEqual(self.server.bytes_read,
1925
                         self.activities.get('write', 0), 'written bytes')
1926
        self.assertEqual(self.server.bytes_written,
1927
                         self.activities.get('read', 0), 'read bytes')
1928
1929
    def test_get(self):
1930
        self.server.canned_response = '''HTTP/1.1 200 OK\r
1931
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
1932
Server: Apache/2.0.54 (Fedora)\r
1933
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
1934
ETag: "56691-23-38e9ae00"\r
1935
Accept-Ranges: bytes\r
1936
Content-Length: 35\r
1937
Connection: close\r
1938
Content-Type: text/plain; charset=UTF-8\r
1939
\r
1940
Bazaar-NG meta directory, format 1
1941
'''
1942
        t = self.get_transport()
3945.1.5 by Vincent Ladeuil
Start implementing http activity reporting at socket level.
1943
        self.assertEqual('Bazaar-NG meta directory, format 1\n',
1944
                         t.get('foo/bar').read())
3945.1.8 by Vincent Ladeuil
Add more tests, fix pycurl double handling, revert previous tracking.
1945
        self.assertActivitiesMatch()
1946
1947
    def test_has(self):
1948
        self.server.canned_response = '''HTTP/1.1 200 OK\r
1949
Server: SimpleHTTP/0.6 Python/2.5.2\r
1950
Date: Thu, 29 Jan 2009 20:21:47 GMT\r
1951
Content-type: application/octet-stream\r
1952
Content-Length: 20\r
1953
Last-Modified: Thu, 29 Jan 2009 20:21:47 GMT\r
1954
\r
1955
'''
1956
        t = self.get_transport()
1957
        self.assertTrue(t.has('foo/bar'))
1958
        self.assertActivitiesMatch()
1959
1960
    def test_readv(self):
1961
        self.server.canned_response = '''HTTP/1.1 206 Partial Content\r
1962
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
1963
Server: Apache/2.0.54 (Fedora)\r
1964
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
1965
ETag: "238a3c-16ec2-805c5540"\r
1966
Accept-Ranges: bytes\r
1967
Content-Length: 1534\r
1968
Connection: close\r
1969
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
1970
\r
1971
\r
1972
--418470f848b63279b\r
1973
Content-type: text/plain; charset=UTF-8\r
1974
Content-range: bytes 0-254/93890\r
1975
\r
1976
mbp@sourcefrog.net-20050309040815-13242001617e4a06
1977
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e7627
1978
mbp@sourcefrog.net-20050309040957-6cad07f466bb0bb8
1979
mbp@sourcefrog.net-20050309041501-c840e09071de3b67
1980
mbp@sourcefrog.net-20050309044615-c24a3250be83220a
1981
\r
1982
--418470f848b63279b\r
1983
Content-type: text/plain; charset=UTF-8\r
1984
Content-range: bytes 1000-2049/93890\r
1985
\r
1986
40-fd4ec249b6b139ab
1987
mbp@sourcefrog.net-20050311063625-07858525021f270b
1988
mbp@sourcefrog.net-20050311231934-aa3776aff5200bb9
1989
mbp@sourcefrog.net-20050311231953-73aeb3a131c3699a
1990
mbp@sourcefrog.net-20050311232353-f5e33da490872c6a
1991
mbp@sourcefrog.net-20050312071639-0a8f59a34a024ff0
1992
mbp@sourcefrog.net-20050312073432-b2c16a55e0d6e9fb
1993
mbp@sourcefrog.net-20050312073831-a47c3335ece1920f
1994
mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
1995
mbp@sourcefrog.net-20050313052251-2bf004cb96b39933
1996
mbp@sourcefrog.net-20050313052856-3edd84094687cb11
1997
mbp@sourcefrog.net-20050313053233-e30a4f28aef48f9d
1998
mbp@sourcefrog.net-20050313053853-7c64085594ff3072
1999
mbp@sourcefrog.net-20050313054757-a86c3f5871069e22
2000
mbp@sourcefrog.net-20050313061422-418f1f73b94879b9
2001
mbp@sourcefrog.net-20050313120651-497bd231b19df600
2002
mbp@sourcefrog.net-20050314024931-eae0170ef25a5d1a
2003
mbp@sourcefrog.net-20050314025438-d52099f915fe65fc
2004
mbp@sourcefrog.net-20050314025539-637a636692c055cf
2005
mbp@sourcefrog.net-20050314025737-55eb441f430ab4ba
2006
mbp@sourcefrog.net-20050314025901-d74aa93bb7ee8f62
2007
mbp@source\r
2008
--418470f848b63279b--\r
2009
'''
2010
        t = self.get_transport()
2011
        # Remember that the request is ignored and that the ranges below
2012
        # doesn't have to match the canned response.
2013
        l = list(t.readv('/foo/bar', ((0, 255), (1000, 1050))))
2014
        self.assertEqual(2, len(l))
2015
        self.assertActivitiesMatch()
2016
2017
    def test_post(self):
2018
        self.server.canned_response = '''HTTP/1.1 200 OK\r
2019
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2020
Server: Apache/2.0.54 (Fedora)\r
2021
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2022
ETag: "56691-23-38e9ae00"\r
2023
Accept-Ranges: bytes\r
2024
Content-Length: 35\r
2025
Connection: close\r
2026
Content-Type: text/plain; charset=UTF-8\r
2027
\r
2028
lalala whatever as long as itsssss
2029
'''
2030
        t = self.get_transport()
2031
        # We must send a single line of body bytes, see
2032
        # PredefinedRequestHandler.handle_one_request
2033
        code, f = t._post('abc def end-of-body\n')
2034
        self.assertEqual('lalala whatever as long as itsssss\n', f.read())
2035
        self.assertActivitiesMatch()