/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

Merge from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 Canonical
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
# FIXME: This test should be repeated for each available http client
 
18
# implementation; at the moment we have urllib and pycurl.
 
19
 
 
20
# TODO: Should be renamed to bzrlib.transport.http.tests?
 
21
 
 
22
import errno
 
23
import select
 
24
import socket
 
25
import threading
 
26
 
 
27
import bzrlib
 
28
from bzrlib.errors import DependencyNotPresent, UnsupportedProtocol
 
29
from bzrlib.tests import TestCase, TestSkipped
 
30
from bzrlib.transport import get_transport, Transport
 
31
from bzrlib.transport.http import extract_auth, HttpTransportBase
 
32
from bzrlib.transport.http._urllib import HttpTransport_urllib
 
33
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
 
34
 
 
35
 
 
36
class FakeManager(object):
 
37
 
 
38
    def __init__(self):
 
39
        self.credentials = []
 
40
        
 
41
    def add_password(self, realm, host, username, password):
 
42
        self.credentials.append([realm, host, username, password])
 
43
 
 
44
 
 
45
class RecordingServer(object):
 
46
    """A fake HTTP server.
 
47
    
 
48
    It records the bytes sent to it, and replies with a 200.
 
49
    """
 
50
 
 
51
    def __init__(self, expect_body_tail=None):
 
52
        self._expect_body_tail = expect_body_tail
 
53
        self.host = None
 
54
        self.port = None
 
55
        self.received_bytes = ''
 
56
 
 
57
    def setUp(self):
 
58
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
59
        self._sock.bind(('127.0.0.1', 0))
 
60
        self.host, self.port = self._sock.getsockname()
 
61
        self._ready = threading.Event()
 
62
        self._thread = threading.Thread(target=self._accept_read_and_reply)
 
63
        self._thread.setDaemon(True)
 
64
        self._thread.start()
 
65
        self._ready.wait(5)
 
66
 
 
67
    def _accept_read_and_reply(self):
 
68
        self._sock.listen(1)
 
69
        self._ready.set()
 
70
        self._sock.settimeout(5)
 
71
        try:
 
72
            conn, address = self._sock.accept()
 
73
            # On win32, the accepted connection will be non-blocking to start
 
74
            # with because we're using settimeout.
 
75
            conn.setblocking(True)
 
76
            while not self.received_bytes.endswith(self._expect_body_tail):
 
77
                self.received_bytes += conn.recv(4096)
 
78
            conn.sendall('HTTP/1.1 200 OK\r\n')
 
79
        except socket.timeout:
 
80
            # Make sure the client isn't stuck waiting for us to e.g. accept.
 
81
            self._sock.close()
 
82
 
 
83
    def tearDown(self):
 
84
        try:
 
85
            self._sock.close()
 
86
        except socket.error:
 
87
            # We might have already closed it.  We don't care.
 
88
            pass
 
89
        self.host = None
 
90
        self.port = None
 
91
 
 
92
 
 
93
class TestHttpUrls(TestCase):
 
94
 
 
95
    def test_url_parsing(self):
 
96
        f = FakeManager()
 
97
        url = extract_auth('http://example.com', f)
 
98
        self.assertEquals('http://example.com', url)
 
99
        self.assertEquals(0, len(f.credentials))
 
100
        url = extract_auth('http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
 
101
        self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
 
102
        self.assertEquals(1, len(f.credentials))
 
103
        self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'], f.credentials[0])
 
104
        
 
105
    def test_abs_url(self):
 
106
        """Construction of absolute http URLs"""
 
107
        t = HttpTransport_urllib('http://bazaar-vcs.org/bzr/bzr.dev/')
 
108
        eq = self.assertEqualDiff
 
109
        eq(t.abspath('.'),
 
110
           'http://bazaar-vcs.org/bzr/bzr.dev')
 
111
        eq(t.abspath('foo/bar'), 
 
112
           'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
 
113
        eq(t.abspath('.bzr'),
 
114
           'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
 
115
        eq(t.abspath('.bzr/1//2/./3'),
 
116
           'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
 
117
 
 
118
    def test_invalid_http_urls(self):
 
119
        """Trap invalid construction of urls"""
 
120
        t = HttpTransport_urllib('http://bazaar-vcs.org/bzr/bzr.dev/')
 
121
        self.assertRaises(ValueError,
 
122
            t.abspath,
 
123
            '.bzr/')
 
124
 
 
125
    def test_http_root_urls(self):
 
126
        """Construction of URLs from server root"""
 
127
        t = HttpTransport_urllib('http://bzr.ozlabs.org/')
 
128
        eq = self.assertEqualDiff
 
129
        eq(t.abspath('.bzr/tree-version'),
 
130
           'http://bzr.ozlabs.org/.bzr/tree-version')
 
131
 
 
132
    def test_http_impl_urls(self):
 
133
        """There are servers which ask for particular clients to connect"""
 
134
        try:
 
135
            from bzrlib.transport.http._pycurl import HttpServer_PyCurl
 
136
            server = HttpServer_PyCurl()
 
137
            try:
 
138
                server.setUp()
 
139
                url = server.get_url()
 
140
                self.assertTrue(url.startswith('http+pycurl://'))
 
141
            finally:
 
142
                server.tearDown()
 
143
        except DependencyNotPresent:
 
144
            raise TestSkipped('pycurl not present')
 
145
 
 
146
 
 
147
class TestHttpMixins(object):
 
148
 
 
149
    def _prep_tree(self):
 
150
        self.build_tree(['xxx', 'foo/', 'foo/bar'], line_endings='binary',
 
151
                        transport=self.get_transport())
 
152
 
 
153
    def test_http_has(self):
 
154
        server = self.get_readonly_server()
 
155
        t = self._transport(server.get_url())
 
156
        self.assertEqual(t.has('foo/bar'), True)
 
157
        self.assertEqual(len(server.logs), 1)
 
158
        self.assertContainsRe(server.logs[0], 
 
159
            r'"HEAD /foo/bar HTTP/1.." (200|302) - "-" "bzr/')
 
160
 
 
161
    def test_http_has_not_found(self):
 
162
        server = self.get_readonly_server()
 
163
        t = self._transport(server.get_url())
 
164
        self.assertEqual(t.has('not-found'), False)
 
165
        self.assertContainsRe(server.logs[1], 
 
166
            r'"HEAD /not-found HTTP/1.." 404 - "-" "bzr/')
 
167
 
 
168
    def test_http_get(self):
 
169
        server = self.get_readonly_server()
 
170
        t = self._transport(server.get_url())
 
171
        fp = t.get('foo/bar')
 
172
        self.assertEqualDiff(
 
173
            fp.read(),
 
174
            'contents of foo/bar\n')
 
175
        self.assertEqual(len(server.logs), 1)
 
176
        self.assertTrue(server.logs[0].find(
 
177
            '"GET /foo/bar HTTP/1.1" 200 - "-" "bzr/%s' % bzrlib.__version__) > -1)
 
178
 
 
179
    def test_get_smart_medium(self):
 
180
        # For HTTP, get_smart_medium should return the transport object.
 
181
        server = self.get_readonly_server()
 
182
        http_transport = self._transport(server.get_url())
 
183
        medium = http_transport.get_smart_medium()
 
184
        self.assertTrue(medium is http_transport)
 
185
        
 
186
 
 
187
class TestHttpConnections_urllib(TestCaseWithWebserver, TestHttpMixins):
 
188
 
 
189
    _transport = HttpTransport_urllib
 
190
 
 
191
    def setUp(self):
 
192
        TestCaseWithWebserver.setUp(self)
 
193
        self._prep_tree()
 
194
 
 
195
    def test_has_on_bogus_host(self):
 
196
        import urllib2
 
197
        # Get a random address, so that we can be sure there is no
 
198
        # http handler there.
 
199
        s = socket.socket()
 
200
        s.bind(('localhost', 0))
 
201
        t = self._transport('http://%s:%s/' % s.getsockname())
 
202
        self.assertRaises(urllib2.URLError, t.has, 'foo/bar')
 
203
 
 
204
 
 
205
class TestHttpConnections_pycurl(TestCaseWithWebserver, TestHttpMixins):
 
206
 
 
207
    def _get_pycurl_maybe(self):
 
208
        try:
 
209
            from bzrlib.transport.http._pycurl import PyCurlTransport
 
210
            return PyCurlTransport
 
211
        except DependencyNotPresent:
 
212
            raise TestSkipped('pycurl not present')
 
213
 
 
214
    _transport = property(_get_pycurl_maybe)
 
215
 
 
216
    def setUp(self):
 
217
        TestCaseWithWebserver.setUp(self)
 
218
        self._prep_tree()
 
219
 
 
220
 
 
221
class TestHttpTransportRegistration(TestCase):
 
222
    """Test registrations of various http implementations"""
 
223
 
 
224
    def test_http_registered(self):
 
225
        import bzrlib.transport.http._urllib
 
226
        from bzrlib.transport import get_transport
 
227
        # urlllib should always be present
 
228
        t = get_transport('http+urllib://bzr.google.com/')
 
229
        self.assertIsInstance(t, Transport)
 
230
        self.assertIsInstance(t, bzrlib.transport.http._urllib.HttpTransport_urllib)
 
231
 
 
232
 
 
233
class TestOffsets(TestCase):
 
234
    """Test offsets_to_ranges method"""
 
235
 
 
236
    def test_offsets_to_ranges_simple(self):
 
237
        to_range = HttpTransportBase.offsets_to_ranges
 
238
        ranges = to_range([(10, 1)])
 
239
        self.assertEqual([[10, 10]], ranges)
 
240
 
 
241
        ranges = to_range([(0, 1), (1, 1)])
 
242
        self.assertEqual([[0, 1]], ranges)
 
243
 
 
244
        ranges = to_range([(1, 1), (0, 1)])
 
245
        self.assertEqual([[0, 1]], ranges)
 
246
 
 
247
    def test_offset_to_ranges_overlapped(self):
 
248
        to_range = HttpTransportBase.offsets_to_ranges
 
249
 
 
250
        ranges = to_range([(10, 1), (20, 2), (22, 5)])
 
251
        self.assertEqual([[10, 10], [20, 26]], ranges)
 
252
 
 
253
        ranges = to_range([(10, 1), (11, 2), (22, 5)])
 
254
        self.assertEqual([[10, 12], [22, 26]], ranges)
 
255
 
 
256
 
 
257
class TestPost(TestCase):
 
258
 
 
259
    def _test_post_body_is_received(self, scheme):
 
260
        server = RecordingServer(expect_body_tail='end-of-body')
 
261
        server.setUp()
 
262
        self.addCleanup(server.tearDown)
 
263
        url = '%s://%s:%s/' % (scheme, server.host, server.port)
 
264
        try:
 
265
            http_transport = get_transport(url)
 
266
        except UnsupportedProtocol:
 
267
            raise TestSkipped('%s not available' % scheme)
 
268
        code, response = http_transport._post('abc def end-of-body')
 
269
        self.assertTrue(
 
270
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
 
271
        self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
 
272
        # The transport should not be assuming that the server can accept
 
273
        # chunked encoding the first time it connects, because HTTP/1.1, so we
 
274
        # check for the literal string.
 
275
        self.assertTrue(
 
276
            server.received_bytes.endswith('\r\n\r\nabc def end-of-body'))
 
277
 
 
278
    def test_post_body_is_received_urllib(self):
 
279
        self._test_post_body_is_received('http+urllib')
 
280
 
 
281
    def test_post_body_is_received_pycurl(self):
 
282
        self._test_post_body_is_received('http+pycurl')
 
283
 
 
284
 
 
285
class TestRangeHeader(TestCase):
 
286
    """Test range_header method"""
 
287
 
 
288
    def check_header(self, value, ranges=[], tail=0):
 
289
        range_header = HttpTransportBase.range_header
 
290
        self.assertEqual(value, range_header(ranges, tail))
 
291
 
 
292
    def test_range_header_single(self):
 
293
        self.check_header('0-9', ranges=[[0,9]])
 
294
        self.check_header('100-109', ranges=[[100,109]])
 
295
 
 
296
    def test_range_header_tail(self):
 
297
        self.check_header('-10', tail=10)
 
298
        self.check_header('-50', tail=50)
 
299
 
 
300
    def test_range_header_multi(self):
 
301
        self.check_header('0-9,100-200,300-5000',
 
302
                          ranges=[(0,9), (100, 200), (300,5000)])
 
303
 
 
304
    def test_range_header_mixed(self):
 
305
        self.check_header('0-9,300-5000,-50',
 
306
                          ranges=[(0,9), (300,5000)],
 
307
                          tail=50)
 
308
 
 
309
        
 
310
class TestRecordingServer(TestCase):
 
311
 
 
312
    def test_create(self):
 
313
        server = RecordingServer(expect_body_tail=None)
 
314
        self.assertEqual('', server.received_bytes)
 
315
        self.assertEqual(None, server.host)
 
316
        self.assertEqual(None, server.port)
 
317
 
 
318
    def test_setUp_and_tearDown(self):
 
319
        server = RecordingServer(expect_body_tail=None)
 
320
        server.setUp()
 
321
        try:
 
322
            self.assertNotEqual(None, server.host)
 
323
            self.assertNotEqual(None, server.port)
 
324
        finally:
 
325
            server.tearDown()
 
326
        self.assertEqual(None, server.host)
 
327
        self.assertEqual(None, server.port)
 
328
 
 
329
    def test_send_receive_bytes(self):
 
330
        server = RecordingServer(expect_body_tail='c')
 
331
        server.setUp()
 
332
        self.addCleanup(server.tearDown)
 
333
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
334
        sock.connect((server.host, server.port))
 
335
        sock.sendall('abc')
 
336
        self.assertEqual('HTTP/1.1 200 OK\r\n',
 
337
                         sock.recv(4096, socket.MSG_WAITALL))
 
338
        self.assertEqual('abc', server.received_bytes)