18
18
"""Tests of the bzr serve command."""
28
26
from bzrlib import (
33
31
revision as _mod_revision,
36
36
from bzrlib.branch import Branch
37
from bzrlib.bzrdir import BzrDir
37
from bzrlib.controldir import ControlDir
38
38
from bzrlib.smart import client, medium
39
from bzrlib.smart.server import BzrServerFactory, SmartTCPServer
39
from bzrlib.smart.server import (
40
43
from bzrlib.tests import (
41
44
TestCaseWithMemoryTransport,
42
45
TestCaseWithTransport,
45
from bzrlib.trace import mutter
46
from bzrlib.transport import get_transport, remote
47
from bzrlib.transport import remote
49
50
class TestBzrServeBase(TestCaseWithTransport):
52
53
*func_args, **func_kwargs):
53
54
"""Run 'bzr serve', and run the given func in a thread once the server
56
57
When 'func' terminates, the server will be terminated too.
58
59
Returns stdout and stderr.
61
def on_server_start(backing_urls, tcp_server):
63
target=on_server_start_thread, args=(tcp_server,))
65
61
def on_server_start_thread(tcp_server):
62
"""This runs concurrently with the server thread.
64
The server is interrupted as soon as ``func`` finishes, even if an
65
exception is encountered.
68
69
self.tcp_server = tcp_server
71
72
func(*func_args, **func_kwargs)
72
73
except Exception, e:
73
74
# Log errors to make some test failures a little less
75
mutter('func broke: %r', e)
76
trace.mutter('func broke: %r', e)
77
78
# Then stop the server
78
mutter('interrupting...')
79
trace.mutter('interrupting...')
79
80
thread.interrupt_main()
81
# When the hook is fired, it just starts ``on_server_start_thread`` and
83
def on_server_start(backing_urls, tcp_server):
85
target=on_server_start_thread, args=(tcp_server,))
80
88
SmartTCPServer.hooks.install_named_hook(
81
89
'server_started_ex', on_server_start,
82
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
94
self.overrideAttr(SmartTCPServer, '_ACCEPT_TIMEOUT', 0.1)
83
95
# start a TCP server
85
out, err = self.run_bzr(['serve'] + list(serve_args))
97
out, err = self.run_bzr(['serve'] + list(serve_args),
86
99
except KeyboardInterrupt, e:
94
107
super(TestBzrServe, self).setUp()
95
108
self.disable_missing_extensions_warning()
110
def test_server_exception_with_hook(self):
111
"""Catch exception from the server in the server_exception hook.
113
We use ``run_bzr_serve_then_func`` without a ``func`` so the server
114
will receive a KeyboardInterrupt exception we want to catch.
117
if exception[0] is KeyboardInterrupt:
118
sys.stderr.write('catching KeyboardInterrupt\n')
122
SmartTCPServer.hooks.install_named_hook(
123
'server_exception', hook,
124
'test_server_except_hook hook')
125
args = ['--listen', 'localhost', '--port', '0', '--quiet']
126
out, err = self.run_bzr_serve_then_func(args, retcode=0)
127
self.assertEqual('catching KeyboardInterrupt\n', err)
129
def test_server_exception_no_hook(self):
130
"""test exception without hook returns error"""
132
out, err = self.run_bzr_serve_then_func(args, retcode=3)
97
134
def assertInetServerShutsdownCleanly(self, process):
98
135
"""Shutdown the server process looking for errors."""
99
136
# Shutdown the server: the server should shut down when it cannot read
153
190
finish_bzr_subprocess, and the base url for the server.
155
192
# Serve from the current directory
156
args = ['serve', '--port', 'localhost:0']
193
args = ['serve', '--listen', 'localhost', '--port', '0']
157
194
args.extend(extra_options)
158
195
process = self.start_bzr_subprocess(args, skip_if_plan_to_signal=True)
159
196
port_line = process.stderr.readline()
163
200
url = 'bzr://localhost:%d/' % port
164
201
self.permit_url(url)
165
202
return process, url
167
204
def test_bzr_serve_quiet(self):
168
205
self.make_branch('.')
169
args = ['--port', 'localhost:0', '--quiet']
206
args = ['--listen', 'localhost', '--port', '0', '--quiet']
170
207
out, err = self.run_bzr_serve_then_func(args, retcode=3)
171
208
self.assertEqual('', out)
172
209
self.assertEqual('', err)
192
229
def test_bzr_serve_port_readonly(self):
193
230
"""bzr server should provide a read only filesystem by default."""
194
231
process, url = self.start_server_port()
195
transport = get_transport(url)
196
self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
232
t = transport.get_transport_from_url(url)
233
self.assertRaises(errors.TransportNotPossible, t.mkdir, 'adir')
197
234
self.assertServerFinishesCleanly(process)
199
236
def test_bzr_serve_port_readwrite(self):
224
261
# -Dhpss, and does drop some hpss logging to the file.
225
262
self.make_branch('.')
226
263
log_fname = os.getcwd() + '/server.log'
227
self._captureVar('BZR_LOG', log_fname)
264
self.overrideEnv('BZR_LOG', log_fname)
228
265
process, transport = self.start_server_inet(['-Dhpss'])
229
branch = BzrDir.open_from_transport(transport).open_branch()
266
branch = ControlDir.open_from_transport(transport).open_branch()
230
267
self.make_read_requests(branch)
231
268
self.assertInetServerShutsdownCleanly(process)
232
269
f = open(log_fname, 'rb')
235
272
self.assertContainsRe(content, r'hpss request: \[[0-9-]+\]')
274
def test_bzr_serve_supports_configurable_timeout(self):
275
gs = config.GlobalStack()
276
gs.set('serve.client_timeout', 0.2)
277
# Save the config as the subprocess will use it
279
process, url = self.start_server_port()
280
self.build_tree_contents([('a_file', 'contents\n')])
281
# We can connect and issue a request
282
t = transport.get_transport_from_url(url)
283
self.assertEqual('contents\n', t.get_bytes('a_file'))
284
# However, if we just wait for more content from the server, it will
285
# eventually disconnect us.
286
m = t.get_smart_medium()
288
# Now, we wait for timeout to trigger
289
err = process.stderr.readline()
291
'Connection Timeout: disconnecting client after 0.2 seconds\n',
293
self.assertServerFinishesCleanly(process)
295
def test_bzr_serve_supports_client_timeout(self):
296
process, url = self.start_server_port(['--client-timeout=0.1'])
297
self.build_tree_contents([('a_file', 'contents\n')])
298
# We can connect and issue a request
299
t = transport.get_transport_from_url(url)
300
self.assertEqual('contents\n', t.get_bytes('a_file'))
301
# However, if we just wait for more content from the server, it will
302
# eventually disconnect us.
303
# TODO: Use something like signal.alarm() so that if the server doesn't
304
# properly handle the timeout, we end up failing the test instead
305
# of hanging forever.
306
m = t.get_smart_medium()
308
# Now, we wait for timeout to trigger
309
err = process.stderr.readline()
311
'Connection Timeout: disconnecting client after 0.1 seconds\n',
313
self.assertServerFinishesCleanly(process)
315
def test_bzr_serve_graceful_shutdown(self):
316
big_contents = 'a'*64*1024
317
self.build_tree_contents([('bigfile', big_contents)])
318
process, url = self.start_server_port(['--client-timeout=1.0'])
319
t = transport.get_transport_from_url(url)
320
m = t.get_smart_medium()
321
c = client._SmartClient(m)
322
# Start, but don't finish a response
323
resp, response_handler = c.call_expecting_body('get', 'bigfile')
324
self.assertEqual(('ok',), resp)
325
# Note: process.send_signal is a Python 2.6ism
326
process.send_signal(signal.SIGHUP)
327
# Wait for the server to notice the signal, and then read the actual
328
# body of the response. That way we know that it is waiting for the
330
self.assertEqual('Requested to stop gracefully\n',
331
process.stderr.readline())
332
self.assertEqual('Waiting for 1 client(s) to finish\n',
333
process.stderr.readline())
334
body = response_handler.read_body_bytes()
335
if body != big_contents:
336
self.fail('Failed to properly read the contents of "bigfile"')
337
# Now that our request is finished, the medium should notice it has
339
self.assertEqual('', m.read_bytes(1))
340
# And the server should be stopping
341
self.assertEqual(0, process.wait())
238
344
class TestCmdServeChrooting(TestBzrServeBase):