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

  • Committer: Jelmer Vernooij
  • Date: 2018-02-18 19:34:37 UTC
  • mfrom: (6857.1.1 fix-import-stacked)
  • Revision ID: jelmer@jelmer.uk-20180218193437-pytr0vyldq867owo
Merge lp:~jelmer/brz/fix-import-stacked.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2011 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 brz serve command."""
 
19
 
 
20
import os
 
21
import signal
 
22
import sys
 
23
import thread
 
24
import threading
 
25
 
 
26
from ... import (
 
27
    builtins,
 
28
    config,
 
29
    errors,
 
30
    osutils,
 
31
    revision as _mod_revision,
 
32
    trace,
 
33
    transport,
 
34
    urlutils,
 
35
    )
 
36
from ...branch import Branch
 
37
from ...controldir import ControlDir
 
38
from ...bzr.smart import client, medium
 
39
from ...bzr.smart.server import (
 
40
    BzrServerFactory,
 
41
    SmartTCPServer,
 
42
    )
 
43
from .. import (
 
44
    TestCaseWithMemoryTransport,
 
45
    TestCaseWithTransport,
 
46
    )
 
47
from ...transport import remote
 
48
 
 
49
 
 
50
class TestBzrServeBase(TestCaseWithTransport):
 
51
 
 
52
    def run_bzr_serve_then_func(self, serve_args, retcode=0, func=None,
 
53
                                *func_args, **func_kwargs):
 
54
        """Run 'brz serve', and run the given func in a thread once the server
 
55
        has started.
 
56
 
 
57
        When 'func' terminates, the server will be terminated too.
 
58
 
 
59
        Returns stdout and stderr.
 
60
        """
 
61
        def on_server_start_thread(tcp_server):
 
62
            """This runs concurrently with the server thread.
 
63
 
 
64
            The server is interrupted as soon as ``func`` finishes, even if an
 
65
            exception is encountered.
 
66
            """
 
67
            try:
 
68
                # Run func if set
 
69
                self.tcp_server = tcp_server
 
70
                if func is not None:
 
71
                    try:
 
72
                        func(*func_args, **func_kwargs)
 
73
                    except Exception as e:
 
74
                        # Log errors to make some test failures a little less
 
75
                        # mysterious.
 
76
                        trace.mutter('func broke: %r', e)
 
77
            finally:
 
78
                # Then stop the server
 
79
                trace.mutter('interrupting...')
 
80
                thread.interrupt_main()
 
81
        # When the hook is fired, it just starts ``on_server_start_thread`` and
 
82
        # return
 
83
        def on_server_start(backing_urls, tcp_server):
 
84
            t = threading.Thread(
 
85
                target=on_server_start_thread, args=(tcp_server,))
 
86
            t.start()
 
87
        # install hook
 
88
        SmartTCPServer.hooks.install_named_hook(
 
89
            'server_started_ex', on_server_start,
 
90
            'run_bzr_serve_then_func hook')
 
91
        # It seesm thread.interrupt_main() will not raise KeyboardInterrupt
 
92
        # until after socket.accept returns. So we set the timeout low to make
 
93
        # the test faster.
 
94
        self.overrideAttr(SmartTCPServer, '_ACCEPT_TIMEOUT', 0.1)
 
95
        # start a TCP server
 
96
        try:
 
97
            out, err = self.run_bzr(['serve'] + list(serve_args),
 
98
                                    retcode=retcode)
 
99
        except KeyboardInterrupt as e:
 
100
            return (self._last_cmd_stdout.getvalue(),
 
101
                self._last_cmd_stderr.getvalue())
 
102
        return out, err
 
103
 
 
104
 
 
105
class TestBzrServe(TestBzrServeBase):
 
106
 
 
107
    def setUp(self):
 
108
        super(TestBzrServe, self).setUp()
 
109
        self.disable_missing_extensions_warning()
 
110
 
 
111
    def test_server_exception_with_hook(self):
 
112
        """Catch exception from the server in the server_exception hook.
 
113
 
 
114
        We use ``run_bzr_serve_then_func`` without a ``func`` so the server
 
115
        will receive a KeyboardInterrupt exception we want to catch.
 
116
        """
 
117
        def hook(exception):
 
118
            if exception[0] is KeyboardInterrupt:
 
119
                sys.stderr.write('catching KeyboardInterrupt\n')
 
120
                return True
 
121
            else:
 
122
                return False
 
123
        SmartTCPServer.hooks.install_named_hook(
 
124
            'server_exception', hook,
 
125
            'test_server_except_hook hook')
 
126
        args = ['--listen', 'localhost', '--port', '0', '--quiet']
 
127
        out, err = self.run_bzr_serve_then_func(args, retcode=0)
 
128
        self.assertEqual('catching KeyboardInterrupt\n', err)
 
129
 
 
130
    def test_server_exception_no_hook(self):
 
131
        """test exception without hook returns error"""
 
132
        args = []
 
133
        out, err = self.run_bzr_serve_then_func(args, retcode=3)
 
134
 
 
135
    def assertInetServerShutsdownCleanly(self, process):
 
136
        """Shutdown the server process looking for errors."""
 
137
        # Shutdown the server: the server should shut down when it cannot read
 
138
        # from stdin anymore.
 
139
        process.stdin.close()
 
140
        # Hide stdin from the subprocess module, so it won't fail to close it.
 
141
        process.stdin = None
 
142
        result = self.finish_bzr_subprocess(process)
 
143
        self.assertEqual('', result[0])
 
144
        self.assertEqual('', result[1])
 
145
 
 
146
    def assertServerFinishesCleanly(self, process):
 
147
        """Shutdown the brz serve instance process looking for errors."""
 
148
        # Shutdown the server
 
149
        result = self.finish_bzr_subprocess(process, retcode=3,
 
150
                                            send_signal=signal.SIGINT)
 
151
        self.assertEqual('', result[0])
 
152
        self.assertEqual('brz: interrupted\n', result[1])
 
153
 
 
154
    def make_read_requests(self, branch):
 
155
        """Do some read only requests."""
 
156
        with branch.lock_read():
 
157
            branch.repository.all_revision_ids()
 
158
            self.assertEqual(_mod_revision.NULL_REVISION,
 
159
                             _mod_revision.ensure_null(branch.last_revision()))
 
160
 
 
161
    def start_server_inet(self, extra_options=()):
 
162
        """Start a brz server subprocess using the --inet option.
 
163
 
 
164
        :param extra_options: extra options to give the server.
 
165
        :return: a tuple with the brz process handle for passing to
 
166
            finish_bzr_subprocess, a client for the server, and a transport.
 
167
        """
 
168
        # Serve from the current directory
 
169
        args = ['serve', '--inet']
 
170
        args.extend(extra_options)
 
171
        process = self.start_bzr_subprocess(args)
 
172
 
 
173
        # Connect to the server
 
174
        # We use this url because while this is no valid URL to connect to this
 
175
        # server instance, the transport needs a URL.
 
176
        url = 'bzr://localhost/'
 
177
        self.permit_url(url)
 
178
        client_medium = medium.SmartSimplePipesClientMedium(
 
179
            process.stdout, process.stdin, url)
 
180
        transport = remote.RemoteTransport(url, medium=client_medium)
 
181
        return process, transport
 
182
 
 
183
    def start_server_port(self, extra_options=()):
 
184
        """Start a brz server subprocess.
 
185
 
 
186
        :param extra_options: extra options to give the server.
 
187
        :return: a tuple with the brz process handle for passing to
 
188
            finish_bzr_subprocess, and the base url for the server.
 
189
        """
 
190
        # Serve from the current directory
 
191
        args = ['serve', '--listen', 'localhost', '--port', '0']
 
192
        args.extend(extra_options)
 
193
        process = self.start_bzr_subprocess(args, skip_if_plan_to_signal=True)
 
194
        port_line = process.stderr.readline()
 
195
        prefix = 'listening on port: '
 
196
        self.assertStartsWith(port_line, prefix)
 
197
        port = int(port_line[len(prefix):])
 
198
        url = 'bzr://localhost:%d/' % port
 
199
        self.permit_url(url)
 
200
        return process, url
 
201
 
 
202
    def test_bzr_serve_quiet(self):
 
203
        self.make_branch('.')
 
204
        args = ['--listen', 'localhost', '--port', '0', '--quiet']
 
205
        out, err = self.run_bzr_serve_then_func(args, retcode=3)
 
206
        self.assertEqual('', out)
 
207
        self.assertEqual('', err)
 
208
 
 
209
    def test_bzr_serve_inet_readonly(self):
 
210
        """brz server should provide a read only filesystem by default."""
 
211
        process, transport = self.start_server_inet()
 
212
        self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
 
213
        self.assertInetServerShutsdownCleanly(process)
 
214
 
 
215
    def test_bzr_serve_inet_readwrite(self):
 
216
        # Make a branch
 
217
        self.make_branch('.')
 
218
 
 
219
        process, transport = self.start_server_inet(['--allow-writes'])
 
220
 
 
221
        # We get a working branch, and can create a directory
 
222
        branch = ControlDir.open_from_transport(transport).open_branch()
 
223
        self.make_read_requests(branch)
 
224
        transport.mkdir('adir')
 
225
        self.assertInetServerShutsdownCleanly(process)
 
226
 
 
227
    def test_bzr_serve_port_readonly(self):
 
228
        """brz server should provide a read only filesystem by default."""
 
229
        process, url = self.start_server_port()
 
230
        t = transport.get_transport_from_url(url)
 
231
        self.assertRaises(errors.TransportNotPossible, t.mkdir, 'adir')
 
232
        self.assertServerFinishesCleanly(process)
 
233
 
 
234
    def test_bzr_serve_port_readwrite(self):
 
235
        # Make a branch
 
236
        self.make_branch('.')
 
237
 
 
238
        process, url = self.start_server_port(['--allow-writes'])
 
239
 
 
240
        # Connect to the server
 
241
        branch = Branch.open(url)
 
242
        self.make_read_requests(branch)
 
243
        self.assertServerFinishesCleanly(process)
 
244
 
 
245
    def test_bzr_serve_supports_protocol(self):
 
246
        # Make a branch
 
247
        self.make_branch('.')
 
248
 
 
249
        process, url = self.start_server_port(['--allow-writes',
 
250
                                               '--protocol=bzr'])
 
251
 
 
252
        # Connect to the server
 
253
        branch = Branch.open(url)
 
254
        self.make_read_requests(branch)
 
255
        self.assertServerFinishesCleanly(process)
 
256
 
 
257
    def test_bzr_serve_dhpss(self):
 
258
        # This is a smoke test that the server doesn't crash when run with
 
259
        # -Dhpss, and does drop some hpss logging to the file.
 
260
        self.make_branch('.')
 
261
        log_fname = self.test_dir + '/server.log'
 
262
        self.overrideEnv('BRZ_LOG', log_fname)
 
263
        process, transport = self.start_server_inet(['-Dhpss'])
 
264
        branch = ControlDir.open_from_transport(transport).open_branch()
 
265
        self.make_read_requests(branch)
 
266
        self.assertInetServerShutsdownCleanly(process)
 
267
        f = open(log_fname, 'rb')
 
268
        content = f.read()
 
269
        f.close()
 
270
        self.assertContainsRe(content, r'hpss request: \[[0-9-]+\]')
 
271
 
 
272
    def test_bzr_serve_supports_configurable_timeout(self):
 
273
        gs = config.GlobalStack()
 
274
        gs.set('serve.client_timeout', 0.2)
 
275
        # Save the config as the subprocess will use it
 
276
        gs.store.save()
 
277
        process, url = self.start_server_port()
 
278
        self.build_tree_contents([('a_file', 'contents\n')])
 
279
        # We can connect and issue a request
 
280
        t = transport.get_transport_from_url(url)
 
281
        self.assertEqual('contents\n', t.get_bytes('a_file'))
 
282
        # However, if we just wait for more content from the server, it will
 
283
        # eventually disconnect us.
 
284
        m = t.get_smart_medium()
 
285
        m.read_bytes(1)
 
286
        # Now, we wait for timeout to trigger
 
287
        err = process.stderr.readline()
 
288
        self.assertEqual(
 
289
            'Connection Timeout: disconnecting client after 0.2 seconds\n',
 
290
            err)
 
291
        self.assertServerFinishesCleanly(process)
 
292
 
 
293
    def test_bzr_serve_supports_client_timeout(self):
 
294
        process, url = self.start_server_port(['--client-timeout=0.1'])
 
295
        self.build_tree_contents([('a_file', 'contents\n')])
 
296
        # We can connect and issue a request
 
297
        t = transport.get_transport_from_url(url)
 
298
        self.assertEqual('contents\n', t.get_bytes('a_file'))
 
299
        # However, if we just wait for more content from the server, it will
 
300
        # eventually disconnect us.
 
301
        # TODO: Use something like signal.alarm() so that if the server doesn't
 
302
        #       properly handle the timeout, we end up failing the test instead
 
303
        #       of hanging forever.
 
304
        m = t.get_smart_medium()
 
305
        m.read_bytes(1)
 
306
        # Now, we wait for timeout to trigger
 
307
        err = process.stderr.readline()
 
308
        self.assertEqual(
 
309
            'Connection Timeout: disconnecting client after 0.1 seconds\n',
 
310
            err)
 
311
        self.assertServerFinishesCleanly(process)
 
312
 
 
313
    def test_bzr_serve_graceful_shutdown(self):
 
314
        big_contents = 'a'*64*1024
 
315
        self.build_tree_contents([('bigfile', big_contents)])
 
316
        process, url = self.start_server_port(['--client-timeout=1.0'])
 
317
        t = transport.get_transport_from_url(url)
 
318
        m = t.get_smart_medium()
 
319
        c = client._SmartClient(m)
 
320
        # Start, but don't finish a response
 
321
        resp, response_handler = c.call_expecting_body('get', 'bigfile')
 
322
        self.assertEqual(('ok',), resp)
 
323
        # Note: process.send_signal is a Python 2.6ism
 
324
        process.send_signal(signal.SIGHUP)
 
325
        # Wait for the server to notice the signal, and then read the actual
 
326
        # body of the response. That way we know that it is waiting for the
 
327
        # request to finish
 
328
        self.assertEqual('Requested to stop gracefully\n',
 
329
                         process.stderr.readline())
 
330
        self.assertEqual('Waiting for 1 client(s) to finish\n',
 
331
                         process.stderr.readline())
 
332
        body = response_handler.read_body_bytes()
 
333
        if body != big_contents:
 
334
            self.fail('Failed to properly read the contents of "bigfile"')
 
335
        # Now that our request is finished, the medium should notice it has
 
336
        # been disconnected.
 
337
        self.assertEqual('', m.read_bytes(1))
 
338
        # And the server should be stopping
 
339
        self.assertEqual(0, process.wait())
 
340
 
 
341
 
 
342
class TestCmdServeChrooting(TestBzrServeBase):
 
343
 
 
344
    def test_serve_tcp(self):
 
345
        """'brz serve' wraps the given --directory in a ChrootServer.
 
346
 
 
347
        So requests that search up through the parent directories (like
 
348
        find_repositoryV3) will give "not found" responses, rather than
 
349
        InvalidURLJoin or jail break errors.
 
350
        """
 
351
        t = self.get_transport()
 
352
        t.mkdir('server-root')
 
353
        self.run_bzr_serve_then_func(
 
354
            ['--listen', '127.0.0.1', '--port', '0',
 
355
             '--directory', t.local_abspath('server-root'),
 
356
             '--allow-writes'],
 
357
            func=self.when_server_started)
 
358
        # The when_server_started method issued a find_repositoryV3 that should
 
359
        # fail with 'norepository' because there are no repositories inside the
 
360
        # --directory.
 
361
        self.assertEqual(('norepository',), self.client_resp)
 
362
 
 
363
    def when_server_started(self):
 
364
        # Connect to the TCP server and issue some requests and see what comes
 
365
        # back.
 
366
        client_medium = medium.SmartTCPClientMedium(
 
367
            '127.0.0.1', self.tcp_server.port,
 
368
            'bzr://localhost:%d/' % (self.tcp_server.port,))
 
369
        smart_client = client._SmartClient(client_medium)
 
370
        resp = smart_client.call('mkdir', 'foo', '')
 
371
        resp = smart_client.call('BzrDirFormat.initialize', 'foo/')
 
372
        try:
 
373
            resp = smart_client.call('BzrDir.find_repositoryV3', 'foo/')
 
374
        except errors.ErrorFromSmartServer as e:
 
375
            resp = e.error_tuple
 
376
        self.client_resp = resp
 
377
        client_medium.disconnect()
 
378
 
 
379
 
 
380
class TestUserdirExpansion(TestCaseWithMemoryTransport):
 
381
 
 
382
    @staticmethod
 
383
    def fake_expanduser(path):
 
384
        """A simple, environment-independent, function for the duration of this
 
385
        test.
 
386
 
 
387
        Paths starting with a path segment of '~user' will expand to start with
 
388
        '/home/user/'.  Every other path will be unchanged.
 
389
        """
 
390
        if path.split('/', 1)[0] == '~user':
 
391
            return '/home/user' + path[len('~user'):]
 
392
        return path
 
393
 
 
394
    def make_test_server(self, base_path='/'):
 
395
        """Make and start a BzrServerFactory, backed by a memory transport, and
 
396
        creat '/home/user' in that transport.
 
397
        """
 
398
        bzr_server = BzrServerFactory(
 
399
            self.fake_expanduser, lambda t: base_path)
 
400
        mem_transport = self.get_transport()
 
401
        mem_transport.mkdir_multi(['home', 'home/user'])
 
402
        bzr_server.set_up(mem_transport, None, None, inet=True, timeout=4.0)
 
403
        self.addCleanup(bzr_server.tear_down)
 
404
        return bzr_server
 
405
 
 
406
    def test_bzr_serve_expands_userdir(self):
 
407
        bzr_server = self.make_test_server()
 
408
        self.assertTrue(bzr_server.smart_server.backing_transport.has('~user'))
 
409
 
 
410
    def test_bzr_serve_does_not_expand_userdir_outside_base(self):
 
411
        bzr_server = self.make_test_server('/foo')
 
412
        self.assertFalse(bzr_server.smart_server.backing_transport.has('~user'))
 
413
 
 
414
    def test_get_base_path(self):
 
415
        """cmd_serve will turn the --directory option into a LocalTransport
 
416
        (optionally decorated with 'readonly+').  BzrServerFactory can
 
417
        determine the original --directory from that transport.
 
418
        """
 
419
        # URLs always include the trailing slash, and get_base_path returns it
 
420
        base_dir = osutils.abspath('/a/b/c') + '/'
 
421
        base_url = urlutils.local_path_to_url(base_dir) + '/'
 
422
        # Define a fake 'protocol' to capture the transport that cmd_serve
 
423
        # passes to serve_bzr.
 
424
        def capture_transport(transport, host, port, inet, timeout):
 
425
            self.bzr_serve_transport = transport
 
426
        cmd = builtins.cmd_serve()
 
427
        # Read-only
 
428
        cmd.run(directory=base_dir, protocol=capture_transport)
 
429
        server_maker = BzrServerFactory()
 
430
        self.assertEqual(
 
431
            'readonly+%s' % base_url, self.bzr_serve_transport.base)
 
432
        self.assertEqual(
 
433
            base_dir, server_maker.get_base_path(self.bzr_serve_transport))
 
434
        # Read-write
 
435
        cmd.run(directory=base_dir, protocol=capture_transport,
 
436
            allow_writes=True)
 
437
        server_maker = BzrServerFactory()
 
438
        self.assertEqual(base_url, self.bzr_serve_transport.base)
 
439
        self.assertEqual(base_dir,
 
440
            server_maker.get_base_path(self.bzr_serve_transport))
 
441
        # Read-only, from a URL
 
442
        cmd.run(directory=base_url, protocol=capture_transport)
 
443
        server_maker = BzrServerFactory()
 
444
        self.assertEqual(
 
445
            'readonly+%s' % base_url, self.bzr_serve_transport.base)
 
446
        self.assertEqual(
 
447
            base_dir, server_maker.get_base_path(self.bzr_serve_transport))