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."""
33
revision as _mod_revision,
36
22
from bzrlib.branch import Branch
37
23
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 (
41
TestCaseWithMemoryTransport,
42
TestCaseWithTransport,
45
from bzrlib.trace import mutter
46
from bzrlib.transport import get_transport, 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.
61
def on_server_start(backing_urls, tcp_server):
63
target=on_server_start_thread, args=(tcp_server,))
65
def on_server_start_thread(tcp_server):
68
self.tcp_server = tcp_server
71
func(*func_args, **func_kwargs)
73
# Log errors to make some test failures a little less
75
mutter('func broke: %r', e)
77
# Then stop the server
78
mutter('interrupting...')
79
thread.interrupt_main()
80
SmartTCPServer.hooks.install_named_hook(
81
'server_started_ex', on_server_start,
82
'run_bzr_serve_then_func hook')
85
out, err = self.run_bzr(['serve'] + list(serve_args))
86
except KeyboardInterrupt, e:
91
class TestBzrServe(TestBzrServeBase):
94
super(TestBzrServe, self).setUp()
95
self.disable_missing_extensions_warning()
97
def assertInetServerShutsdownCleanly(self, process):
98
"""Shutdown the server process looking for errors."""
99
# Shutdown the server: the server should shut down when it cannot read
100
# from stdin anymore.
101
process.stdin.close()
24
from bzrlib.tests import TestCaseWithTransport
25
from bzrlib.transport import smart
28
class DoesNotCloseStdOutClient(smart.SmartStreamClient):
29
"""A client that doesn't close stdout upon disconnect().
31
We wish to let stdout remain open so that we can see if the server writes
32
anything to stdout during its shutdown.
37
self._connected = False
38
# The client's out is the server's in.
42
class TestBzrServe(TestCaseWithTransport):
44
def test_bzr_serve_inet(self):
48
# Serve that branch from the current directory
49
process = self.start_bzr_subprocess(['serve', '--inet'])
51
# Connect to the server
52
# We use this url because while this is no valid URL to connect to this
53
# server instance, the transport needs a URL.
54
client = DoesNotCloseStdOutClient(
55
lambda: (process.stdout, process.stdin))
56
transport = smart.SmartTransport('bzr://localhost/', client=client)
58
# We get a working branch
59
branch = BzrDir.open_from_transport(transport).open_branch()
60
branch.repository.get_revision_graph()
61
self.assertEqual(None, branch.last_revision())
63
# finish with the transport
65
# Disconnect the client forcefully JUST IN CASE because of __del__'s use
66
# in the smart module.
69
# Shutdown the server: the client should have disconnected cleanly and
70
# closed stdin, so the server process should shut itself down.
71
self.assertTrue(process.stdin.closed)
102
72
# Hide stdin from the subprocess module, so it won't fail to close it.
103
73
process.stdin = None
104
result = self.finish_bzr_subprocess(process)
74
result = self.finish_bzr_subprocess(process, retcode=0)
105
75
self.assertEqual('', result[0])
106
76
self.assertEqual('', result[1])
108
def assertServerFinishesCleanly(self, process):
109
"""Shutdown the bzr serve instance process looking for errors."""
78
def test_bzr_serve_port(self):
82
# Serve that branch from the current directory
83
process = self.start_bzr_subprocess(['serve', '--port', 'localhost:0'],
84
skip_if_plan_to_signal=True)
85
port_line = process.stdout.readline()
86
prefix = 'listening on port: '
87
self.assertStartsWith(port_line, prefix)
88
port = int(port_line[len(prefix):])
90
# Connect to the server
91
branch = Branch.open('bzr://localhost:%d/' % port)
93
# We get a working branch
94
branch.repository.get_revision_graph()
95
self.assertEqual(None, branch.last_revision())
110
97
# Shutdown the server
111
98
result = self.finish_bzr_subprocess(process, retcode=3,
112
99
send_signal=signal.SIGINT)
113
100
self.assertEqual('', result[0])
114
101
self.assertEqual('bzr: interrupted\n', result[1])
116
def make_read_requests(self, branch):
117
"""Do some read only requests."""
120
branch.repository.all_revision_ids()
121
self.assertEqual(_mod_revision.NULL_REVISION,
122
_mod_revision.ensure_null(branch.last_revision()))
126
def start_server_inet(self, extra_options=()):
127
"""Start a bzr server subprocess using the --inet option.
129
:param extra_options: extra options to give the server.
130
:return: a tuple with the bzr process handle for passing to
131
finish_bzr_subprocess, a client for the server, and a transport.
133
# Serve from the current directory
134
args = ['serve', '--inet']
135
args.extend(extra_options)
136
process = self.start_bzr_subprocess(args)
138
# Connect to the server
139
# We use this url because while this is no valid URL to connect to this
140
# server instance, the transport needs a URL.
141
url = 'bzr://localhost/'
143
client_medium = medium.SmartSimplePipesClientMedium(
144
process.stdout, process.stdin, url)
145
transport = remote.RemoteTransport(url, medium=client_medium)
146
return process, transport
148
def start_server_port(self, extra_options=()):
149
"""Start a bzr server subprocess.
151
:param extra_options: extra options to give the server.
152
:return: a tuple with the bzr process handle for passing to
153
finish_bzr_subprocess, and the base url for the server.
155
# Serve from the current directory
156
args = ['serve', '--port', 'localhost:0']
157
args.extend(extra_options)
158
process = self.start_bzr_subprocess(args, skip_if_plan_to_signal=True)
159
port_line = process.stderr.readline()
160
prefix = 'listening on port: '
161
self.assertStartsWith(port_line, prefix)
162
port = int(port_line[len(prefix):])
163
url = 'bzr://localhost:%d/' % port
167
def test_bzr_serve_quiet(self):
168
self.make_branch('.')
169
args = ['--port', 'localhost:0', '--quiet']
170
out, err = self.run_bzr_serve_then_func(args, retcode=3)
171
self.assertEqual('', out)
172
self.assertEqual('', err)
174
def test_bzr_serve_inet_readonly(self):
175
"""bzr server should provide a read only filesystem by default."""
176
process, transport = self.start_server_inet()
177
self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
178
self.assertInetServerShutsdownCleanly(process)
180
def test_bzr_serve_inet_readwrite(self):
182
self.make_branch('.')
184
process, transport = self.start_server_inet(['--allow-writes'])
186
# We get a working branch, and can create a directory
187
branch = BzrDir.open_from_transport(transport).open_branch()
188
self.make_read_requests(branch)
189
transport.mkdir('adir')
190
self.assertInetServerShutsdownCleanly(process)
192
def test_bzr_serve_port_readonly(self):
193
"""bzr server should provide a read only filesystem by default."""
194
process, url = self.start_server_port()
195
transport = get_transport(url)
196
self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
197
self.assertServerFinishesCleanly(process)
199
def test_bzr_serve_port_readwrite(self):
201
self.make_branch('.')
203
process, url = self.start_server_port(['--allow-writes'])
205
# Connect to the server
206
branch = Branch.open(url)
207
self.make_read_requests(branch)
208
self.assertServerFinishesCleanly(process)
210
def test_bzr_serve_supports_protocol(self):
212
self.make_branch('.')
214
process, url = self.start_server_port(['--allow-writes',
217
# Connect to the server
218
branch = Branch.open(url)
219
self.make_read_requests(branch)
220
self.assertServerFinishesCleanly(process)
222
def test_bzr_serve_dhpss(self):
223
# This is a smoke test that the server doesn't crash when run with
224
# -Dhpss, and does drop some hpss logging to the file.
225
self.make_branch('.')
226
log_fname = os.getcwd() + '/server.log'
227
self._captureVar('BZR_LOG', log_fname)
228
process, transport = self.start_server_inet(['-Dhpss'])
229
branch = BzrDir.open_from_transport(transport).open_branch()
230
self.make_read_requests(branch)
231
self.assertInetServerShutsdownCleanly(process)
232
f = open(log_fname, 'rb')
235
self.assertContainsRe(content, r'hpss request: \[[0-9-]+\]')
238
class TestCmdServeChrooting(TestBzrServeBase):
240
def test_serve_tcp(self):
241
"""'bzr serve' wraps the given --directory in a ChrootServer.
243
So requests that search up through the parent directories (like
244
find_repositoryV3) will give "not found" responses, rather than
245
InvalidURLJoin or jail break errors.
247
t = self.get_transport()
248
t.mkdir('server-root')
249
self.run_bzr_serve_then_func(
250
['--port', '127.0.0.1:0',
251
'--directory', t.local_abspath('server-root'),
253
func=self.when_server_started)
254
# The when_server_started method issued a find_repositoryV3 that should
255
# fail with 'norepository' because there are no repositories inside the
257
self.assertEqual(('norepository',), self.client_resp)
259
def when_server_started(self):
260
# Connect to the TCP server and issue some requests and see what comes
262
client_medium = medium.SmartTCPClientMedium(
263
'127.0.0.1', self.tcp_server.port,
264
'bzr://localhost:%d/' % (self.tcp_server.port,))
265
smart_client = client._SmartClient(client_medium)
266
resp = smart_client.call('mkdir', 'foo', '')
267
resp = smart_client.call('BzrDirFormat.initialize', 'foo/')
269
resp = smart_client.call('BzrDir.find_repositoryV3', 'foo/')
270
except errors.ErrorFromSmartServer, e:
272
self.client_resp = resp
273
client_medium.disconnect()
276
class TestUserdirExpansion(TestCaseWithMemoryTransport):
278
def fake_expanduser(self, path):
279
"""A simple, environment-independent, function for the duration of this
282
Paths starting with a path segment of '~user' will expand to start with
283
'/home/user/'. Every other path will be unchanged.
285
if path.split('/', 1)[0] == '~user':
286
return '/home/user' + path[len('~user'):]
289
def make_test_server(self, base_path='/'):
290
"""Make and start a BzrServerFactory, backed by a memory transport, and
291
creat '/home/user' in that transport.
293
bzr_server = BzrServerFactory(
294
self.fake_expanduser, lambda t: base_path)
295
mem_transport = self.get_transport()
296
mem_transport.mkdir_multi(['home', 'home/user'])
297
bzr_server.set_up(mem_transport, None, None, inet=True)
298
self.addCleanup(bzr_server.tear_down)
301
def test_bzr_serve_expands_userdir(self):
302
bzr_server = self.make_test_server()
303
self.assertTrue(bzr_server.smart_server.backing_transport.has('~user'))
305
def test_bzr_serve_does_not_expand_userdir_outside_base(self):
306
bzr_server = self.make_test_server('/foo')
307
self.assertFalse(bzr_server.smart_server.backing_transport.has('~user'))
309
def test_get_base_path(self):
310
"""cmd_serve will turn the --directory option into a LocalTransport
311
(optionally decorated with 'readonly+'). BzrServerFactory can
312
determine the original --directory from that transport.
314
# URLs always include the trailing slash, and get_base_path returns it
315
base_dir = osutils.abspath('/a/b/c') + '/'
316
base_url = urlutils.local_path_to_url(base_dir) + '/'
317
# Define a fake 'protocol' to capture the transport that cmd_serve
318
# passes to serve_bzr.
319
def capture_transport(transport, host, port, inet):
320
self.bzr_serve_transport = transport
321
cmd = builtins.cmd_serve()
323
cmd.run(directory=base_dir, protocol=capture_transport)
324
server_maker = BzrServerFactory()
326
'readonly+%s' % base_url, self.bzr_serve_transport.base)
328
base_dir, server_maker.get_base_path(self.bzr_serve_transport))
330
cmd.run(directory=base_dir, protocol=capture_transport,
332
server_maker = BzrServerFactory()
333
self.assertEqual(base_url, self.bzr_serve_transport.base)
334
self.assertEqual(base_dir,
335
server_maker.get_base_path(self.bzr_serve_transport))
103
def test_bzr_serve_no_args(self):
104
"""'bzr serve' with no arguments or options should not traceback."""
105
out, err = self.run_bzr_error(
106
['bzr serve requires one of --inet or --port'], 'serve')