/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: Robert Collins
  • Date: 2010-05-06 23:41:35 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506234135-yivbzczw1sejxnxc
Lock methods on ``Tree``, ``Branch`` and ``Repository`` are now
expected to return an object which can be used to unlock them. This reduces
duplicate code when using cleanups. The previous 'tokens's returned by
``Branch.lock_write`` and ``Repository.lock_write`` are now attributes
on the result of the lock_write. ``repository.RepositoryWriteLockResult``
and ``branch.BranchWriteLockResult`` document this. (Robert Collins)

``log._get_info_for_log_files`` now takes an add_cleanup callable.
(Robert Collins)

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
 
    transport,
38
34
    urlutils,
39
35
    )
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 (
 
36
from bzrlib.branch import Branch
 
37
from bzrlib.bzrdir import BzrDir
 
38
from bzrlib.smart import client, medium
 
39
from bzrlib.smart.server import BzrServerFactory, SmartTCPServer
 
40
from bzrlib.tests import (
48
41
    TestCaseWithMemoryTransport,
49
42
    TestCaseWithTransport,
 
43
    TestSkipped,
50
44
    )
51
 
from ...transport import remote
 
45
from bzrlib.trace import mutter
 
46
from bzrlib.transport import get_transport, remote
52
47
 
53
48
 
54
49
class TestBzrServeBase(TestCaseWithTransport):
55
50
 
56
51
    def run_bzr_serve_then_func(self, serve_args, retcode=0, func=None,
57
52
                                *func_args, **func_kwargs):
58
 
        """Run 'brz serve', and run the given func in a thread once the server
 
53
        """Run 'bzr serve', and run the given func in a thread once the server
59
54
        has started.
60
 
 
 
55
        
61
56
        When 'func' terminates, the server will be terminated too.
62
 
 
 
57
        
63
58
        Returns stdout and stderr.
64
59
        """
 
60
        # install hook
 
61
        def on_server_start(backing_urls, tcp_server):
 
62
            t = threading.Thread(
 
63
                target=on_server_start_thread, args=(tcp_server,))
 
64
            t.start()
65
65
        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
66
            try:
72
67
                # Run func if set
73
68
                self.tcp_server = tcp_server
74
 
                if func is not None:
 
69
                if not func is None:
75
70
                    try:
76
71
                        func(*func_args, **func_kwargs)
77
 
                    except Exception as e:
 
72
                    except Exception, e:
78
73
                        # Log errors to make some test failures a little less
79
74
                        # mysterious.
80
 
                        trace.mutter('func broke: %r', e)
 
75
                        mutter('func broke: %r', e)
81
76
            finally:
82
77
                # 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
 
78
                mutter('interrupting...')
 
79
                thread.interrupt_main()
93
80
        SmartTCPServer.hooks.install_named_hook(
94
81
            'server_started_ex', on_server_start,
95
82
            '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
83
        # start a TCP server
101
84
        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())
 
85
            out, err = self.run_bzr(['serve'] + list(serve_args))
 
86
        except KeyboardInterrupt, e:
 
87
            out, err = e.args
107
88
        return out, err
108
89
 
109
90
 
113
94
        super(TestBzrServe, self).setUp()
114
95
        self.disable_missing_extensions_warning()
115
96
 
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
97
    def assertInetServerShutsdownCleanly(self, process):
141
98
        """Shutdown the server process looking for errors."""
142
99
        # Shutdown the server: the server should shut down when it cannot read
145
102
        # Hide stdin from the subprocess module, so it won't fail to close it.
146
103
        process.stdin = None
147
104
        result = self.finish_bzr_subprocess(process)
148
 
        self.assertEqual(b'', result[0])
149
 
        self.assertEqual(b'', result[1])
 
105
        self.assertEqual('', result[0])
 
106
        self.assertEqual('', result[1])
150
107
 
151
108
    def assertServerFinishesCleanly(self, process):
152
 
        """Shutdown the brz serve instance process looking for errors."""
 
109
        """Shutdown the bzr serve instance process looking for errors."""
153
110
        # Shutdown the server
154
111
        result = self.finish_bzr_subprocess(process, retcode=3,
155
112
                                            send_signal=signal.SIGINT)
156
 
        self.assertEqual(b'', result[0])
157
 
        self.assertEqual(b'brz: interrupted\n', result[1])
 
113
        self.assertEqual('', result[0])
 
114
        self.assertEqual('bzr: interrupted\n', result[1])
158
115
 
159
116
    def make_read_requests(self, branch):
160
117
        """Do some read only requests."""
161
 
        with branch.lock_read():
 
118
        branch.lock_read()
 
119
        try:
162
120
            branch.repository.all_revision_ids()
163
121
            self.assertEqual(_mod_revision.NULL_REVISION,
164
122
                             _mod_revision.ensure_null(branch.last_revision()))
 
123
        finally:
 
124
            branch.unlock()
165
125
 
166
126
    def start_server_inet(self, extra_options=()):
167
 
        """Start a brz server subprocess using the --inet option.
 
127
        """Start a bzr server subprocess using the --inet option.
168
128
 
169
129
        :param extra_options: extra options to give the server.
170
 
        :return: a tuple with the brz process handle for passing to
 
130
        :return: a tuple with the bzr process handle for passing to
171
131
            finish_bzr_subprocess, a client for the server, and a transport.
172
132
        """
173
133
        # Serve from the current directory
186
146
        return process, transport
187
147
 
188
148
    def start_server_port(self, extra_options=()):
189
 
        """Start a brz server subprocess.
 
149
        """Start a bzr server subprocess.
190
150
 
191
151
        :param extra_options: extra options to give the server.
192
 
        :return: a tuple with the brz process handle for passing to
 
152
        :return: a tuple with the bzr process handle for passing to
193
153
            finish_bzr_subprocess, and the base url for the server.
194
154
        """
195
155
        # Serve from the current directory
196
 
        args = ['serve', '--listen', 'localhost', '--port', '0']
 
156
        args = ['serve', '--port', 'localhost:0']
197
157
        args.extend(extra_options)
198
158
        process = self.start_bzr_subprocess(args, skip_if_plan_to_signal=True)
199
159
        port_line = process.stderr.readline()
200
 
        prefix = b'listening on port: '
 
160
        prefix = 'listening on port: '
201
161
        self.assertStartsWith(port_line, prefix)
202
162
        port = int(port_line[len(prefix):])
203
163
        url = 'bzr://localhost:%d/' % port
204
164
        self.permit_url(url)
205
165
        return process, url
206
 
 
 
166
    
207
167
    def test_bzr_serve_quiet(self):
208
168
        self.make_branch('.')
209
 
        args = ['--listen', 'localhost', '--port', '0', '--quiet']
 
169
        args = ['--port', 'localhost:0', '--quiet']
210
170
        out, err = self.run_bzr_serve_then_func(args, retcode=3)
211
171
        self.assertEqual('', out)
212
172
        self.assertEqual('', err)
213
173
 
214
174
    def test_bzr_serve_inet_readonly(self):
215
 
        """brz server should provide a read only filesystem by default."""
 
175
        """bzr server should provide a read only filesystem by default."""
216
176
        process, transport = self.start_server_inet()
217
177
        self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
218
178
        self.assertInetServerShutsdownCleanly(process)
224
184
        process, transport = self.start_server_inet(['--allow-writes'])
225
185
 
226
186
        # We get a working branch, and can create a directory
227
 
        branch = ControlDir.open_from_transport(transport).open_branch()
 
187
        branch = BzrDir.open_from_transport(transport).open_branch()
228
188
        self.make_read_requests(branch)
229
189
        transport.mkdir('adir')
230
190
        self.assertInetServerShutsdownCleanly(process)
231
191
 
232
192
    def test_bzr_serve_port_readonly(self):
233
 
        """brz server should provide a read only filesystem by default."""
 
193
        """bzr server should provide a read only filesystem by default."""
234
194
        process, url = self.start_server_port()
235
 
        t = transport.get_transport_from_url(url)
236
 
        self.assertRaises(errors.TransportNotPossible, t.mkdir, 'adir')
 
195
        transport = get_transport(url)
 
196
        self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
237
197
        self.assertServerFinishesCleanly(process)
238
198
 
239
199
    def test_bzr_serve_port_readwrite(self):
263
223
        # This is a smoke test that the server doesn't crash when run with
264
224
        # -Dhpss, and does drop some hpss logging to the file.
265
225
        self.make_branch('.')
266
 
        log_fname = self.test_dir + '/server.log'
267
 
        self.overrideEnv('BRZ_LOG', log_fname)
 
226
        log_fname = os.getcwd() + '/server.log'
 
227
        self._captureVar('BZR_LOG', log_fname)
268
228
        process, transport = self.start_server_inet(['-Dhpss'])
269
 
        branch = ControlDir.open_from_transport(transport).open_branch()
 
229
        branch = BzrDir.open_from_transport(transport).open_branch()
270
230
        self.make_read_requests(branch)
271
231
        self.assertInetServerShutsdownCleanly(process)
272
232
        f = open(log_fname, 'rb')
273
233
        content = f.read()
274
234
        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())
 
235
        self.assertContainsRe(content, r'hpss request: \[[0-9-]+\]')
345
236
 
346
237
 
347
238
class TestCmdServeChrooting(TestBzrServeBase):
348
239
 
349
240
    def test_serve_tcp(self):
350
 
        """'brz serve' wraps the given --directory in a ChrootServer.
 
241
        """'bzr serve' wraps the given --directory in a ChrootServer.
351
242
 
352
243
        So requests that search up through the parent directories (like
353
244
        find_repositoryV3) will give "not found" responses, rather than
356
247
        t = self.get_transport()
357
248
        t.mkdir('server-root')
358
249
        self.run_bzr_serve_then_func(
359
 
            ['--listen', '127.0.0.1', '--port', '0',
 
250
            ['--port', '127.0.0.1:0',
360
251
             '--directory', t.local_abspath('server-root'),
361
252
             '--allow-writes'],
362
253
            func=self.when_server_started)
363
254
        # The when_server_started method issued a find_repositoryV3 that should
364
255
        # fail with 'norepository' because there are no repositories inside the
365
256
        # --directory.
366
 
        self.assertEqual((b'norepository',), self.client_resp)
 
257
        self.assertEqual(('norepository',), self.client_resp)
367
258
 
368
259
    def when_server_started(self):
369
260
        # Connect to the TCP server and issue some requests and see what comes
376
267
        resp = smart_client.call('BzrDirFormat.initialize', 'foo/')
377
268
        try:
378
269
            resp = smart_client.call('BzrDir.find_repositoryV3', 'foo/')
379
 
        except errors.ErrorFromSmartServer as e:
 
270
        except errors.ErrorFromSmartServer, e:
380
271
            resp = e.error_tuple
381
272
        self.client_resp = resp
382
273
        client_medium.disconnect()
384
275
 
385
276
class TestUserdirExpansion(TestCaseWithMemoryTransport):
386
277
 
387
 
    @staticmethod
388
 
    def fake_expanduser(path):
 
278
    def fake_expanduser(self, path):
389
279
        """A simple, environment-independent, function for the duration of this
390
280
        test.
391
281
 
403
293
        bzr_server = BzrServerFactory(
404
294
            self.fake_expanduser, lambda t: base_path)
405
295
        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)
 
296
        mem_transport.mkdir_multi(['home', 'home/user'])
 
297
        bzr_server.set_up(mem_transport, None, None, inet=True)
409
298
        self.addCleanup(bzr_server.tear_down)
410
299
        return bzr_server
411
300
 
415
304
 
416
305
    def test_bzr_serve_does_not_expand_userdir_outside_base(self):
417
306
        bzr_server = self.make_test_server('/foo')
418
 
        self.assertFalse(
419
 
            bzr_server.smart_server.backing_transport.has('~user'))
 
307
        self.assertFalse(bzr_server.smart_server.backing_transport.has('~user'))
420
308
 
421
309
    def test_get_base_path(self):
422
310
        """cmd_serve will turn the --directory option into a LocalTransport
428
316
        base_url = urlutils.local_path_to_url(base_dir) + '/'
429
317
        # Define a fake 'protocol' to capture the transport that cmd_serve
430
318
        # passes to serve_bzr.
431
 
 
432
 
        def capture_transport(transport, host, port, inet, timeout):
 
319
        def capture_transport(transport, host, port, inet):
433
320
            self.bzr_serve_transport = transport
434
321
        cmd = builtins.cmd_serve()
435
322
        # Read-only
441
328
            base_dir, server_maker.get_base_path(self.bzr_serve_transport))
442
329
        # Read-write
443
330
        cmd.run(directory=base_dir, protocol=capture_transport,
444
 
                allow_writes=True)
 
331
            allow_writes=True)
445
332
        server_maker = BzrServerFactory()
446
333
        self.assertEqual(base_url, self.bzr_serve_transport.base)
447
334
        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))
 
335
            server_maker.get_base_path(self.bzr_serve_transport))
 
336