~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/blackbox/test_serve.py

  • Committer: Ian Clatworthy
  • Date: 2009-07-23 00:24:30 UTC
  • mfrom: (4371.6.5 bzr.mv_after)
  • mto: This revision was merged to the branch mainline in revision 4561.
  • Revision ID: ian.clatworthy@canonical.com-20090723002430-70me272jpp3uss7i
(igc) Allow rename of items already removed from the inventory (Marius Kruger)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
19
19
 
20
20
import os
21
21
import signal
 
22
import subprocess
22
23
import sys
23
24
import thread
24
25
import threading
25
26
 
26
27
from bzrlib import (
27
 
    builtins,
28
28
    errors,
29
29
    osutils,
30
30
    revision as _mod_revision,
31
 
    trace,
32
31
    transport,
33
 
    urlutils,
34
32
    )
35
33
from bzrlib.branch import Branch
36
34
from bzrlib.bzrdir import BzrDir
 
35
from bzrlib.errors import ParamikoNotPresent
37
36
from bzrlib.smart import client, medium
38
 
from bzrlib.smart.server import (
39
 
    BzrServerFactory,
40
 
    SmartTCPServer,
41
 
    )
42
 
from bzrlib.tests import (
43
 
    TestCaseWithMemoryTransport,
44
 
    TestCaseWithTransport,
45
 
    )
46
 
from bzrlib.transport import remote
47
 
 
48
 
 
49
 
class TestBzrServeBase(TestCaseWithTransport):
50
 
 
51
 
    def run_bzr_serve_then_func(self, serve_args, retcode=0, func=None,
52
 
                                *func_args, **func_kwargs):
53
 
        """Run 'bzr serve', and run the given func in a thread once the server
54
 
        has started.
55
 
 
56
 
        When 'func' terminates, the server will be terminated too.
57
 
 
58
 
        Returns stdout and stderr.
59
 
        """
60
 
        def on_server_start_thread(tcp_server):
61
 
            """This runs concurrently with the server thread.
62
 
 
63
 
            The server is interrupted as soon as ``func`` finishes, even if an
64
 
            exception is encountered.
65
 
            """
66
 
            try:
67
 
                # Run func if set
68
 
                self.tcp_server = tcp_server
69
 
                if not func is None:
70
 
                    try:
71
 
                        func(*func_args, **func_kwargs)
72
 
                    except Exception, e:
73
 
                        # Log errors to make some test failures a little less
74
 
                        # mysterious.
75
 
                        trace.mutter('func broke: %r', e)
76
 
            finally:
77
 
                # Then stop the server
78
 
                trace.mutter('interrupting...')
79
 
                thread.interrupt_main()
80
 
        # When the hook is fired, it just starts ``on_server_start_thread`` and
81
 
        # return
82
 
        def on_server_start(backing_urls, tcp_server):
83
 
            t = threading.Thread(
84
 
                target=on_server_start_thread, args=(tcp_server,))
85
 
            t.start()
86
 
        # install hook
87
 
        SmartTCPServer.hooks.install_named_hook(
88
 
            'server_started_ex', on_server_start,
89
 
            'run_bzr_serve_then_func hook')
90
 
        # start a TCP server
91
 
        try:
92
 
            out, err = self.run_bzr(['serve'] + list(serve_args),
93
 
                                    retcode=retcode)
94
 
        except KeyboardInterrupt, e:
95
 
            out, err = e.args
96
 
        return out, err
97
 
 
98
 
 
99
 
class TestBzrServe(TestBzrServeBase):
100
 
 
101
 
    def setUp(self):
102
 
        super(TestBzrServe, self).setUp()
103
 
        self.disable_missing_extensions_warning()
104
 
 
105
 
    def test_server_exception_with_hook(self):
106
 
        """Catch exception from the server in the server_exception hook.
107
 
 
108
 
        We use ``run_bzr_serve_then_func`` without a ``func`` so the server
109
 
        will receive a KeyboardInterrupt exception we want to catch.
110
 
        """
111
 
        def hook(exception):
112
 
            if exception[0] is KeyboardInterrupt:
113
 
                sys.stderr.write('catching KeyboardInterrupt\n')
114
 
                return True
115
 
            else:
116
 
                return False
117
 
        SmartTCPServer.hooks.install_named_hook(
118
 
            'server_exception', hook,
119
 
            'test_server_except_hook hook')
120
 
        args = ['--port', 'localhost:0', '--quiet']
121
 
        out, err = self.run_bzr_serve_then_func(args, retcode=0)
122
 
        self.assertEqual('catching KeyboardInterrupt\n', err)
123
 
 
124
 
    def test_server_exception_no_hook(self):
125
 
        """test exception without hook returns error"""
126
 
        args = []
127
 
        out, err = self.run_bzr_serve_then_func(args, retcode=3)
 
37
from bzrlib.smart.server import SmartTCPServer
 
38
from bzrlib.tests import TestCaseWithTransport, TestSkipped
 
39
from bzrlib.trace import mutter
 
40
from bzrlib.transport import get_transport, remote
 
41
 
 
42
 
 
43
class TestBzrServe(TestCaseWithTransport):
128
44
 
129
45
    def assertInetServerShutsdownCleanly(self, process):
130
46
        """Shutdown the server process looking for errors."""
163
79
            finish_bzr_subprocess, a client for the server, and a transport.
164
80
        """
165
81
        # Serve from the current directory
166
 
        args = ['serve', '--inet']
167
 
        args.extend(extra_options)
168
 
        process = self.start_bzr_subprocess(args)
 
82
        process = self.start_bzr_subprocess(['serve', '--inet'])
169
83
 
170
84
        # Connect to the server
171
85
        # We use this url because while this is no valid URL to connect to this
172
86
        # server instance, the transport needs a URL.
173
87
        url = 'bzr://localhost/'
174
 
        self.permit_url(url)
175
88
        client_medium = medium.SmartSimplePipesClientMedium(
176
89
            process.stdout, process.stdin, url)
177
90
        transport = remote.RemoteTransport(url, medium=client_medium)
192
105
        prefix = 'listening on port: '
193
106
        self.assertStartsWith(port_line, prefix)
194
107
        port = int(port_line[len(prefix):])
195
 
        url = 'bzr://localhost:%d/' % port
196
 
        self.permit_url(url)
197
 
        return process, url
198
 
 
199
 
    def test_bzr_serve_quiet(self):
200
 
        self.make_branch('.')
201
 
        args = ['--port', 'localhost:0', '--quiet']
202
 
        out, err = self.run_bzr_serve_then_func(args, retcode=3)
203
 
        self.assertEqual('', out)
204
 
        self.assertEqual('', err)
 
108
        return process,'bzr://localhost:%d/' % port
205
109
 
206
110
    def test_bzr_serve_inet_readonly(self):
207
111
        """bzr server should provide a read only filesystem by default."""
215
119
 
216
120
        process, transport = self.start_server_inet(['--allow-writes'])
217
121
 
218
 
        # We get a working branch, and can create a directory
 
122
        # We get a working branch
219
123
        branch = BzrDir.open_from_transport(transport).open_branch()
220
124
        self.make_read_requests(branch)
221
 
        transport.mkdir('adir')
222
125
        self.assertInetServerShutsdownCleanly(process)
223
126
 
224
127
    def test_bzr_serve_port_readonly(self):
225
128
        """bzr server should provide a read only filesystem by default."""
226
129
        process, url = self.start_server_port()
227
 
        t = transport.get_transport(url)
228
 
        self.assertRaises(errors.TransportNotPossible, t.mkdir, 'adir')
 
130
        transport = get_transport(url)
 
131
        self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
229
132
        self.assertServerFinishesCleanly(process)
230
133
 
231
134
    def test_bzr_serve_port_readwrite(self):
251
154
        self.make_read_requests(branch)
252
155
        self.assertServerFinishesCleanly(process)
253
156
 
254
 
    def test_bzr_serve_dhpss(self):
255
 
        # This is a smoke test that the server doesn't crash when run with
256
 
        # -Dhpss, and does drop some hpss logging to the file.
257
 
        self.make_branch('.')
258
 
        log_fname = os.getcwd() + '/server.log'
259
 
        self.overrideEnv('BZR_LOG', log_fname)
260
 
        process, transport = self.start_server_inet(['-Dhpss'])
261
 
        branch = BzrDir.open_from_transport(transport).open_branch()
262
 
        self.make_read_requests(branch)
263
 
        self.assertInetServerShutsdownCleanly(process)
264
 
        f = open(log_fname, 'rb')
265
 
        content = f.read()
266
 
        f.close()
267
 
        self.assertContainsRe(content, r'hpss request: \[[0-9-]+\]')
268
 
 
269
 
 
270
 
class TestCmdServeChrooting(TestBzrServeBase):
 
157
    def test_bzr_connect_to_bzr_ssh(self):
 
158
        """User acceptance that get_transport of a bzr+ssh:// behaves correctly.
 
159
 
 
160
        bzr+ssh:// should cause bzr to run a remote bzr smart server over SSH.
 
161
        """
 
162
        try:
 
163
            from bzrlib.transport.sftp import SFTPServer
 
164
        except ParamikoNotPresent:
 
165
            raise TestSkipped('Paramiko not installed')
 
166
        from bzrlib.tests.stub_sftp import StubServer
 
167
 
 
168
        # Make a branch
 
169
        self.make_branch('a_branch')
 
170
 
 
171
        # Start an SSH server
 
172
        self.command_executed = []
 
173
        # XXX: This is horrible -- we define a really dumb SSH server that
 
174
        # executes commands, and manage the hooking up of stdin/out/err to the
 
175
        # SSH channel ourselves.  Surely this has already been implemented
 
176
        # elsewhere?
 
177
        class StubSSHServer(StubServer):
 
178
 
 
179
            test = self
 
180
 
 
181
            def check_channel_exec_request(self, channel, command):
 
182
                self.test.command_executed.append(command)
 
183
                proc = subprocess.Popen(
 
184
                    command, shell=True, stdin=subprocess.PIPE,
 
185
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
186
 
 
187
                # XXX: horribly inefficient, not to mention ugly.
 
188
                # Start a thread for each of stdin/out/err, and relay bytes from
 
189
                # the subprocess to channel and vice versa.
 
190
                def ferry_bytes(read, write, close):
 
191
                    while True:
 
192
                        bytes = read(1)
 
193
                        if bytes == '':
 
194
                            close()
 
195
                            break
 
196
                        write(bytes)
 
197
 
 
198
                file_functions = [
 
199
                    (channel.recv, proc.stdin.write, proc.stdin.close),
 
200
                    (proc.stdout.read, channel.sendall, channel.close),
 
201
                    (proc.stderr.read, channel.sendall_stderr, channel.close)]
 
202
                for read, write, close in file_functions:
 
203
                    t = threading.Thread(
 
204
                        target=ferry_bytes, args=(read, write, close))
 
205
                    t.start()
 
206
 
 
207
                return True
 
208
 
 
209
        ssh_server = SFTPServer(StubSSHServer)
 
210
        # XXX: We *don't* want to override the default SSH vendor, so we set
 
211
        # _vendor to what _get_ssh_vendor returns.
 
212
        ssh_server.setUp()
 
213
        self.addCleanup(ssh_server.tearDown)
 
214
        port = ssh_server._listener.port
 
215
 
 
216
        # Access the branch via a bzr+ssh URL.  The BZR_REMOTE_PATH environment
 
217
        # variable is used to tell bzr what command to run on the remote end.
 
218
        path_to_branch = osutils.abspath('a_branch')
 
219
 
 
220
        orig_bzr_remote_path = os.environ.get('BZR_REMOTE_PATH')
 
221
        bzr_remote_path = self.get_bzr_path()
 
222
        if sys.platform == 'win32':
 
223
            bzr_remote_path = sys.executable + ' ' + self.get_bzr_path()
 
224
        os.environ['BZR_REMOTE_PATH'] = bzr_remote_path
 
225
        try:
 
226
            if sys.platform == 'win32':
 
227
                path_to_branch = os.path.splitdrive(path_to_branch)[1]
 
228
            branch = Branch.open(
 
229
                'bzr+ssh://fred:secret@localhost:%d%s' % (port, path_to_branch))
 
230
            self.make_read_requests(branch)
 
231
            # Check we can perform write operations
 
232
            branch.bzrdir.root_transport.mkdir('foo')
 
233
        finally:
 
234
            # Restore the BZR_REMOTE_PATH environment variable back to its
 
235
            # original state.
 
236
            if orig_bzr_remote_path is None:
 
237
                del os.environ['BZR_REMOTE_PATH']
 
238
            else:
 
239
                os.environ['BZR_REMOTE_PATH'] = orig_bzr_remote_path
 
240
 
 
241
        self.assertEqual(
 
242
            ['%s serve --inet --directory=/ --allow-writes'
 
243
             % bzr_remote_path],
 
244
            self.command_executed)
 
245
 
 
246
 
 
247
class TestCmdServeChrooting(TestCaseWithTransport):
271
248
 
272
249
    def test_serve_tcp(self):
273
250
        """'bzr serve' wraps the given --directory in a ChrootServer.
279
256
        t = self.get_transport()
280
257
        t.mkdir('server-root')
281
258
        self.run_bzr_serve_then_func(
282
 
            ['--port', '127.0.0.1:0',
283
 
             '--directory', t.local_abspath('server-root'),
 
259
            ['--port', '0', '--directory', t.local_abspath('server-root'),
284
260
             '--allow-writes'],
285
 
            func=self.when_server_started)
 
261
            self.when_server_started)
286
262
        # The when_server_started method issued a find_repositoryV3 that should
287
263
        # fail with 'norepository' because there are no repositories inside the
288
264
        # --directory.
289
265
        self.assertEqual(('norepository',), self.client_resp)
 
266
        
 
267
    def run_bzr_serve_then_func(self, serve_args, func, *func_args,
 
268
            **func_kwargs):
 
269
        """Run 'bzr serve', and run the given func in a thread once the server
 
270
        has started.
 
271
        
 
272
        When 'func' terminates, the server will be terminated too.
 
273
        """
 
274
        # install hook
 
275
        def on_server_start(backing_urls, tcp_server):
 
276
            t = threading.Thread(
 
277
                target=on_server_start_thread, args=(tcp_server,))
 
278
            t.start()
 
279
        def on_server_start_thread(tcp_server):
 
280
            try:
 
281
                # Run func
 
282
                self.tcp_server = tcp_server
 
283
                try:
 
284
                    func(*func_args, **func_kwargs)
 
285
                except Exception, e:
 
286
                    # Log errors to make some test failures a little less
 
287
                    # mysterious.
 
288
                    mutter('func broke: %r', e)
 
289
            finally:
 
290
                # Then stop the server
 
291
                mutter('interrupting...')
 
292
                thread.interrupt_main()
 
293
        SmartTCPServer.hooks.install_named_hook(
 
294
            'server_started_ex', on_server_start,
 
295
            'run_bzr_serve_then_func hook')
 
296
        # start a TCP server
 
297
        try:
 
298
            self.run_bzr(['serve'] + list(serve_args))
 
299
        except KeyboardInterrupt:
 
300
            pass
290
301
 
291
302
    def when_server_started(self):
292
303
        # Connect to the TCP server and issue some requests and see what comes
305
316
        client_medium.disconnect()
306
317
 
307
318
 
308
 
class TestUserdirExpansion(TestCaseWithMemoryTransport):
309
 
 
310
 
    @staticmethod
311
 
    def fake_expanduser(path):
312
 
        """A simple, environment-independent, function for the duration of this
313
 
        test.
314
 
 
315
 
        Paths starting with a path segment of '~user' will expand to start with
316
 
        '/home/user/'.  Every other path will be unchanged.
317
 
        """
318
 
        if path.split('/', 1)[0] == '~user':
319
 
            return '/home/user' + path[len('~user'):]
320
 
        return path
321
 
 
322
 
    def make_test_server(self, base_path='/'):
323
 
        """Make and start a BzrServerFactory, backed by a memory transport, and
324
 
        creat '/home/user' in that transport.
325
 
        """
326
 
        bzr_server = BzrServerFactory(
327
 
            self.fake_expanduser, lambda t: base_path)
328
 
        mem_transport = self.get_transport()
329
 
        mem_transport.mkdir_multi(['home', 'home/user'])
330
 
        bzr_server.set_up(mem_transport, None, None, inet=True)
331
 
        self.addCleanup(bzr_server.tear_down)
332
 
        return bzr_server
333
 
 
334
 
    def test_bzr_serve_expands_userdir(self):
335
 
        bzr_server = self.make_test_server()
336
 
        self.assertTrue(bzr_server.smart_server.backing_transport.has('~user'))
337
 
 
338
 
    def test_bzr_serve_does_not_expand_userdir_outside_base(self):
339
 
        bzr_server = self.make_test_server('/foo')
340
 
        self.assertFalse(bzr_server.smart_server.backing_transport.has('~user'))
341
 
 
342
 
    def test_get_base_path(self):
343
 
        """cmd_serve will turn the --directory option into a LocalTransport
344
 
        (optionally decorated with 'readonly+').  BzrServerFactory can
345
 
        determine the original --directory from that transport.
346
 
        """
347
 
        # URLs always include the trailing slash, and get_base_path returns it
348
 
        base_dir = osutils.abspath('/a/b/c') + '/'
349
 
        base_url = urlutils.local_path_to_url(base_dir) + '/'
350
 
        # Define a fake 'protocol' to capture the transport that cmd_serve
351
 
        # passes to serve_bzr.
352
 
        def capture_transport(transport, host, port, inet):
353
 
            self.bzr_serve_transport = transport
354
 
        cmd = builtins.cmd_serve()
355
 
        # Read-only
356
 
        cmd.run(directory=base_dir, protocol=capture_transport)
357
 
        server_maker = BzrServerFactory()
358
 
        self.assertEqual(
359
 
            'readonly+%s' % base_url, self.bzr_serve_transport.base)
360
 
        self.assertEqual(
361
 
            base_dir, server_maker.get_base_path(self.bzr_serve_transport))
362
 
        # Read-write
363
 
        cmd.run(directory=base_dir, protocol=capture_transport,
364
 
            allow_writes=True)
365
 
        server_maker = BzrServerFactory()
366
 
        self.assertEqual(base_url, self.bzr_serve_transport.base)
367
 
        self.assertEqual(base_dir,
368
 
            server_maker.get_base_path(self.bzr_serve_transport))
 
319