~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-07-30 14:24:06 UTC
  • mfrom: (4576.1.1 export-to-dir)
  • Revision ID: pqm@pqm.ubuntu.com-20090730142406-wg8gmxpcjz4c1z00
(bialix) Allow 'bzr export' to export into an existing (but empty)
        directory

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 signal
 
22
import subprocess
 
23
import sys
 
24
import thread
 
25
import threading
 
26
 
 
27
from bzrlib import (
 
28
    errors,
 
29
    osutils,
 
30
    revision as _mod_revision,
 
31
    transport,
 
32
    )
 
33
from bzrlib.branch import Branch
 
34
from bzrlib.bzrdir import BzrDir
 
35
from bzrlib.errors import ParamikoNotPresent
 
36
from bzrlib.smart import client, medium
 
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):
 
44
 
 
45
    def assertInetServerShutsdownCleanly(self, process):
 
46
        """Shutdown the server process looking for errors."""
 
47
        # Shutdown the server: the server should shut down when it cannot read
 
48
        # from stdin anymore.
 
49
        process.stdin.close()
 
50
        # Hide stdin from the subprocess module, so it won't fail to close it.
 
51
        process.stdin = None
 
52
        result = self.finish_bzr_subprocess(process)
 
53
        self.assertEqual('', result[0])
 
54
        self.assertEqual('', result[1])
 
55
 
 
56
    def assertServerFinishesCleanly(self, process):
 
57
        """Shutdown the bzr serve instance process looking for errors."""
 
58
        # Shutdown the server
 
59
        result = self.finish_bzr_subprocess(process, retcode=3,
 
60
                                            send_signal=signal.SIGINT)
 
61
        self.assertEqual('', result[0])
 
62
        self.assertEqual('bzr: interrupted\n', result[1])
 
63
 
 
64
    def make_read_requests(self, branch):
 
65
        """Do some read only requests."""
 
66
        branch.lock_read()
 
67
        try:
 
68
            branch.repository.all_revision_ids()
 
69
            self.assertEqual(_mod_revision.NULL_REVISION,
 
70
                             _mod_revision.ensure_null(branch.last_revision()))
 
71
        finally:
 
72
            branch.unlock()
 
73
 
 
74
    def start_server_inet(self, extra_options=()):
 
75
        """Start a bzr server subprocess using the --inet option.
 
76
 
 
77
        :param extra_options: extra options to give the server.
 
78
        :return: a tuple with the bzr process handle for passing to
 
79
            finish_bzr_subprocess, a client for the server, and a transport.
 
80
        """
 
81
        # Serve from the current directory
 
82
        process = self.start_bzr_subprocess(['serve', '--inet'])
 
83
 
 
84
        # Connect to the server
 
85
        # We use this url because while this is no valid URL to connect to this
 
86
        # server instance, the transport needs a URL.
 
87
        url = 'bzr://localhost/'
 
88
        client_medium = medium.SmartSimplePipesClientMedium(
 
89
            process.stdout, process.stdin, url)
 
90
        transport = remote.RemoteTransport(url, medium=client_medium)
 
91
        return process, transport
 
92
 
 
93
    def start_server_port(self, extra_options=()):
 
94
        """Start a bzr server subprocess.
 
95
 
 
96
        :param extra_options: extra options to give the server.
 
97
        :return: a tuple with the bzr process handle for passing to
 
98
            finish_bzr_subprocess, and the base url for the server.
 
99
        """
 
100
        # Serve from the current directory
 
101
        args = ['serve', '--port', 'localhost:0']
 
102
        args.extend(extra_options)
 
103
        process = self.start_bzr_subprocess(args, skip_if_plan_to_signal=True)
 
104
        port_line = process.stderr.readline()
 
105
        prefix = 'listening on port: '
 
106
        self.assertStartsWith(port_line, prefix)
 
107
        port = int(port_line[len(prefix):])
 
108
        return process,'bzr://localhost:%d/' % port
 
109
 
 
110
    def test_bzr_serve_inet_readonly(self):
 
111
        """bzr server should provide a read only filesystem by default."""
 
112
        process, transport = self.start_server_inet()
 
113
        self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
 
114
        self.assertInetServerShutsdownCleanly(process)
 
115
 
 
116
    def test_bzr_serve_inet_readwrite(self):
 
117
        # Make a branch
 
118
        self.make_branch('.')
 
119
 
 
120
        process, transport = self.start_server_inet(['--allow-writes'])
 
121
 
 
122
        # We get a working branch
 
123
        branch = BzrDir.open_from_transport(transport).open_branch()
 
124
        self.make_read_requests(branch)
 
125
        self.assertInetServerShutsdownCleanly(process)
 
126
 
 
127
    def test_bzr_serve_port_readonly(self):
 
128
        """bzr server should provide a read only filesystem by default."""
 
129
        process, url = self.start_server_port()
 
130
        transport = get_transport(url)
 
131
        self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
 
132
        self.assertServerFinishesCleanly(process)
 
133
 
 
134
    def test_bzr_serve_port_readwrite(self):
 
135
        # Make a branch
 
136
        self.make_branch('.')
 
137
 
 
138
        process, url = self.start_server_port(['--allow-writes'])
 
139
 
 
140
        # Connect to the server
 
141
        branch = Branch.open(url)
 
142
        self.make_read_requests(branch)
 
143
        self.assertServerFinishesCleanly(process)
 
144
 
 
145
    def test_bzr_serve_supports_protocol(self):
 
146
        # Make a branch
 
147
        self.make_branch('.')
 
148
 
 
149
        process, url = self.start_server_port(['--allow-writes',
 
150
                                               '--protocol=bzr'])
 
151
 
 
152
        # Connect to the server
 
153
        branch = Branch.open(url)
 
154
        self.make_read_requests(branch)
 
155
        self.assertServerFinishesCleanly(process)
 
156
 
 
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):
 
248
 
 
249
    def test_serve_tcp(self):
 
250
        """'bzr serve' wraps the given --directory in a ChrootServer.
 
251
 
 
252
        So requests that search up through the parent directories (like
 
253
        find_repositoryV3) will give "not found" responses, rather than
 
254
        InvalidURLJoin or jail break errors.
 
255
        """
 
256
        t = self.get_transport()
 
257
        t.mkdir('server-root')
 
258
        self.run_bzr_serve_then_func(
 
259
            ['--port', '0', '--directory', t.local_abspath('server-root'),
 
260
             '--allow-writes'],
 
261
            self.when_server_started)
 
262
        # The when_server_started method issued a find_repositoryV3 that should
 
263
        # fail with 'norepository' because there are no repositories inside the
 
264
        # --directory.
 
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
 
301
 
 
302
    def when_server_started(self):
 
303
        # Connect to the TCP server and issue some requests and see what comes
 
304
        # back.
 
305
        client_medium = medium.SmartTCPClientMedium(
 
306
            '127.0.0.1', self.tcp_server.port,
 
307
            'bzr://localhost:%d/' % (self.tcp_server.port,))
 
308
        smart_client = client._SmartClient(client_medium)
 
309
        resp = smart_client.call('mkdir', 'foo', '')
 
310
        resp = smart_client.call('BzrDirFormat.initialize', 'foo/')
 
311
        try:
 
312
            resp = smart_client.call('BzrDir.find_repositoryV3', 'foo/')
 
313
        except errors.ErrorFromSmartServer, e:
 
314
            resp = e.error_tuple
 
315
        self.client_resp = resp
 
316
        client_medium.disconnect()
 
317
 
 
318
 
 
319