/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
1
# Copyright (C) 2006 Canonical Ltd
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
"""Tests for smart transport"""
18
19
# all of this deals with byte strings so this is safe
20
from cStringIO import StringIO
21
import subprocess
22
import sys
23
24
import bzrlib
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
25
from bzrlib import (
26
        bzrdir,
27
        errors,
28
        tests,
29
        )
30
from bzrlib.transport import (
31
        get_transport,
32
        local,
33
        memory,
34
        smart,
35
        )
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
36
37
38
class SmartClientTests(tests.TestCase):
39
40
    def test_construct_smart_stream_client(self):
1910.19.3 by Andrew Bennetts
Add SSH support.
41
        # make a new client; this really wants a connector function that returns
42
        # two fifos or sockets but the constructor should not do any IO
43
        client = smart.SmartStreamClient(None)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
44
45
46
class TCPClientTests(tests.TestCaseWithTransport):
47
48
    def setUp(self):
49
        super(TCPClientTests, self).setUp()
50
        # We're allowed to set  the transport class here, so that we don't use
51
        # the default or a parameterized class, but rather use the
52
        # TestCaseWithTransport infrastructure to set up a smart server and
53
        # transport.
54
        self.transport_server = smart.SmartTCPServer_for_testing
55
56
    def test_plausible_url(self):
57
        self.assert_(self.get_url().startswith('bzr://'))
58
59
    def test_probe_transport(self):
60
        t = self.get_transport()
61
        self.assertIsInstance(t, smart.SmartTransport)
62
63
    def test_get_client_from_transport(self):
64
        t = self.get_transport()
65
        client = t.get_smart_client()
66
        self.assertIsInstance(client, smart.SmartStreamClient)
67
68
69
class BasicSmartTests(tests.TestCase):
70
    
71
    def test_smart_query_version(self):
72
        """Feed a canned query version to a server"""
73
        to_server = StringIO('hello\n')
74
        from_server = StringIO()
75
        server = smart.SmartStreamServer(to_server, from_server, local.LocalTransport('file:///'))
76
        server._serve_one_request()
77
        self.assertEqual('ok\0011\n',
78
                         from_server.getvalue())
79
80
    def test_canned_get_response(self):
81
        transport = memory.MemoryTransport('memory:///')
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
82
        transport.put_bytes('testfile', 'contents\nof\nfile\n')
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
83
        to_server = StringIO('get\001./testfile\n')
84
        from_server = StringIO()
85
        server = smart.SmartStreamServer(to_server, from_server, transport)
86
        server._serve_one_request()
87
        self.assertEqual('ok\n'
88
                         '17\n'
89
                         'contents\nof\nfile\n'
90
                         'done\n',
91
                         from_server.getvalue())
92
93
    def test_get_error_unexpected(self):
94
        """Error reported by server with no specific representation"""
95
        class FlakyTransport(object):
1910.19.14 by Robert Collins
Fix up all tests to pass, remove a couple more deprecated function calls, and break the dependency on sftp for the smart transport.
96
            def get_bytes(self, path):
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
97
                raise Exception("some random exception from inside server")
98
        server = smart.SmartTCPServer(backing_transport=FlakyTransport())
99
        server.start_background_thread()
100
        try:
101
            transport = smart.SmartTCPTransport(server.get_url())
102
            try:
103
                transport.get('something')
104
            except errors.TransportError, e:
105
                self.assertContainsRe(str(e), 'some random exception')
106
            else:
107
                self.fail("get did not raise expected error")
108
        finally:
109
            server.stop_background_thread()
110
111
    def test_server_subprocess(self):
112
        """Talk to a server started as a subprocess
113
        
114
        This is similar to running it over ssh, except that it runs in the same machine 
115
        without ssh intermediating.
116
        """
117
        args = [sys.executable, sys.argv[0], 'serve', '--inet']
118
        child = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
119
                                 close_fds=True)
1910.19.3 by Andrew Bennetts
Add SSH support.
120
        conn = smart.SmartStreamClient(lambda: (child.stdout, child.stdin))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
121
        conn.query_version()
122
        conn.query_version()
123
        conn.disconnect()
124
        returncode = child.wait()
125
        self.assertEquals(0, returncode)
126
127
128
class SmartTCPTests(tests.TestCase):
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
129
    """Tests for connection/end to end behaviour using the TCP server.
130
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
131
    All of these tests are run with a server running on another thread serving
132
    a MemoryTransport, and a connection to it already open.
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
133
134
    the server is obtained by calling self.setUpServer(readonly=False).
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
135
    """
136
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
137
    def setUpServer(self, readonly=False):
138
        """Setup the server.
139
140
        :param readonly: Create a readonly server.
141
        """
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
142
        self.backing_transport = memory.MemoryTransport()
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
143
        if readonly:
144
            self.real_backing_transport = self.backing_transport
145
            self.backing_transport = get_transport("readonly+" + self.backing_transport.abspath('.'))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
146
        self.server = smart.SmartTCPServer(self.backing_transport)
147
        self.server.start_background_thread()
148
        self.transport = smart.SmartTCPTransport(self.server.get_url())
149
150
    def tearDown(self):
151
        if getattr(self, 'transport', None):
152
            self.transport.disconnect()
153
        if getattr(self, 'server', None):
154
            self.server.stop_background_thread()
155
        super(SmartTCPTests, self).tearDown()
156
        
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
157
158
class WritableEndToEndTests(SmartTCPTests):
159
    """Client to server tests that require a writable transport."""
160
161
    def setUp(self):
162
        super(WritableEndToEndTests, self).setUp()
163
        self.setUpServer()
164
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
165
    def test_start_tcp_server(self):
166
        url = self.server.get_url()
167
        self.assertContainsRe(url, r'^bzr://127\.0\.0\.1:[0-9]{2,}/')
168
169
    def test_smart_transport_has(self):
170
        """Checking for file existence over smart."""
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
171
        self.backing_transport.put_bytes("foo", "contents of foo\n")
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
172
        self.assertTrue(self.transport.has("foo"))
173
        self.assertFalse(self.transport.has("non-foo"))
174
175
    def test_smart_transport_get(self):
176
        """Read back a file over smart."""
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
177
        self.backing_transport.put_bytes("foo", "contents\nof\nfoo\n")
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
178
        fp = self.transport.get("foo")
179
        self.assertEqual('contents\nof\nfoo\n', fp.read())
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
180
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
181
    def test_get_error_enoent(self):
182
        """Error reported from server getting nonexistent file."""
1910.19.3 by Andrew Bennetts
Add SSH support.
183
        # The path in a raised NoSuchFile exception should be the precise path
184
        # asked for by the client. This gives meaningful and unsurprising errors
185
        # for users.
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
186
        try:
1910.19.3 by Andrew Bennetts
Add SSH support.
187
            self.transport.get('not%20a%20file')
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
188
        except errors.NoSuchFile, e:
1910.19.3 by Andrew Bennetts
Add SSH support.
189
            self.assertEqual('not%20a%20file', e.path)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
190
        else:
191
            self.fail("get did not raise expected error")
192
193
    def test_simple_clone_conn(self):
194
        """Test that cloning reuses the same connection."""
195
        # we create a real connection not a loopback one, but it will use the
196
        # same server and pipes
1910.19.3 by Andrew Bennetts
Add SSH support.
197
        conn2 = self.transport.clone('.')
1910.19.12 by Andrew Bennetts
Activate a disabled test, rename another test to be consistent with what it's testing. (Andrew Bennetts, Robert Collins)
198
        self.assertTrue(self.transport._client is conn2._client)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
199
1910.19.12 by Andrew Bennetts
Activate a disabled test, rename another test to be consistent with what it's testing. (Andrew Bennetts, Robert Collins)
200
    def test__remote_path(self):
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
201
        self.assertEquals('/foo/bar',
202
                          self.transport._remote_path('foo/bar'))
203
204
    def test_clone_changes_base(self):
205
        """Cloning transport produces one with a new base location"""
206
        conn2 = self.transport.clone('subdir')
207
        self.assertEquals(self.transport.base + 'subdir/',
208
                          conn2.base)
209
210
    def test_open_dir(self):
211
        """Test changing directory"""
212
        transport = self.transport
213
        self.backing_transport.mkdir('toffee')
214
        self.backing_transport.mkdir('toffee/apple')
215
        self.assertEquals('/toffee', transport._remote_path('toffee'))
1910.19.13 by Andrew Bennetts
Address various review comments.
216
        toffee_trans = transport.clone('toffee')
217
        # Check that each transport has only the contents of its directory
218
        # directly visible. If state was being held in the wrong object, it's
219
        # conceivable that cloning a transport would alter the state of the
220
        # cloned-from transport.
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
221
        self.assertTrue(transport.has('toffee'))
1910.19.13 by Andrew Bennetts
Address various review comments.
222
        self.assertFalse(toffee_trans.has('toffee'))
223
        self.assertFalse(transport.has('apple'))
224
        self.assertTrue(toffee_trans.has('apple'))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
225
226
    def test_open_bzrdir(self):
227
        """Open an existing bzrdir over smart transport"""
228
        transport = self.transport
229
        t = self.backing_transport
230
        bzrdir.BzrDirFormat.get_default_format().initialize_on_transport(t)
231
        result_dir = bzrdir.BzrDir.open_containing_from_transport(transport)
232
233
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
234
class ReadOnlyEndToEndTests(SmartTCPTests):
235
    """Tests from the client to the server using a readonly backing transport."""
236
237
    def test_mkdir_error_readonly(self):
238
        """TransportNotPossible should be preserved from the backing transport."""
239
        self.setUpServer(readonly=True)
240
        self.assertRaises(errors.TransportNotPossible, self.transport.mkdir,
241
            'foo')
242
        
243
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
244
class SmartServerTests(tests.TestCaseWithTransport):
245
    """Test that call directly into the server logic, bypassing the network."""
246
247
    def test_hello(self):
248
        server = smart.SmartServer(None)
249
        response = server.dispatch_command('hello', ())
250
        self.assertEqual(('ok', '1'), response.args)
251
        self.assertEqual(None, response.body)
252
        
253
    def test_get_bundle(self):
254
        from bzrlib.bundle import serializer
255
        wt = self.make_branch_and_tree('.')
1910.19.13 by Andrew Bennetts
Address various review comments.
256
        self.build_tree_contents([('hello', 'hello world')])
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
257
        wt.add('hello')
1910.19.13 by Andrew Bennetts
Address various review comments.
258
        rev_id = wt.commit('add hello')
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
259
        
260
        server = smart.SmartServer(self.get_transport())
1910.19.13 by Andrew Bennetts
Address various review comments.
261
        response = server.dispatch_command('get_bundle', ('.', rev_id))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
262
        bundle = serializer.read_bundle(StringIO(response.body))
263
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
264
    def test_readonly_exception_becomes_transport_not_possible(self):
265
        """The response for a read-only error is ('ReadOnlyError')."""
266
        server = smart.SmartServer(self.get_readonly_transport())
267
        # send a mkdir for foo, with no explicit mode - should fail.
268
        response = server.dispatch_command('mkdir', ('foo', ''))
269
        # and the failure should be an explicit ReadOnlyError
270
        self.assertEqual(("ReadOnlyError", ), response.args)
271
        # XXX: TODO: test that other TransportNotPossible errors are
272
        # presented as TransportNotPossible - not possible to do that
273
        # until I figure out how to trigger that relatively cleanly via
274
        # the api. RBC 20060918
275
1910.19.3 by Andrew Bennetts
Add SSH support.
276
277
class SmartTransportRegistration(tests.TestCase):
278
279
    def test_registration(self):
280
        t = get_transport('bzr+ssh://example.com/path')
281
        self.assertIsInstance(t, smart.SmartSSHTransport)
282
        self.assertEqual('example.com', t._host)
283
284
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
285
class FakeClient(smart.SmartStreamClient):
286
    """Emulate a client for testing a transport's use of the client."""
287
288
    def __init__(self):
289
        smart.SmartStreamClient.__init__(self, None)
290
        self._calls = []
291
292
    def _call(self, *args):
293
        self._calls.append(('_call', args))
294
        return ('ok', )
295
296
    def _recv_bulk(self):
297
        return 'bar'
298
299
300
class TestSmartTransport(tests.TestCase):
301
        
302
    def test_use_connection_factory(self):
303
        # We want to be able to pass a client as a parameter to SmartTransport.
304
        client = FakeClient()
305
        transport = smart.SmartTransport('bzr://localhost/', client=client)
306
307
        # We want to make sure the client is used when the first remote
308
        # method is called.  No method should have been called yet.
309
        self.assertEqual([], client._calls)
310
311
        # Now call a method that should result in a single request.
312
        self.assertEqual('bar', transport.get_bytes('foo'))
313
        # The only call to _call should have been to get /foo.
314
        self.assertEqual([('_call', ('get', '/foo'))], client._calls)
315
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
316
    def test__translate_error_readonly(self):
317
        """Sending a ReadOnlyError to _translate_error raises TransportNotPossible."""
318
        client = FakeClient()
319
        transport = smart.SmartTransport('bzr://localhost/', client=client)
320
        self.assertRaises(errors.TransportNotPossible,
321
            transport._translate_error, ("ReadOnlyError", ))
322
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
323
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
324
class InstrumentedClient(smart.SmartStreamClient):
325
    """A smart client whose writes are stored to a supplied list."""
326
327
    def __init__(self, write_output_list):
328
        smart.SmartStreamClient.__init__(self, None)
329
        self._write_output_list = write_output_list
330
331
    def _ensure_connection(self):
332
        """We are never strictly connected."""
333
334
    def _write_and_flush(self, bytes):
335
        self._write_output_list.append(bytes)
336
337
338
class InstrumentedServerProtocol(smart.SmartStreamServer):
339
    """A smart server which is backed by memory and saves its write requests."""
340
341
    def __init__(self, write_output_list):
342
        smart.SmartStreamServer.__init__(self, None, None,
343
            memory.MemoryTransport())
344
        self._write_output_list = write_output_list
345
346
    def _write_and_flush(self, bytes):
347
        self._write_output_list.append(bytes)
348
349
350
class TestSmartProtocol(tests.TestCase):
351
    """Tests for the smart protocol.
352
353
    Each test case gets a smart_server and smart_client created during setUp().
354
355
    It is planned that the client can be called with self.call_client() giving
356
    it an expected server response, which will be fed into it when it tries to
357
    read. Likewise, self.call_server will call a servers method with a canned
358
    serialised client request. Output done by the client or server for these
359
    calls will be captured to self.to_server and self.to_client. Each element
360
    in the list is a write call from the client or server respectively.
361
    """
362
363
    def setUp(self):
364
        super(TestSmartProtocol, self).setUp()
365
        self.to_server = []
366
        self.to_client = []
367
        self.smart_client = InstrumentedClient(self.to_server)
368
        self.smart_server = InstrumentedServerProtocol(self.to_client)
369
370
    def assertOffsetSerialisation(self, expected_offsets, expected_serialised,
371
        client, server_protocol):
372
        """Check that smart (de)serialises offsets as expected.
373
        
374
        We check both serialisation and deserialisation at the same time
375
        to ensure that the round tripping cannot skew: both directions should
376
        be as expected.
377
        
378
        :param expected_offsets: a readv offset list.
379
        :param expected_seralised: an expected serial form of the offsets.
380
        :param server: a SmartServer instance.
381
        """
382
        offsets = server_protocol.smart_server._deserialise_offsets(
383
            expected_serialised)
384
        self.assertEqual(expected_offsets, offsets)
385
        serialised = client._serialise_offsets(offsets)
386
        self.assertEqual(expected_serialised, serialised)
387
388
    def test_server_offset_serialisation(self):
389
        """The Smart protocol serialises offsets as a comma and \n string.
390
391
        We check a number of boundary cases are as expected: empty, one offset,
392
        one with the order of reads not increasing (an out of order read), and
393
        one that should coalesce.
394
        """
395
        self.assertOffsetSerialisation([], '',
396
            self.smart_client, self.smart_server)
397
        self.assertOffsetSerialisation([(1,2)], '1,2',
398
            self.smart_client, self.smart_server)
399
        self.assertOffsetSerialisation([(10,40), (0,5)], '10,40\n0,5',
400
            self.smart_client, self.smart_server)
401
        self.assertOffsetSerialisation([(1,2), (3,4), (100, 200)],
402
            '1,2\n3,4\n100,200', self.smart_client, self.smart_server)
403
404
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
405
# TODO: Client feature that does get_bundle and then installs that into a
406
# branch; this can be used in place of the regular pull/fetch operation when
407
# coming from a smart server.
408
#
409
# TODO: Eventually, want to do a 'branch' command by fetching the whole
410
# history as one big bundle.  How?  
411
#
412
# The branch command does 'br_from.sprout', which tries to preserve the same
413
# format.  We don't necessarily even want that.  
414
#
415
# It might be simpler to handle cmd_pull first, which does a simpler fetch()
416
# operation from one branch into another.  It already has some code for
417
# pulling from a bundle, which it does by trying to see if the destination is
418
# a bundle file.  So it seems the logic for pull ought to be:
419
# 
420
#  - if it's a smart server, get a bundle from there and install that
421
#  - if it's a bundle, install that
422
#  - if it's a branch, pull from there
423
#
424
# Getting a bundle from a smart server is a bit different from reading a
425
# bundle from a URL:
426
#
427
#  - we can reasonably remember the URL we last read from 
428
#  - you can specify a revision number to pull, and we need to pass it across
429
#    to the server as a limit on what will be requested
430
#
431
# TODO: Given a URL, determine whether it is a smart server or not (or perhaps
432
# otherwise whether it's a bundle?)  Should this be a property or method of
433
# the transport?  For the ssh protocol, we always know it's a smart server.
434
# For http, we potentially need to probe.  But if we're explicitly given
435
# bzr+http:// then we can skip that for now.