/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_smart_transport.py

Support bzr:// urls to work with the new RPC-based transport which will be used
with the upcoming high-performance smart server. The new command ``bzr serve``
will invoke bzr in server mode, which processes these requests. (Andrew
Bennetts, Robert Collins, Martin Pool)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
25
from bzrlib import tests, errors, bzrdir
 
26
from bzrlib.transport import local, memory, smart, get_transport
 
27
 
 
28
## class SmartURLTests(tests.TestCase):
 
29
##     """Tests for handling of URLs and detection of smart servers"""
 
30
## 
 
31
##     def test_bzr_url_is_smart(self):
 
32
 
 
33
 
 
34
class SmartClientTests(tests.TestCase):
 
35
 
 
36
    def test_construct_smart_stream_client(self):
 
37
        # make a new client; this really wants two fifos or sockets
 
38
        # but the constructor should not do any IO
 
39
        client = smart.SmartStreamClient(None, None)
 
40
 
 
41
 
 
42
class TCPClientTests(tests.TestCaseWithTransport):
 
43
 
 
44
    def setUp(self):
 
45
        super(TCPClientTests, self).setUp()
 
46
        # We're allowed to set  the transport class here, so that we don't use
 
47
        # the default or a parameterized class, but rather use the
 
48
        # TestCaseWithTransport infrastructure to set up a smart server and
 
49
        # transport.
 
50
        self.transport_server = smart.SmartTCPServer_for_testing
 
51
 
 
52
    def test_plausible_url(self):
 
53
        self.assert_(self.get_url().startswith('bzr://'))
 
54
 
 
55
    def test_probe_transport(self):
 
56
        t = self.get_transport()
 
57
        self.assertIsInstance(t, smart.SmartTransport)
 
58
 
 
59
    def test_get_client_from_transport(self):
 
60
        t = self.get_transport()
 
61
        client = t.get_smart_client()
 
62
        self.assertIsInstance(client, smart.SmartStreamClient)
 
63
 
 
64
 
 
65
class BasicSmartTests(tests.TestCase):
 
66
    
 
67
    def test_smart_query_version(self):
 
68
        """Feed a canned query version to a server"""
 
69
        to_server = StringIO('hello\n')
 
70
        from_server = StringIO()
 
71
        server = smart.SmartStreamServer(to_server, from_server, local.LocalTransport('file:///'))
 
72
        server._serve_one_request()
 
73
        self.assertEqual('ok\0011\n',
 
74
                         from_server.getvalue())
 
75
 
 
76
    def test_canned_get_response(self):
 
77
        transport = memory.MemoryTransport('memory:///')
 
78
        transport.put('testfile', StringIO('contents\nof\nfile\n'))
 
79
        to_server = StringIO('get\001./testfile\n')
 
80
        from_server = StringIO()
 
81
        server = smart.SmartStreamServer(to_server, from_server, transport)
 
82
        server._serve_one_request()
 
83
        self.assertEqual('ok\n'
 
84
                         '17\n'
 
85
                         'contents\nof\nfile\n'
 
86
                         'done\n',
 
87
                         from_server.getvalue())
 
88
 
 
89
    def test_get_error_unexpected(self):
 
90
        """Error reported by server with no specific representation"""
 
91
        class FlakyTransport(object):
 
92
            def get(self, path):
 
93
                raise Exception("some random exception from inside server")
 
94
        server = smart.SmartTCPServer(backing_transport=FlakyTransport())
 
95
        server.start_background_thread()
 
96
        try:
 
97
            transport = smart.SmartTCPTransport(server.get_url())
 
98
            try:
 
99
                transport.get('something')
 
100
            except errors.TransportError, e:
 
101
                self.assertContainsRe(str(e), 'some random exception')
 
102
            else:
 
103
                self.fail("get did not raise expected error")
 
104
        finally:
 
105
            server.stop_background_thread()
 
106
 
 
107
    def test_server_subprocess(self):
 
108
        """Talk to a server started as a subprocess
 
109
        
 
110
        This is similar to running it over ssh, except that it runs in the same machine 
 
111
        without ssh intermediating.
 
112
        """
 
113
        args = [sys.executable, sys.argv[0], 'serve', '--inet']
 
114
        child = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
 
115
                                 close_fds=True)
 
116
        conn = smart.SmartStreamClient(to_server=child.stdin, from_server=child.stdout)
 
117
        conn.query_version()
 
118
        conn.query_version()
 
119
        conn.disconnect()
 
120
        returncode = child.wait()
 
121
        self.assertEquals(0, returncode)
 
122
 
 
123
 
 
124
class SmartTCPTests(tests.TestCase):
 
125
    """Tests for connection to TCP server.
 
126
    
 
127
    All of these tests are run with a server running on another thread serving
 
128
    a MemoryTransport, and a connection to it already open.
 
129
    """
 
130
 
 
131
    def setUp(self):
 
132
        super(SmartTCPTests, self).setUp()
 
133
        self.backing_transport = memory.MemoryTransport()
 
134
        self.server = smart.SmartTCPServer(self.backing_transport)
 
135
        self.server.start_background_thread()
 
136
        self.transport = smart.SmartTCPTransport(self.server.get_url())
 
137
 
 
138
    def tearDown(self):
 
139
        if getattr(self, 'transport', None):
 
140
            self.transport.disconnect()
 
141
        if getattr(self, 'server', None):
 
142
            self.server.stop_background_thread()
 
143
        super(SmartTCPTests, self).tearDown()
 
144
        
 
145
    def test_start_tcp_server(self):
 
146
        url = self.server.get_url()
 
147
        self.assertContainsRe(url, r'^bzr://127\.0\.0\.1:[0-9]{2,}/')
 
148
 
 
149
    def test_smart_transport_has(self):
 
150
        """Checking for file existence over smart."""
 
151
        self.backing_transport.put("foo", StringIO("contents of foo\n"))
 
152
        self.assertTrue(self.transport.has("foo"))
 
153
        self.assertFalse(self.transport.has("non-foo"))
 
154
 
 
155
    def test_smart_transport_get(self):
 
156
        """Read back a file over smart."""
 
157
        self.backing_transport.put("foo", StringIO("contents\nof\nfoo\n"))
 
158
        fp = self.transport.get("foo")
 
159
        self.assertEqual('contents\nof\nfoo\n', fp.read())
 
160
        
 
161
    def test_get_error_enoent(self):
 
162
        """Error reported from server getting nonexistent file."""
 
163
        try:
 
164
            self.transport.get('not a file')
 
165
        except errors.NoSuchFile, e:
 
166
            self.assertEqual('/not a file', e.path)
 
167
        else:
 
168
            self.fail("get did not raise expected error")
 
169
 
 
170
    def test_simple_clone_conn(self):
 
171
        """Test that cloning reuses the same connection."""
 
172
        # we create a real connection not a loopback one, but it will use the
 
173
        # same server and pipes
 
174
        conn2 = smart.SmartTransport(self.transport.base, clone_from=self.transport)
 
175
 
 
176
    def test_remote_path(self):
 
177
        self.assertEquals('/foo/bar',
 
178
                          self.transport._remote_path('foo/bar'))
 
179
 
 
180
    def test_clone_changes_base(self):
 
181
        """Cloning transport produces one with a new base location"""
 
182
        conn2 = self.transport.clone('subdir')
 
183
        self.assertEquals(self.transport.base + 'subdir/',
 
184
                          conn2.base)
 
185
 
 
186
    def test_open_dir(self):
 
187
        """Test changing directory"""
 
188
        transport = self.transport
 
189
        self.backing_transport.mkdir('toffee')
 
190
        self.backing_transport.mkdir('toffee/apple')
 
191
        self.assertEquals('/toffee', transport._remote_path('toffee'))
 
192
        self.assertTrue(transport.has('toffee'))
 
193
        sub_conn = transport.clone('toffee')
 
194
        self.assertTrue(sub_conn.has('apple'))
 
195
 
 
196
    def test_open_bzrdir(self):
 
197
        """Open an existing bzrdir over smart transport"""
 
198
        transport = self.transport
 
199
        t = self.backing_transport
 
200
        bzrdir.BzrDirFormat.get_default_format().initialize_on_transport(t)
 
201
        result_dir = bzrdir.BzrDir.open_containing_from_transport(transport)
 
202
 
 
203
 
 
204
class SmartServerTests(tests.TestCaseWithTransport):
 
205
    """Test that call directly into the server logic, bypassing the network."""
 
206
 
 
207
    def test_hello(self):
 
208
        server = smart.SmartServer(None)
 
209
        response = server.dispatch_command('hello', ())
 
210
        self.assertEqual(('ok', '1'), response.args)
 
211
        self.assertEqual(None, response.body)
 
212
        
 
213
    def test_get_bundle(self):
 
214
        from bzrlib.bundle import serializer
 
215
        wt = self.make_branch_and_tree('.')
 
216
        b = wt.branch
 
217
        file('hello', 'w').write('hello world')
 
218
        wt.add('hello')
 
219
        wt.commit(message='add hello', rev_id='rev-1')
 
220
        
 
221
        server = smart.SmartServer(self.get_transport())
 
222
        response = server.dispatch_command('get_bundle', ('.', 'rev-1'))
 
223
        self.assert_(response.body.startswith('# Bazaar revision bundle '),
 
224
                     "doesn't look like a bundle: %r" % response.body)
 
225
        bundle = serializer.read_bundle(StringIO(response.body))
 
226
 
 
227
# TODO: Client feature that does get_bundle and then installs that into a
 
228
# branch; this can be used in place of the regular pull/fetch operation when
 
229
# coming from a smart server.
 
230
#
 
231
# TODO: Eventually, want to do a 'branch' command by fetching the whole
 
232
# history as one big bundle.  How?  
 
233
#
 
234
# The branch command does 'br_from.sprout', which tries to preserve the same
 
235
# format.  We don't necessarily even want that.  
 
236
#
 
237
# It might be simpler to handle cmd_pull first, which does a simpler fetch()
 
238
# operation from one branch into another.  It already has some code for
 
239
# pulling from a bundle, which it does by trying to see if the destination is
 
240
# a bundle file.  So it seems the logic for pull ought to be:
 
241
 
242
#  - if it's a smart server, get a bundle from there and install that
 
243
#  - if it's a bundle, install that
 
244
#  - if it's a branch, pull from there
 
245
#
 
246
# Getting a bundle from a smart server is a bit different from reading a
 
247
# bundle from a URL:
 
248
#
 
249
#  - we can reasonably remember the URL we last read from 
 
250
#  - you can specify a revision number to pull, and we need to pass it across
 
251
#    to the server as a limit on what will be requested
 
252
#
 
253
# TODO: Given a URL, determine whether it is a smart server or not (or perhaps
 
254
# otherwise whether it's a bundle?)  Should this be a property or method of
 
255
# the transport?  For the ssh protocol, we always know it's a smart server.
 
256
# For http, we potentially need to probe.  But if we're explicitly given
 
257
# bzr+http:// then we can skip that for now.