/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: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

Show diffs side-by-side

added added

removed removed

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