~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

Add a NEWS entry and prepare submission.

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
    urlutils,
 
34
    )
 
35
from bzrlib.branch import Branch
 
36
from bzrlib.bzrdir import BzrDir
 
37
from bzrlib.smart import client, medium
 
38
from bzrlib.smart.server import BzrServerFactory, SmartTCPServer
 
39
from bzrlib.tests import (
 
40
    ParamikoFeature,
 
41
    TestCaseWithMemoryTransport,
 
42
    TestCaseWithTransport,
 
43
    TestSkipped,
 
44
    )
 
45
from bzrlib.trace import mutter
 
46
from bzrlib.transport import get_transport, remote
 
47
 
 
48
class TestBzrServeBase(TestCaseWithTransport):
 
49
 
 
50
    def run_bzr_serve_then_func(self, serve_args, retcode=0, func=None,
 
51
                                *func_args, **func_kwargs):
 
52
        """Run 'bzr serve', and run the given func in a thread once the server
 
53
        has started.
 
54
        
 
55
        When 'func' terminates, the server will be terminated too.
 
56
        
 
57
        Returns stdout and stderr.
 
58
        """
 
59
        # install hook
 
60
        def on_server_start(backing_urls, tcp_server):
 
61
            t = threading.Thread(
 
62
                target=on_server_start_thread, args=(tcp_server,))
 
63
            t.start()
 
64
        def on_server_start_thread(tcp_server):
 
65
            try:
 
66
                # Run func if set
 
67
                self.tcp_server = tcp_server
 
68
                if not func is None:
 
69
                    try:
 
70
                        func(*func_args, **func_kwargs)
 
71
                    except Exception, e:
 
72
                        # Log errors to make some test failures a little less
 
73
                        # mysterious.
 
74
                        mutter('func broke: %r', e)
 
75
            finally:
 
76
                # Then stop the server
 
77
                mutter('interrupting...')
 
78
                thread.interrupt_main()
 
79
        SmartTCPServer.hooks.install_named_hook(
 
80
            'server_started_ex', on_server_start,
 
81
            'run_bzr_serve_then_func hook')
 
82
        # start a TCP server
 
83
        try:
 
84
            out, err = self.run_bzr(['serve'] + list(serve_args))
 
85
        except KeyboardInterrupt, e:
 
86
            out, err = e.args
 
87
        return out, err
 
88
 
 
89
 
 
90
class TestBzrServe(TestBzrServeBase):
 
91
 
 
92
    def setUp(self):
 
93
        super(TestBzrServe, self).setUp()
 
94
        self.disable_missing_extensions_warning()
 
95
 
 
96
    def assertInetServerShutsdownCleanly(self, process):
 
97
        """Shutdown the server process looking for errors."""
 
98
        # Shutdown the server: the server should shut down when it cannot read
 
99
        # from stdin anymore.
 
100
        process.stdin.close()
 
101
        # Hide stdin from the subprocess module, so it won't fail to close it.
 
102
        process.stdin = None
 
103
        result = self.finish_bzr_subprocess(process)
 
104
        self.assertEqual('', result[0])
 
105
        self.assertEqual('', result[1])
 
106
 
 
107
    def assertServerFinishesCleanly(self, process):
 
108
        """Shutdown the bzr serve instance process looking for errors."""
 
109
        # Shutdown the server
 
110
        result = self.finish_bzr_subprocess(process, retcode=3,
 
111
                                            send_signal=signal.SIGINT)
 
112
        self.assertEqual('', result[0])
 
113
        self.assertEqual('bzr: interrupted\n', result[1])
 
114
 
 
115
    def make_read_requests(self, branch):
 
116
        """Do some read only requests."""
 
117
        branch.lock_read()
 
118
        try:
 
119
            branch.repository.all_revision_ids()
 
120
            self.assertEqual(_mod_revision.NULL_REVISION,
 
121
                             _mod_revision.ensure_null(branch.last_revision()))
 
122
        finally:
 
123
            branch.unlock()
 
124
 
 
125
    def start_server_inet(self, extra_options=()):
 
126
        """Start a bzr server subprocess using the --inet option.
 
127
 
 
128
        :param extra_options: extra options to give the server.
 
129
        :return: a tuple with the bzr process handle for passing to
 
130
            finish_bzr_subprocess, a client for the server, and a transport.
 
131
        """
 
132
        # Serve from the current directory
 
133
        process = self.start_bzr_subprocess(['serve', '--inet'])
 
134
 
 
135
        # Connect to the server
 
136
        # We use this url because while this is no valid URL to connect to this
 
137
        # server instance, the transport needs a URL.
 
138
        url = 'bzr://localhost/'
 
139
        self.permit_url(url)
 
140
        client_medium = medium.SmartSimplePipesClientMedium(
 
141
            process.stdout, process.stdin, url)
 
142
        transport = remote.RemoteTransport(url, medium=client_medium)
 
143
        return process, transport
 
144
 
 
145
    def start_server_port(self, extra_options=()):
 
146
        """Start a bzr server subprocess.
 
147
 
 
148
        :param extra_options: extra options to give the server.
 
149
        :return: a tuple with the bzr process handle for passing to
 
150
            finish_bzr_subprocess, and the base url for the server.
 
151
        """
 
152
        # Serve from the current directory
 
153
        args = ['serve', '--port', 'localhost:0']
 
154
        args.extend(extra_options)
 
155
        process = self.start_bzr_subprocess(args, skip_if_plan_to_signal=True)
 
156
        port_line = process.stderr.readline()
 
157
        prefix = 'listening on port: '
 
158
        self.assertStartsWith(port_line, prefix)
 
159
        port = int(port_line[len(prefix):])
 
160
        url = 'bzr://localhost:%d/' % port
 
161
        self.permit_url(url)
 
162
        return process, url
 
163
    
 
164
    def test_bzr_serve_quiet(self):
 
165
        self.make_branch('.')
 
166
        args = ['--port', 'localhost:0', '--quiet']
 
167
        out, err = self.run_bzr_serve_then_func(args, retcode=3)
 
168
        self.assertEqual('', out)
 
169
        self.assertEqual('', err)
 
170
 
 
171
    def test_bzr_serve_inet_readonly(self):
 
172
        """bzr server should provide a read only filesystem by default."""
 
173
        process, transport = self.start_server_inet()
 
174
        self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
 
175
        self.assertInetServerShutsdownCleanly(process)
 
176
 
 
177
    def test_bzr_serve_inet_readwrite(self):
 
178
        # Make a branch
 
179
        self.make_branch('.')
 
180
 
 
181
        process, transport = self.start_server_inet(['--allow-writes'])
 
182
 
 
183
        # We get a working branch
 
184
        branch = BzrDir.open_from_transport(transport).open_branch()
 
185
        self.make_read_requests(branch)
 
186
        self.assertInetServerShutsdownCleanly(process)
 
187
 
 
188
    def test_bzr_serve_port_readonly(self):
 
189
        """bzr server should provide a read only filesystem by default."""
 
190
        process, url = self.start_server_port()
 
191
        transport = get_transport(url)
 
192
        self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
 
193
        self.assertServerFinishesCleanly(process)
 
194
 
 
195
    def test_bzr_serve_port_readwrite(self):
 
196
        # Make a branch
 
197
        self.make_branch('.')
 
198
 
 
199
        process, url = self.start_server_port(['--allow-writes'])
 
200
 
 
201
        # Connect to the server
 
202
        branch = Branch.open(url)
 
203
        self.make_read_requests(branch)
 
204
        self.assertServerFinishesCleanly(process)
 
205
 
 
206
    def test_bzr_serve_supports_protocol(self):
 
207
        # Make a branch
 
208
        self.make_branch('.')
 
209
 
 
210
        process, url = self.start_server_port(['--allow-writes',
 
211
                                               '--protocol=bzr'])
 
212
 
 
213
        # Connect to the server
 
214
        branch = Branch.open(url)
 
215
        self.make_read_requests(branch)
 
216
        self.assertServerFinishesCleanly(process)
 
217
 
 
218
 
 
219
class TestCmdServeChrooting(TestBzrServeBase):
 
220
 
 
221
    def test_serve_tcp(self):
 
222
        """'bzr serve' wraps the given --directory in a ChrootServer.
 
223
 
 
224
        So requests that search up through the parent directories (like
 
225
        find_repositoryV3) will give "not found" responses, rather than
 
226
        InvalidURLJoin or jail break errors.
 
227
        """
 
228
        t = self.get_transport()
 
229
        t.mkdir('server-root')
 
230
        self.run_bzr_serve_then_func(
 
231
            ['--port', '127.0.0.1:0',
 
232
             '--directory', t.local_abspath('server-root'),
 
233
             '--allow-writes'],
 
234
            func=self.when_server_started)
 
235
        # The when_server_started method issued a find_repositoryV3 that should
 
236
        # fail with 'norepository' because there are no repositories inside the
 
237
        # --directory.
 
238
        self.assertEqual(('norepository',), self.client_resp)
 
239
 
 
240
    def when_server_started(self):
 
241
        # Connect to the TCP server and issue some requests and see what comes
 
242
        # back.
 
243
        client_medium = medium.SmartTCPClientMedium(
 
244
            '127.0.0.1', self.tcp_server.port,
 
245
            'bzr://localhost:%d/' % (self.tcp_server.port,))
 
246
        smart_client = client._SmartClient(client_medium)
 
247
        resp = smart_client.call('mkdir', 'foo', '')
 
248
        resp = smart_client.call('BzrDirFormat.initialize', 'foo/')
 
249
        try:
 
250
            resp = smart_client.call('BzrDir.find_repositoryV3', 'foo/')
 
251
        except errors.ErrorFromSmartServer, e:
 
252
            resp = e.error_tuple
 
253
        self.client_resp = resp
 
254
        client_medium.disconnect()
 
255
 
 
256
 
 
257
class TestUserdirExpansion(TestCaseWithMemoryTransport):
 
258
 
 
259
    def fake_expanduser(self, path):
 
260
        """A simple, environment-independent, function for the duration of this
 
261
        test.
 
262
 
 
263
        Paths starting with a path segment of '~user' will expand to start with
 
264
        '/home/user/'.  Every other path will be unchanged.
 
265
        """
 
266
        if path.split('/', 1)[0] == '~user':
 
267
            return '/home/user' + path[len('~user'):]
 
268
        return path
 
269
 
 
270
    def make_test_server(self, base_path='/'):
 
271
        """Make and setUp a BzrServerFactory, backed by a memory transport, and
 
272
        creat '/home/user' in that transport.
 
273
        """
 
274
        bzr_server = BzrServerFactory(
 
275
            self.fake_expanduser, lambda t: base_path)
 
276
        mem_transport = self.get_transport()
 
277
        mem_transport.mkdir_multi(['home', 'home/user'])
 
278
        bzr_server.set_up(mem_transport, None, None, inet=True)
 
279
        self.addCleanup(bzr_server.tear_down)
 
280
        return bzr_server
 
281
 
 
282
    def test_bzr_serve_expands_userdir(self):
 
283
        bzr_server = self.make_test_server()
 
284
        self.assertTrue(bzr_server.smart_server.backing_transport.has('~user'))
 
285
 
 
286
    def test_bzr_serve_does_not_expand_userdir_outside_base(self):
 
287
        bzr_server = self.make_test_server('/foo')
 
288
        self.assertFalse(bzr_server.smart_server.backing_transport.has('~user'))
 
289
 
 
290
    def test_get_base_path(self):
 
291
        """cmd_serve will turn the --directory option into a LocalTransport
 
292
        (optionally decorated with 'readonly+').  BzrServerFactory can
 
293
        determine the original --directory from that transport.
 
294
        """
 
295
        # URLs always include the trailing slash, and get_base_path returns it
 
296
        base_dir = osutils.abspath('/a/b/c') + '/'
 
297
        base_url = urlutils.local_path_to_url(base_dir) + '/'
 
298
        # Define a fake 'protocol' to capture the transport that cmd_serve
 
299
        # passes to serve_bzr.
 
300
        def capture_transport(transport, host, port, inet):
 
301
            self.bzr_serve_transport = transport
 
302
        cmd = builtins.cmd_serve()
 
303
        # Read-only
 
304
        cmd.run(directory=base_dir, protocol=capture_transport)
 
305
        server_maker = BzrServerFactory()
 
306
        self.assertEqual(
 
307
            'readonly+%s' % base_url, self.bzr_serve_transport.base)
 
308
        self.assertEqual(
 
309
            base_dir, server_maker.get_base_path(self.bzr_serve_transport))
 
310
        # Read-write
 
311
        cmd.run(directory=base_dir, protocol=capture_transport,
 
312
            allow_writes=True)
 
313
        server_maker = BzrServerFactory()
 
314
        self.assertEqual(base_url, self.bzr_serve_transport.base)
 
315
        self.assertEqual(base_dir,
 
316
            server_maker.get_base_path(self.bzr_serve_transport))
 
317