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