/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/blackbox/test_serve.py

  • Committer: Andrew Bennetts
  • Date: 2009-09-17 03:16:05 UTC
  • mto: This revision was merged to the branch mainline in revision 4702.
  • Revision ID: andrew.bennetts@canonical.com-20090917031605-xilizo5jfq4scbw0
Update documentation.

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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
 
 
18
"""Tests of the bzr serve command."""
 
19
 
 
20
import os
 
21
import os.path
 
22
import signal
 
23
import subprocess
 
24
import sys
 
25
import thread
 
26
import threading
 
27
 
 
28
from bzrlib import (
 
29
    builtins,
 
30
    errors,
 
31
    osutils,
 
32
    revision as _mod_revision,
 
33
    )
 
34
from bzrlib.branch import Branch
 
35
from bzrlib.bzrdir import BzrDir
 
36
from bzrlib.errors import ParamikoNotPresent
 
37
from bzrlib.smart import client, medium
 
38
from bzrlib.smart.server import BzrServerMaker, SmartTCPServer
 
39
from bzrlib.tests import (
 
40
    TestCaseWithTransport,
 
41
    TestCaseWithMemoryTransport,
 
42
    TestSkipped,
 
43
    )
 
44
from bzrlib.trace import mutter
 
45
from bzrlib.transport import get_transport, remote
 
46
 
 
47
 
 
48
class TestBzrServe(TestCaseWithTransport):
 
49
 
 
50
    def assertInetServerShutsdownCleanly(self, process):
 
51
        """Shutdown the server process looking for errors."""
 
52
        # Shutdown the server: the server should shut down when it cannot read
 
53
        # from stdin anymore.
 
54
        process.stdin.close()
 
55
        # Hide stdin from the subprocess module, so it won't fail to close it.
 
56
        process.stdin = None
 
57
        result = self.finish_bzr_subprocess(process)
 
58
        self.assertEqual('', result[0])
 
59
        self.assertEqual('', result[1])
 
60
 
 
61
    def assertServerFinishesCleanly(self, process):
 
62
        """Shutdown the bzr serve instance process looking for errors."""
 
63
        # Shutdown the server
 
64
        result = self.finish_bzr_subprocess(process, retcode=3,
 
65
                                            send_signal=signal.SIGINT)
 
66
        self.assertEqual('', result[0])
 
67
        self.assertEqual('bzr: interrupted\n', result[1])
 
68
 
 
69
    def make_read_requests(self, branch):
 
70
        """Do some read only requests."""
 
71
        branch.lock_read()
 
72
        try:
 
73
            branch.repository.all_revision_ids()
 
74
            self.assertEqual(_mod_revision.NULL_REVISION,
 
75
                             _mod_revision.ensure_null(branch.last_revision()))
 
76
        finally:
 
77
            branch.unlock()
 
78
 
 
79
    def start_server_inet(self, extra_options=()):
 
80
        """Start a bzr server subprocess using the --inet option.
 
81
 
 
82
        :param extra_options: extra options to give the server.
 
83
        :return: a tuple with the bzr process handle for passing to
 
84
            finish_bzr_subprocess, a client for the server, and a transport.
 
85
        """
 
86
        # Serve from the current directory
 
87
        process = self.start_bzr_subprocess(['serve', '--inet'])
 
88
 
 
89
        # Connect to the server
 
90
        # We use this url because while this is no valid URL to connect to this
 
91
        # server instance, the transport needs a URL.
 
92
        url = 'bzr://localhost/'
 
93
        client_medium = medium.SmartSimplePipesClientMedium(
 
94
            process.stdout, process.stdin, url)
 
95
        transport = remote.RemoteTransport(url, medium=client_medium)
 
96
        return process, transport
 
97
 
 
98
    def start_server_port(self, extra_options=()):
 
99
        """Start a bzr server subprocess.
 
100
 
 
101
        :param extra_options: extra options to give the server.
 
102
        :return: a tuple with the bzr process handle for passing to
 
103
            finish_bzr_subprocess, and the base url for the server.
 
104
        """
 
105
        # Serve from the current directory
 
106
        args = ['serve', '--port', 'localhost:0']
 
107
        args.extend(extra_options)
 
108
        process = self.start_bzr_subprocess(args, skip_if_plan_to_signal=True)
 
109
        port_line = process.stderr.readline()
 
110
        prefix = 'listening on port: '
 
111
        self.assertStartsWith(port_line, prefix)
 
112
        port = int(port_line[len(prefix):])
 
113
        return process,'bzr://localhost:%d/' % port
 
114
 
 
115
    def test_bzr_serve_inet_readonly(self):
 
116
        """bzr server should provide a read only filesystem by default."""
 
117
        process, transport = self.start_server_inet()
 
118
        self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
 
119
        self.assertInetServerShutsdownCleanly(process)
 
120
 
 
121
    def test_bzr_serve_inet_readwrite(self):
 
122
        # Make a branch
 
123
        self.make_branch('.')
 
124
 
 
125
        process, transport = self.start_server_inet(['--allow-writes'])
 
126
 
 
127
        # We get a working branch
 
128
        branch = BzrDir.open_from_transport(transport).open_branch()
 
129
        self.make_read_requests(branch)
 
130
        self.assertInetServerShutsdownCleanly(process)
 
131
 
 
132
    def test_bzr_serve_port_readonly(self):
 
133
        """bzr server should provide a read only filesystem by default."""
 
134
        process, url = self.start_server_port()
 
135
        transport = get_transport(url)
 
136
        self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
 
137
        self.assertServerFinishesCleanly(process)
 
138
 
 
139
    def test_bzr_serve_port_readwrite(self):
 
140
        # Make a branch
 
141
        self.make_branch('.')
 
142
 
 
143
        process, url = self.start_server_port(['--allow-writes'])
 
144
 
 
145
        # Connect to the server
 
146
        branch = Branch.open(url)
 
147
        self.make_read_requests(branch)
 
148
        self.assertServerFinishesCleanly(process)
 
149
 
 
150
    def test_bzr_serve_supports_protocol(self):
 
151
        # Make a branch
 
152
        self.make_branch('.')
 
153
 
 
154
        process, url = self.start_server_port(['--allow-writes',
 
155
                                               '--protocol=bzr'])
 
156
 
 
157
        # Connect to the server
 
158
        branch = Branch.open(url)
 
159
        self.make_read_requests(branch)
 
160
        self.assertServerFinishesCleanly(process)
 
161
 
 
162
    def test_bzr_connect_to_bzr_ssh(self):
 
163
        """User acceptance that get_transport of a bzr+ssh:// behaves correctly.
 
164
 
 
165
        bzr+ssh:// should cause bzr to run a remote bzr smart server over SSH.
 
166
        """
 
167
        try:
 
168
            from bzrlib.transport.sftp import SFTPServer
 
169
        except ParamikoNotPresent:
 
170
            raise TestSkipped('Paramiko not installed')
 
171
        from bzrlib.tests.stub_sftp import StubServer
 
172
 
 
173
        # Make a branch
 
174
        self.make_branch('a_branch')
 
175
 
 
176
        # Start an SSH server
 
177
        self.command_executed = []
 
178
        # XXX: This is horrible -- we define a really dumb SSH server that
 
179
        # executes commands, and manage the hooking up of stdin/out/err to the
 
180
        # SSH channel ourselves.  Surely this has already been implemented
 
181
        # elsewhere?
 
182
        class StubSSHServer(StubServer):
 
183
 
 
184
            test = self
 
185
 
 
186
            def check_channel_exec_request(self, channel, command):
 
187
                self.test.command_executed.append(command)
 
188
                proc = subprocess.Popen(
 
189
                    command, shell=True, stdin=subprocess.PIPE,
 
190
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
191
 
 
192
                # XXX: horribly inefficient, not to mention ugly.
 
193
                # Start a thread for each of stdin/out/err, and relay bytes from
 
194
                # the subprocess to channel and vice versa.
 
195
                def ferry_bytes(read, write, close):
 
196
                    while True:
 
197
                        bytes = read(1)
 
198
                        if bytes == '':
 
199
                            close()
 
200
                            break
 
201
                        write(bytes)
 
202
 
 
203
                file_functions = [
 
204
                    (channel.recv, proc.stdin.write, proc.stdin.close),
 
205
                    (proc.stdout.read, channel.sendall, channel.close),
 
206
                    (proc.stderr.read, channel.sendall_stderr, channel.close)]
 
207
                for read, write, close in file_functions:
 
208
                    t = threading.Thread(
 
209
                        target=ferry_bytes, args=(read, write, close))
 
210
                    t.start()
 
211
 
 
212
                return True
 
213
 
 
214
        ssh_server = SFTPServer(StubSSHServer)
 
215
        # XXX: We *don't* want to override the default SSH vendor, so we set
 
216
        # _vendor to what _get_ssh_vendor returns.
 
217
        ssh_server.setUp()
 
218
        self.addCleanup(ssh_server.tearDown)
 
219
        port = ssh_server._listener.port
 
220
 
 
221
        # Access the branch via a bzr+ssh URL.  The BZR_REMOTE_PATH environment
 
222
        # variable is used to tell bzr what command to run on the remote end.
 
223
        path_to_branch = osutils.abspath('a_branch')
 
224
 
 
225
        orig_bzr_remote_path = os.environ.get('BZR_REMOTE_PATH')
 
226
        bzr_remote_path = self.get_bzr_path()
 
227
        if sys.platform == 'win32':
 
228
            bzr_remote_path = sys.executable + ' ' + self.get_bzr_path()
 
229
        os.environ['BZR_REMOTE_PATH'] = bzr_remote_path
 
230
        try:
 
231
            if sys.platform == 'win32':
 
232
                path_to_branch = os.path.splitdrive(path_to_branch)[1]
 
233
            branch = Branch.open(
 
234
                'bzr+ssh://fred:secret@localhost:%d%s' % (port, path_to_branch))
 
235
            self.make_read_requests(branch)
 
236
            # Check we can perform write operations
 
237
            branch.bzrdir.root_transport.mkdir('foo')
 
238
        finally:
 
239
            # Restore the BZR_REMOTE_PATH environment variable back to its
 
240
            # original state.
 
241
            if orig_bzr_remote_path is None:
 
242
                del os.environ['BZR_REMOTE_PATH']
 
243
            else:
 
244
                os.environ['BZR_REMOTE_PATH'] = orig_bzr_remote_path
 
245
 
 
246
        self.assertEqual(
 
247
            ['%s serve --inet --directory=/ --allow-writes'
 
248
             % bzr_remote_path],
 
249
            self.command_executed)
 
250
 
 
251
 
 
252
class TestCmdServeChrooting(TestCaseWithTransport):
 
253
 
 
254
    def test_serve_tcp(self):
 
255
        """'bzr serve' wraps the given --directory in a ChrootServer.
 
256
 
 
257
        So requests that search up through the parent directories (like
 
258
        find_repositoryV3) will give "not found" responses, rather than
 
259
        InvalidURLJoin or jail break errors.
 
260
        """
 
261
        t = self.get_transport()
 
262
        t.mkdir('server-root')
 
263
        self.run_bzr_serve_then_func(
 
264
            ['--port', '0', '--directory', t.local_abspath('server-root'),
 
265
             '--allow-writes'],
 
266
            self.when_server_started)
 
267
        # The when_server_started method issued a find_repositoryV3 that should
 
268
        # fail with 'norepository' because there are no repositories inside the
 
269
        # --directory.
 
270
        self.assertEqual(('norepository',), self.client_resp)
 
271
        
 
272
    def run_bzr_serve_then_func(self, serve_args, func, *func_args,
 
273
            **func_kwargs):
 
274
        """Run 'bzr serve', and run the given func in a thread once the server
 
275
        has started.
 
276
        
 
277
        When 'func' terminates, the server will be terminated too.
 
278
        """
 
279
        # install hook
 
280
        def on_server_start(backing_urls, tcp_server):
 
281
            t = threading.Thread(
 
282
                target=on_server_start_thread, args=(tcp_server,))
 
283
            t.start()
 
284
        def on_server_start_thread(tcp_server):
 
285
            try:
 
286
                # Run func
 
287
                self.tcp_server = tcp_server
 
288
                try:
 
289
                    func(*func_args, **func_kwargs)
 
290
                except Exception, e:
 
291
                    # Log errors to make some test failures a little less
 
292
                    # mysterious.
 
293
                    mutter('func broke: %r', e)
 
294
            finally:
 
295
                # Then stop the server
 
296
                mutter('interrupting...')
 
297
                thread.interrupt_main()
 
298
        SmartTCPServer.hooks.install_named_hook(
 
299
            'server_started_ex', on_server_start,
 
300
            'run_bzr_serve_then_func hook')
 
301
        # start a TCP server
 
302
        try:
 
303
            self.run_bzr(['serve'] + list(serve_args))
 
304
        except KeyboardInterrupt:
 
305
            pass
 
306
 
 
307
    def when_server_started(self):
 
308
        # Connect to the TCP server and issue some requests and see what comes
 
309
        # back.
 
310
        client_medium = medium.SmartTCPClientMedium(
 
311
            '127.0.0.1', self.tcp_server.port,
 
312
            'bzr://localhost:%d/' % (self.tcp_server.port,))
 
313
        smart_client = client._SmartClient(client_medium)
 
314
        resp = smart_client.call('mkdir', 'foo', '')
 
315
        resp = smart_client.call('BzrDirFormat.initialize', 'foo/')
 
316
        try:
 
317
            resp = smart_client.call('BzrDir.find_repositoryV3', 'foo/')
 
318
        except errors.ErrorFromSmartServer, e:
 
319
            resp = e.error_tuple
 
320
        self.client_resp = resp
 
321
        client_medium.disconnect()
 
322
 
 
323
 
 
324
class TestUserdirExpansion(TestCaseWithMemoryTransport):
 
325
 
 
326
    def fake_expanduser(self, path):
 
327
        """A simple, environment-independent, function for the duration of this
 
328
        test.
 
329
 
 
330
        Paths starting with a path segment of '~user' will expand to start with
 
331
        '/home/user/'.  Every other path will be unchanged.
 
332
        """
 
333
        if path.split('/', 1)[0] == '~user':
 
334
            return '/home/user' + path[len('~user'):]
 
335
        return path
 
336
 
 
337
    def make_test_server(self, base_path='/'):
 
338
        """Make and setUp a BzrServerMaker, backed by a memory transport, and
 
339
        creat '/home/user' in that transport.
 
340
        """
 
341
        bzr_server = BzrServerMaker(
 
342
            self.fake_expanduser, lambda t: base_path)
 
343
        mem_transport = self.get_transport()
 
344
        mem_transport.mkdir_multi(['home', 'home/user'])
 
345
        bzr_server.setUp(mem_transport, None, None, inet=True)
 
346
        self.addCleanup(bzr_server.tearDown)
 
347
        return bzr_server
 
348
 
 
349
    def test_bzr_serve_expands_userdir(self):
 
350
        bzr_server = self.make_test_server()
 
351
        self.assertTrue(bzr_server.smart_server.backing_transport.has('~user'))
 
352
 
 
353
    def test_bzr_serve_does_not_expand_userdir_outside_base(self):
 
354
        bzr_server = self.make_test_server('/foo')
 
355
        self.assertFalse(bzr_server.smart_server.backing_transport.has('~user'))
 
356
 
 
357
    def test_get_base_path(self):
 
358
        """cmd_serve will turn the --directory option into a LocalTransport
 
359
        (optionally decorated with 'readonly+').  BzrServerMaker can determine
 
360
        the original --directory from that transport.
 
361
        """
 
362
        # Define a fake 'protocol' to capture the transport that cmd_serve
 
363
        # passes to serve_bzr.
 
364
        def capture_transport(transport, host, port, inet):
 
365
            self.bzr_serve_transport = transport
 
366
        cmd = builtins.cmd_serve()
 
367
        # Read-only
 
368
        cmd.run(directory='/a/b/c', protocol=capture_transport)
 
369
        server_maker = BzrServerMaker()
 
370
        self.assertEqual(
 
371
            'readonly+file:///a/b/c/', self.bzr_serve_transport.base)
 
372
        self.assertEqual(
 
373
            u'/a/b/c/', server_maker.get_base_path(self.bzr_serve_transport))
 
374
        # Read-write
 
375
        cmd.run(directory='/a/b/c', protocol=capture_transport,
 
376
            allow_writes=True)
 
377
        server_maker = BzrServerMaker()
 
378
        self.assertEqual('file:///a/b/c/', self.bzr_serve_transport.base)
 
379
        self.assertEqual(
 
380
            u'/a/b/c/', server_maker.get_base_path(self.bzr_serve_transport))
 
381