13
13
# You should have received a copy of the GNU General Public License
14
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
18
"""Tests of the bzr serve command."""
30
revision as _mod_revision,
25
from bzrlib import errors
35
26
from bzrlib.branch import Branch
36
27
from bzrlib.bzrdir import BzrDir
37
from bzrlib.smart import client, medium
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)
129
def assertInetServerShutsdownCleanly(self, process):
28
from bzrlib.errors import ParamikoNotPresent
29
from bzrlib.tests import TestCaseWithTransport, TestSkipped
30
from bzrlib.transport import get_transport, smart
33
class DoesNotCloseStdOutClient(smart.SmartStreamClient):
34
"""A client that doesn't close stdout upon disconnect().
36
We wish to let stdout remain open so that we can see if the server writes
37
anything to stdout during its shutdown.
42
self._connected = False
43
# The client's out is the server's in.
47
class TestBzrServe(TestCaseWithTransport):
49
def assertInetServerShutsdownCleanly(self, client, process):
130
50
"""Shutdown the server process looking for errors."""
131
# Shutdown the server: the server should shut down when it cannot read
132
# from stdin anymore.
133
process.stdin.close()
51
# Disconnect the client forcefully JUST IN CASE because of __del__'s use
52
# in the smart module.
55
# Shutdown the server: the client should have disconnected cleanly and
56
# closed stdin, so the server process should shut itself down.
57
self.assertTrue(process.stdin.closed)
134
58
# Hide stdin from the subprocess module, so it won't fail to close it.
135
59
process.stdin = None
136
result = self.finish_bzr_subprocess(process)
60
result = self.finish_bzr_subprocess(process, retcode=0)
137
61
self.assertEqual('', result[0])
138
62
self.assertEqual('', result[1])
140
64
def assertServerFinishesCleanly(self, process):
141
65
"""Shutdown the bzr serve instance process looking for errors."""
142
66
# Shutdown the server
188
98
args = ['serve', '--port', 'localhost:0']
189
99
args.extend(extra_options)
190
100
process = self.start_bzr_subprocess(args, skip_if_plan_to_signal=True)
191
port_line = process.stderr.readline()
101
port_line = process.stdout.readline()
192
102
prefix = 'listening on port: '
193
103
self.assertStartsWith(port_line, prefix)
194
104
port = int(port_line[len(prefix):])
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)
105
return process,'bzr://localhost:%d/' % port
206
107
def test_bzr_serve_inet_readonly(self):
207
108
"""bzr server should provide a read only filesystem by default."""
208
process, transport = self.start_server_inet()
109
process, client, transport = self.start_server_inet()
209
110
self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
210
self.assertInetServerShutsdownCleanly(process)
111
# finish with the transport
113
self.assertInetServerShutsdownCleanly(client, process)
212
115
def test_bzr_serve_inet_readwrite(self):
214
117
self.make_branch('.')
216
process, transport = self.start_server_inet(['--allow-writes'])
119
process, client, transport = self.start_server_inet(['--allow-writes'])
218
# We get a working branch, and can create a directory
121
# We get a working branch
219
122
branch = BzrDir.open_from_transport(transport).open_branch()
220
self.make_read_requests(branch)
221
transport.mkdir('adir')
222
self.assertInetServerShutsdownCleanly(process)
123
branch.repository.get_revision_graph()
124
self.assertEqual(None, branch.last_revision())
126
# finish with the transport
129
self.assertInetServerShutsdownCleanly(client, process)
224
131
def test_bzr_serve_port_readonly(self):
225
132
"""bzr server should provide a read only filesystem by default."""
226
133
process, url = self.start_server_port()
227
t = transport.get_transport(url)
228
self.assertRaises(errors.TransportNotPossible, t.mkdir, 'adir')
134
transport = get_transport(url)
135
self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
229
136
self.assertServerFinishesCleanly(process)
231
138
def test_bzr_serve_port_readwrite(self):
237
144
# Connect to the server
238
145
branch = Branch.open(url)
239
self.make_read_requests(branch)
147
# We get a working branch
148
branch.repository.get_revision_graph()
149
self.assertEqual(None, branch.last_revision())
240
151
self.assertServerFinishesCleanly(process)
242
def test_bzr_serve_supports_protocol(self):
153
def test_bzr_serve_no_args(self):
154
"""'bzr serve' with no arguments or options should not traceback."""
155
out, err = self.run_bzr_error(
156
['bzr serve requires one of --inet or --port'], 'serve')
158
def test_bzr_connect_to_bzr_ssh(self):
159
"""User acceptance that get_transport of a bzr+ssh:// behaves correctly.
161
bzr+ssh:// should cause bzr to run a remote bzr smart server over SSH.
164
from bzrlib.transport.sftp import SFTPServer
165
except ParamikoNotPresent:
166
raise TestSkipped('Paramiko not installed')
167
from bzrlib.tests.stub_sftp import StubServer
244
self.make_branch('.')
246
process, url = self.start_server_port(['--allow-writes',
249
# Connect to the server
250
branch = Branch.open(url)
251
self.make_read_requests(branch)
252
self.assertServerFinishesCleanly(process)
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):
272
def test_serve_tcp(self):
273
"""'bzr serve' wraps the given --directory in a ChrootServer.
275
So requests that search up through the parent directories (like
276
find_repositoryV3) will give "not found" responses, rather than
277
InvalidURLJoin or jail break errors.
279
t = self.get_transport()
280
t.mkdir('server-root')
281
self.run_bzr_serve_then_func(
282
['--port', '127.0.0.1:0',
283
'--directory', t.local_abspath('server-root'),
285
func=self.when_server_started)
286
# The when_server_started method issued a find_repositoryV3 that should
287
# fail with 'norepository' because there are no repositories inside the
289
self.assertEqual(('norepository',), self.client_resp)
291
def when_server_started(self):
292
# Connect to the TCP server and issue some requests and see what comes
294
client_medium = medium.SmartTCPClientMedium(
295
'127.0.0.1', self.tcp_server.port,
296
'bzr://localhost:%d/' % (self.tcp_server.port,))
297
smart_client = client._SmartClient(client_medium)
298
resp = smart_client.call('mkdir', 'foo', '')
299
resp = smart_client.call('BzrDirFormat.initialize', 'foo/')
170
self.make_branch('a_branch')
172
# Start an SSH server
173
self.command_executed = []
174
# XXX: This is horrible -- we define a really dumb SSH server that
175
# executes commands, and manage the hooking up of stdin/out/err to the
176
# SSH channel ourselves. Surely this has already been implemented
178
class StubSSHServer(StubServer):
182
def check_channel_exec_request(self, channel, command):
183
self.test.command_executed.append(command)
184
proc = subprocess.Popen(
185
command, shell=True, stdin=subprocess.PIPE,
186
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
188
# XXX: horribly inefficient, not to mention ugly.
189
# Start a thread for each of stdin/out/err, and relay bytes from
190
# the subprocess to channel and vice versa.
191
def ferry_bytes(read, write, close):
200
(channel.recv, proc.stdin.write, proc.stdin.close),
201
(proc.stdout.read, channel.sendall, channel.close),
202
(proc.stderr.read, channel.sendall_stderr, channel.close)]
203
for read, write, close in file_functions:
204
t = threading.Thread(
205
target=ferry_bytes, args=(read, write, close))
210
ssh_server = SFTPServer(StubSSHServer)
211
# XXX: We *don't* want to override the default SSH vendor, so we set
212
# _vendor to what _get_ssh_vendor returns.
214
self.addCleanup(ssh_server.tearDown)
215
port = ssh_server._listener.port
217
# Access the branch via a bzr+ssh URL. The BZR_REMOTE_PATH environment
218
# variable is used to tell bzr what command to run on the remote end.
219
path_to_branch = os.path.abspath('a_branch')
221
orig_bzr_remote_path = os.environ.get('BZR_REMOTE_PATH')
222
os.environ['BZR_REMOTE_PATH'] = self.get_bzr_path()
301
resp = smart_client.call('BzrDir.find_repositoryV3', 'foo/')
302
except errors.ErrorFromSmartServer, e:
304
self.client_resp = resp
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))
224
branch = Branch.open(
225
'bzr+ssh://fred:secret@localhost:%d%s' % (port, path_to_branch))
227
branch.repository.get_revision_graph()
228
self.assertEqual(None, branch.last_revision())
229
# Check we can perform write operations
230
branch.bzrdir.root_transport.mkdir('foo')
232
# Restore the BZR_REMOTE_PATH environment variable back to its
234
if orig_bzr_remote_path is None:
235
del os.environ['BZR_REMOTE_PATH']
237
os.environ['BZR_REMOTE_PATH'] = orig_bzr_remote_path
240
['%s serve --inet --directory=/ --allow-writes'
241
% self.get_bzr_path()],
242
self.command_executed)