27
26
from bzrlib import (
30
30
revision as _mod_revision,
33
35
from bzrlib.branch import Branch
34
36
from bzrlib.bzrdir import BzrDir
35
from bzrlib.errors import ParamikoNotPresent
36
37
from bzrlib.smart import client, medium
37
from bzrlib.smart.server import SmartTCPServer
38
from bzrlib.tests import TestCaseWithTransport, TestSkipped
39
from bzrlib.trace import mutter
40
from bzrlib.transport import get_transport, remote
43
class TestBzrServe(TestCaseWithTransport):
38
from bzrlib.smart.server import (
42
from bzrlib.tests import (
43
TestCaseWithMemoryTransport,
44
TestCaseWithTransport,
46
from bzrlib.transport import remote
49
class TestBzrServeBase(TestCaseWithTransport):
51
def run_bzr_serve_then_func(self, serve_args, retcode=0, func=None,
52
*func_args, **func_kwargs):
53
"""Run 'bzr serve', and run the given func in a thread once the server
56
When 'func' terminates, the server will be terminated too.
58
Returns stdout and stderr.
60
def on_server_start_thread(tcp_server):
61
"""This runs concurrently with the server thread.
63
The server is interrupted as soon as ``func`` finishes, even if an
64
exception is encountered.
68
self.tcp_server = tcp_server
71
func(*func_args, **func_kwargs)
73
# Log errors to make some test failures a little less
75
trace.mutter('func broke: %r', e)
77
# Then stop the server
78
trace.mutter('interrupting...')
79
thread.interrupt_main()
80
# When the hook is fired, it just starts ``on_server_start_thread`` and
82
def on_server_start(backing_urls, tcp_server):
84
target=on_server_start_thread, args=(tcp_server,))
87
SmartTCPServer.hooks.install_named_hook(
88
'server_started_ex', on_server_start,
89
'run_bzr_serve_then_func hook')
92
out, err = self.run_bzr(['serve'] + list(serve_args),
94
except KeyboardInterrupt, e:
99
class TestBzrServe(TestBzrServeBase):
102
super(TestBzrServe, self).setUp()
103
self.disable_missing_extensions_warning()
105
def test_server_exception_with_hook(self):
106
"""Catch exception from the server in the server_exception hook.
108
We use ``run_bzr_serve_then_func`` without a ``func`` so the server
109
will receive a KeyboardInterrupt exception we want to catch.
112
if exception[0] is KeyboardInterrupt:
113
sys.stderr.write('catching KeyboardInterrupt\n')
117
SmartTCPServer.hooks.install_named_hook(
118
'server_exception', hook,
119
'test_server_except_hook hook')
120
args = ['--port', 'localhost:0', '--quiet']
121
out, err = self.run_bzr_serve_then_func(args, retcode=0)
122
self.assertEqual('catching KeyboardInterrupt\n', err)
124
def test_server_exception_no_hook(self):
125
"""test exception without hook returns error"""
127
out, err = self.run_bzr_serve_then_func(args, retcode=3)
45
129
def assertInetServerShutsdownCleanly(self, process):
46
130
"""Shutdown the server process looking for errors."""
79
163
finish_bzr_subprocess, a client for the server, and a transport.
81
165
# Serve from the current directory
82
process = self.start_bzr_subprocess(['serve', '--inet'])
166
args = ['serve', '--inet']
167
args.extend(extra_options)
168
process = self.start_bzr_subprocess(args)
84
170
# Connect to the server
85
171
# We use this url because while this is no valid URL to connect to this
86
172
# server instance, the transport needs a URL.
87
173
url = 'bzr://localhost/'
88
175
client_medium = medium.SmartSimplePipesClientMedium(
89
176
process.stdout, process.stdin, url)
90
177
transport = remote.RemoteTransport(url, medium=client_medium)
105
192
prefix = 'listening on port: '
106
193
self.assertStartsWith(port_line, prefix)
107
194
port = int(port_line[len(prefix):])
108
return process,'bzr://localhost:%d/' % port
195
url = 'bzr://localhost:%d/' % port
199
def test_bzr_serve_quiet(self):
200
self.make_branch('.')
201
args = ['--port', 'localhost:0', '--quiet']
202
out, err = self.run_bzr_serve_then_func(args, retcode=3)
203
self.assertEqual('', out)
204
self.assertEqual('', err)
110
206
def test_bzr_serve_inet_readonly(self):
111
207
"""bzr server should provide a read only filesystem by default."""
120
216
process, transport = self.start_server_inet(['--allow-writes'])
122
# We get a working branch
218
# We get a working branch, and can create a directory
123
219
branch = BzrDir.open_from_transport(transport).open_branch()
124
220
self.make_read_requests(branch)
221
transport.mkdir('adir')
125
222
self.assertInetServerShutsdownCleanly(process)
127
224
def test_bzr_serve_port_readonly(self):
128
225
"""bzr server should provide a read only filesystem by default."""
129
226
process, url = self.start_server_port()
130
transport = get_transport(url)
131
self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
227
t = transport.get_transport(url)
228
self.assertRaises(errors.TransportNotPossible, t.mkdir, 'adir')
132
229
self.assertServerFinishesCleanly(process)
134
231
def test_bzr_serve_port_readwrite(self):
154
251
self.make_read_requests(branch)
155
252
self.assertServerFinishesCleanly(process)
157
def test_bzr_connect_to_bzr_ssh(self):
158
"""User acceptance that get_transport of a bzr+ssh:// behaves correctly.
160
bzr+ssh:// should cause bzr to run a remote bzr smart server over SSH.
163
from bzrlib.transport.sftp import SFTPServer
164
except ParamikoNotPresent:
165
raise TestSkipped('Paramiko not installed')
166
from bzrlib.tests.stub_sftp import StubServer
169
self.make_branch('a_branch')
171
# Start an SSH server
172
self.command_executed = []
173
# XXX: This is horrible -- we define a really dumb SSH server that
174
# executes commands, and manage the hooking up of stdin/out/err to the
175
# SSH channel ourselves. Surely this has already been implemented
177
class StubSSHServer(StubServer):
181
def check_channel_exec_request(self, channel, command):
182
self.test.command_executed.append(command)
183
proc = subprocess.Popen(
184
command, shell=True, stdin=subprocess.PIPE,
185
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
187
# XXX: horribly inefficient, not to mention ugly.
188
# Start a thread for each of stdin/out/err, and relay bytes from
189
# the subprocess to channel and vice versa.
190
def ferry_bytes(read, write, close):
199
(channel.recv, proc.stdin.write, proc.stdin.close),
200
(proc.stdout.read, channel.sendall, channel.close),
201
(proc.stderr.read, channel.sendall_stderr, channel.close)]
202
for read, write, close in file_functions:
203
t = threading.Thread(
204
target=ferry_bytes, args=(read, write, close))
209
ssh_server = SFTPServer(StubSSHServer)
210
# XXX: We *don't* want to override the default SSH vendor, so we set
211
# _vendor to what _get_ssh_vendor returns.
213
self.addCleanup(ssh_server.tearDown)
214
port = ssh_server._listener.port
216
# Access the branch via a bzr+ssh URL. The BZR_REMOTE_PATH environment
217
# variable is used to tell bzr what command to run on the remote end.
218
path_to_branch = osutils.abspath('a_branch')
220
orig_bzr_remote_path = os.environ.get('BZR_REMOTE_PATH')
221
bzr_remote_path = self.get_bzr_path()
222
if sys.platform == 'win32':
223
bzr_remote_path = sys.executable + ' ' + self.get_bzr_path()
224
os.environ['BZR_REMOTE_PATH'] = bzr_remote_path
226
if sys.platform == 'win32':
227
path_to_branch = os.path.splitdrive(path_to_branch)[1]
228
branch = Branch.open(
229
'bzr+ssh://fred:secret@localhost:%d%s' % (port, path_to_branch))
230
self.make_read_requests(branch)
231
# Check we can perform write operations
232
branch.bzrdir.root_transport.mkdir('foo')
234
# Restore the BZR_REMOTE_PATH environment variable back to its
236
if orig_bzr_remote_path is None:
237
del os.environ['BZR_REMOTE_PATH']
239
os.environ['BZR_REMOTE_PATH'] = orig_bzr_remote_path
242
['%s serve --inet --directory=/ --allow-writes'
244
self.command_executed)
247
class TestCmdServeChrooting(TestCaseWithTransport):
254
def test_bzr_serve_dhpss(self):
255
# This is a smoke test that the server doesn't crash when run with
256
# -Dhpss, and does drop some hpss logging to the file.
257
self.make_branch('.')
258
log_fname = os.getcwd() + '/server.log'
259
self.overrideEnv('BZR_LOG', log_fname)
260
process, transport = self.start_server_inet(['-Dhpss'])
261
branch = BzrDir.open_from_transport(transport).open_branch()
262
self.make_read_requests(branch)
263
self.assertInetServerShutsdownCleanly(process)
264
f = open(log_fname, 'rb')
267
self.assertContainsRe(content, r'hpss request: \[[0-9-]+\]')
270
class TestCmdServeChrooting(TestBzrServeBase):
249
272
def test_serve_tcp(self):
250
273
"""'bzr serve' wraps the given --directory in a ChrootServer.
256
279
t = self.get_transport()
257
280
t.mkdir('server-root')
258
281
self.run_bzr_serve_then_func(
259
['--port', '0', '--directory', t.local_abspath('server-root'),
282
['--port', '127.0.0.1:0',
283
'--directory', t.local_abspath('server-root'),
260
284
'--allow-writes'],
261
self.when_server_started)
285
func=self.when_server_started)
262
286
# The when_server_started method issued a find_repositoryV3 that should
263
287
# fail with 'norepository' because there are no repositories inside the
265
289
self.assertEqual(('norepository',), self.client_resp)
267
def run_bzr_serve_then_func(self, serve_args, func, *func_args,
269
"""Run 'bzr serve', and run the given func in a thread once the server
272
When 'func' terminates, the server will be terminated too.
275
def on_server_start(backing_urls, tcp_server):
276
t = threading.Thread(
277
target=on_server_start_thread, args=(tcp_server,))
279
def on_server_start_thread(tcp_server):
282
self.tcp_server = tcp_server
284
func(*func_args, **func_kwargs)
286
# Log errors to make some test failures a little less
288
mutter('func broke: %r', e)
290
# Then stop the server
291
mutter('interrupting...')
292
thread.interrupt_main()
293
SmartTCPServer.hooks.install_named_hook(
294
'server_started_ex', on_server_start,
295
'run_bzr_serve_then_func hook')
298
self.run_bzr(['serve'] + list(serve_args))
299
except KeyboardInterrupt:
302
291
def when_server_started(self):
303
292
# Connect to the TCP server and issue some requests and see what comes
316
305
client_medium.disconnect()
308
class TestUserdirExpansion(TestCaseWithMemoryTransport):
311
def fake_expanduser(path):
312
"""A simple, environment-independent, function for the duration of this
315
Paths starting with a path segment of '~user' will expand to start with
316
'/home/user/'. Every other path will be unchanged.
318
if path.split('/', 1)[0] == '~user':
319
return '/home/user' + path[len('~user'):]
322
def make_test_server(self, base_path='/'):
323
"""Make and start a BzrServerFactory, backed by a memory transport, and
324
creat '/home/user' in that transport.
326
bzr_server = BzrServerFactory(
327
self.fake_expanduser, lambda t: base_path)
328
mem_transport = self.get_transport()
329
mem_transport.mkdir_multi(['home', 'home/user'])
330
bzr_server.set_up(mem_transport, None, None, inet=True)
331
self.addCleanup(bzr_server.tear_down)
334
def test_bzr_serve_expands_userdir(self):
335
bzr_server = self.make_test_server()
336
self.assertTrue(bzr_server.smart_server.backing_transport.has('~user'))
338
def test_bzr_serve_does_not_expand_userdir_outside_base(self):
339
bzr_server = self.make_test_server('/foo')
340
self.assertFalse(bzr_server.smart_server.backing_transport.has('~user'))
342
def test_get_base_path(self):
343
"""cmd_serve will turn the --directory option into a LocalTransport
344
(optionally decorated with 'readonly+'). BzrServerFactory can
345
determine the original --directory from that transport.
347
# URLs always include the trailing slash, and get_base_path returns it
348
base_dir = osutils.abspath('/a/b/c') + '/'
349
base_url = urlutils.local_path_to_url(base_dir) + '/'
350
# Define a fake 'protocol' to capture the transport that cmd_serve
351
# passes to serve_bzr.
352
def capture_transport(transport, host, port, inet):
353
self.bzr_serve_transport = transport
354
cmd = builtins.cmd_serve()
356
cmd.run(directory=base_dir, protocol=capture_transport)
357
server_maker = BzrServerFactory()
359
'readonly+%s' % base_url, self.bzr_serve_transport.base)
361
base_dir, server_maker.get_base_path(self.bzr_serve_transport))
363
cmd.run(directory=base_dir, protocol=capture_transport,
365
server_maker = BzrServerFactory()
366
self.assertEqual(base_url, self.bzr_serve_transport.base)
367
self.assertEqual(base_dir,
368
server_maker.get_base_path(self.bzr_serve_transport))