~bzr-pqm/bzr/bzr.dev

2052.3.2 by John Arbash Meinel
Change Copyright .. by Canonical to Copyright ... Canonical
1
# Copyright (C) 2006 Canonical Ltd
1910.19.7 by Andrew Bennetts
Allow specifying the host/interface to bzr serve, and use the new test
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1910.19.7 by Andrew Bennetts
Allow specifying the host/interface to bzr serve, and use the new test
16
17
18
"""Tests of the bzr serve command."""
19
2018.1.2 by Andrew Bennetts
Tidy imports, skip test if paramiko isn't installed.
20
import os
1910.19.7 by Andrew Bennetts
Allow specifying the host/interface to bzr serve, and use the new test
21
import signal
2018.1.2 by Andrew Bennetts
Tidy imports, skip test if paramiko isn't installed.
22
import subprocess
2309.2.5 by Alexander Belchenko
test_bzr_connect_to_bzr_ssh: win32-compatibility
23
import sys
4544.1.2 by Andrew Bennetts
Add test that would catch the lack of ChrootServer in cmd_serve.
24
import thread
2018.1.2 by Andrew Bennetts
Tidy imports, skip test if paramiko isn't installed.
25
import threading
1910.19.7 by Andrew Bennetts
Allow specifying the host/interface to bzr serve, and use the new test
26
2309.2.5 by Alexander Belchenko
test_bzr_connect_to_bzr_ssh: win32-compatibility
27
from bzrlib import (
28
    errors,
29
    osutils,
2598.5.1 by Aaron Bentley
Start eliminating the use of None to indicate null revision
30
    revision as _mod_revision,
4544.1.2 by Andrew Bennetts
Add test that would catch the lack of ChrootServer in cmd_serve.
31
    transport,
2309.2.5 by Alexander Belchenko
test_bzr_connect_to_bzr_ssh: win32-compatibility
32
    )
1910.19.7 by Andrew Bennetts
Allow specifying the host/interface to bzr serve, and use the new test
33
from bzrlib.branch import Branch
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
34
from bzrlib.bzrdir import BzrDir
2018.1.2 by Andrew Bennetts
Tidy imports, skip test if paramiko isn't installed.
35
from bzrlib.errors import ParamikoNotPresent
4544.1.2 by Andrew Bennetts
Add test that would catch the lack of ChrootServer in cmd_serve.
36
from bzrlib.smart import client, medium
37
from bzrlib.smart.server import SmartTCPServer
2018.1.2 by Andrew Bennetts
Tidy imports, skip test if paramiko isn't installed.
38
from bzrlib.tests import TestCaseWithTransport, TestSkipped
4544.1.2 by Andrew Bennetts
Add test that would catch the lack of ChrootServer in cmd_serve.
39
from bzrlib.trace import mutter
2018.5.21 by Andrew Bennetts
Move bzrlib.transport.smart to bzrlib.smart
40
from bzrlib.transport import get_transport, remote
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
41
42
1910.19.7 by Andrew Bennetts
Allow specifying the host/interface to bzr serve, and use the new test
43
class TestBzrServe(TestCaseWithTransport):
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
44
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
45
    def assertInetServerShutsdownCleanly(self, process):
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
46
        """Shutdown the server process looking for errors."""
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
47
        # Shutdown the server: the server should shut down when it cannot read
48
        # from stdin anymore.
49
        process.stdin.close()
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
50
        # Hide stdin from the subprocess module, so it won't fail to close it.
51
        process.stdin = None
2581.1.2 by Martin Pool
Remove unnecessary retcode=0 to run_bzr calls
52
        result = self.finish_bzr_subprocess(process)
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
53
        self.assertEqual('', result[0])
54
        self.assertEqual('', result[1])
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
55
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
56
    def assertServerFinishesCleanly(self, process):
57
        """Shutdown the bzr serve instance process looking for errors."""
58
        # Shutdown the server
59
        result = self.finish_bzr_subprocess(process, retcode=3,
60
                                            send_signal=signal.SIGINT)
61
        self.assertEqual('', result[0])
62
        self.assertEqual('bzr: interrupted\n', result[1])
63
3287.6.4 by Robert Collins
Fix up deprecation warnings for get_revision_graph.
64
    def make_read_requests(self, branch):
65
        """Do some read only requests."""
66
        branch.lock_read()
67
        try:
68
            branch.repository.all_revision_ids()
69
            self.assertEqual(_mod_revision.NULL_REVISION,
70
                             _mod_revision.ensure_null(branch.last_revision()))
71
        finally:
72
            branch.unlock()
73
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
74
    def start_server_inet(self, extra_options=()):
75
        """Start a bzr server subprocess using the --inet option.
76
77
        :param extra_options: extra options to give the server.
78
        :return: a tuple with the bzr process handle for passing to
79
            finish_bzr_subprocess, a client for the server, and a transport.
80
        """
81
        # Serve from the current directory
82
        process = self.start_bzr_subprocess(['serve', '--inet'])
83
84
        # Connect to the server
85
        # We use this url because while this is no valid URL to connect to this
86
        # server instance, the transport needs a URL.
3431.3.7 by Andrew Bennetts
Fix incidental breakage in blackbox/test_serve.py
87
        url = 'bzr://localhost/'
2018.5.15 by Andrew Bennetts
Tidy some imports, and bugs introduced when adding server.py
88
        client_medium = medium.SmartSimplePipesClientMedium(
3431.3.7 by Andrew Bennetts
Fix incidental breakage in blackbox/test_serve.py
89
            process.stdout, process.stdin, url)
90
        transport = remote.RemoteTransport(url, medium=client_medium)
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
91
        return process, transport
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
92
93
    def start_server_port(self, extra_options=()):
94
        """Start a bzr server subprocess.
95
96
        :param extra_options: extra options to give the server.
97
        :return: a tuple with the bzr process handle for passing to
98
            finish_bzr_subprocess, and the base url for the server.
99
        """
100
        # Serve from the current directory
101
        args = ['serve', '--port', 'localhost:0']
102
        args.extend(extra_options)
103
        process = self.start_bzr_subprocess(args, skip_if_plan_to_signal=True)
3955.1.7 by Jonathan Lange
Fix some tests that assumed the port was on stderr rather than stdout.
104
        port_line = process.stderr.readline()
1910.19.7 by Andrew Bennetts
Allow specifying the host/interface to bzr serve, and use the new test
105
        prefix = 'listening on port: '
106
        self.assertStartsWith(port_line, prefix)
107
        port = int(port_line[len(prefix):])
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
108
        return process,'bzr://localhost:%d/' % port
109
110
    def test_bzr_serve_inet_readonly(self):
111
        """bzr server should provide a read only filesystem by default."""
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
112
        process, transport = self.start_server_inet()
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
113
        self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
114
        self.assertInetServerShutsdownCleanly(process)
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
115
116
    def test_bzr_serve_inet_readwrite(self):
117
        # Make a branch
118
        self.make_branch('.')
119
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
120
        process, transport = self.start_server_inet(['--allow-writes'])
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
121
122
        # We get a working branch
123
        branch = BzrDir.open_from_transport(transport).open_branch()
3287.6.4 by Robert Collins
Fix up deprecation warnings for get_revision_graph.
124
        self.make_read_requests(branch)
2018.2.3 by Andrew Bennetts
Starting factoring out the smart server client "medium" from the protocol.
125
        self.assertInetServerShutsdownCleanly(process)
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
126
127
    def test_bzr_serve_port_readonly(self):
128
        """bzr server should provide a read only filesystem by default."""
129
        process, url = self.start_server_port()
130
        transport = get_transport(url)
131
        self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
132
        self.assertServerFinishesCleanly(process)
133
134
    def test_bzr_serve_port_readwrite(self):
135
        # Make a branch
136
        self.make_branch('.')
137
138
        process, url = self.start_server_port(['--allow-writes'])
1910.19.7 by Andrew Bennetts
Allow specifying the host/interface to bzr serve, and use the new test
139
140
        # Connect to the server
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
141
        branch = Branch.open(url)
3287.6.4 by Robert Collins
Fix up deprecation warnings for get_revision_graph.
142
        self.make_read_requests(branch)
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
143
        self.assertServerFinishesCleanly(process)
1910.19.7 by Andrew Bennetts
Allow specifying the host/interface to bzr serve, and use the new test
144
4370.4.2 by Jelmer Vernooij
Add --protocol option to 'bzr serve'.
145
    def test_bzr_serve_supports_protocol(self):
146
        # Make a branch
147
        self.make_branch('.')
148
149
        process, url = self.start_server_port(['--allow-writes',
150
                                               '--protocol=bzr'])
151
152
        # Connect to the server
153
        branch = Branch.open(url)
154
        self.make_read_requests(branch)
155
        self.assertServerFinishesCleanly(process)
156
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
157
    def test_bzr_connect_to_bzr_ssh(self):
2018.1.7 by Andrew Bennetts
Use bzrlib rather than a bzr subprocess in test_bzr_connect_to_bzr_ssh.
158
        """User acceptance that get_transport of a bzr+ssh:// behaves correctly.
159
160
        bzr+ssh:// should cause bzr to run a remote bzr smart server over SSH.
161
        """
2018.1.2 by Andrew Bennetts
Tidy imports, skip test if paramiko isn't installed.
162
        try:
163
            from bzrlib.transport.sftp import SFTPServer
164
        except ParamikoNotPresent:
165
            raise TestSkipped('Paramiko not installed')
166
        from bzrlib.tests.stub_sftp import StubServer
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
167
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
168
        # Make a branch
2018.1.2 by Andrew Bennetts
Tidy imports, skip test if paramiko isn't installed.
169
        self.make_branch('a_branch')
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
170
171
        # Start an SSH server
2018.1.11 by Andrew Bennetts
Improvements to test_bzr_connect_to_bzr_ssh based on review comments
172
        self.command_executed = []
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
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
176
        # elsewhere?
2018.1.2 by Andrew Bennetts
Tidy imports, skip test if paramiko isn't installed.
177
        class StubSSHServer(StubServer):
178
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
179
            test = self
180
181
            def check_channel_exec_request(self, channel, command):
2018.1.11 by Andrew Bennetts
Improvements to test_bzr_connect_to_bzr_ssh based on review comments
182
                self.test.command_executed.append(command)
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
183
                proc = subprocess.Popen(
184
                    command, shell=True, stdin=subprocess.PIPE,
185
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
186
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
187
                # XXX: horribly inefficient, not to mention ugly.
2018.1.11 by Andrew Bennetts
Improvements to test_bzr_connect_to_bzr_ssh based on review comments
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):
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
191
                    while True:
2018.1.11 by Andrew Bennetts
Improvements to test_bzr_connect_to_bzr_ssh based on review comments
192
                        bytes = read(1)
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
193
                        if bytes == '':
2018.1.11 by Andrew Bennetts
Improvements to test_bzr_connect_to_bzr_ssh based on review comments
194
                            close()
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
195
                            break
2018.1.11 by Andrew Bennetts
Improvements to test_bzr_connect_to_bzr_ssh based on review comments
196
                        write(bytes)
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
197
2018.1.11 by Andrew Bennetts
Improvements to test_bzr_connect_to_bzr_ssh based on review comments
198
                file_functions = [
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))
205
                    t.start()
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
206
207
                return True
208
2018.1.2 by Andrew Bennetts
Tidy imports, skip test if paramiko isn't installed.
209
        ssh_server = SFTPServer(StubSSHServer)
2018.1.7 by Andrew Bennetts
Use bzrlib rather than a bzr subprocess in test_bzr_connect_to_bzr_ssh.
210
        # XXX: We *don't* want to override the default SSH vendor, so we set
211
        # _vendor to what _get_ssh_vendor returns.
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
212
        ssh_server.setUp()
213
        self.addCleanup(ssh_server.tearDown)
214
        port = ssh_server._listener.port
215
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.
2309.2.5 by Alexander Belchenko
test_bzr_connect_to_bzr_ssh: win32-compatibility
218
        path_to_branch = osutils.abspath('a_branch')
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
219
2018.1.7 by Andrew Bennetts
Use bzrlib rather than a bzr subprocess in test_bzr_connect_to_bzr_ssh.
220
        orig_bzr_remote_path = os.environ.get('BZR_REMOTE_PATH')
2309.2.5 by Alexander Belchenko
test_bzr_connect_to_bzr_ssh: win32-compatibility
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
2018.1.7 by Andrew Bennetts
Use bzrlib rather than a bzr subprocess in test_bzr_connect_to_bzr_ssh.
225
        try:
2309.2.5 by Alexander Belchenko
test_bzr_connect_to_bzr_ssh: win32-compatibility
226
            if sys.platform == 'win32':
227
                path_to_branch = os.path.splitdrive(path_to_branch)[1]
2018.1.7 by Andrew Bennetts
Use bzrlib rather than a bzr subprocess in test_bzr_connect_to_bzr_ssh.
228
            branch = Branch.open(
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
229
                'bzr+ssh://fred:secret@localhost:%d%s' % (port, path_to_branch))
3287.6.4 by Robert Collins
Fix up deprecation warnings for get_revision_graph.
230
            self.make_read_requests(branch)
2018.1.10 by Andrew Bennetts
Merge --allow-writes from Robert
231
            # Check we can perform write operations
232
            branch.bzrdir.root_transport.mkdir('foo')
2018.1.7 by Andrew Bennetts
Use bzrlib rather than a bzr subprocess in test_bzr_connect_to_bzr_ssh.
233
        finally:
234
            # Restore the BZR_REMOTE_PATH environment variable back to its
235
            # original state.
236
            if orig_bzr_remote_path is None:
237
                del os.environ['BZR_REMOTE_PATH']
238
            else:
239
                os.environ['BZR_REMOTE_PATH'] = orig_bzr_remote_path
240
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
241
        self.assertEqual(
2018.1.11 by Andrew Bennetts
Improvements to test_bzr_connect_to_bzr_ssh based on review comments
242
            ['%s serve --inet --directory=/ --allow-writes'
2309.2.5 by Alexander Belchenko
test_bzr_connect_to_bzr_ssh: win32-compatibility
243
             % bzr_remote_path],
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
244
            self.command_executed)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
245
4544.1.2 by Andrew Bennetts
Add test that would catch the lack of ChrootServer in cmd_serve.
246
247
class TestCmdServeChrooting(TestCaseWithTransport):
248
249
    def test_serve_tcp(self):
250
        """'bzr serve' wraps the given --directory in a ChrootServer.
251
252
        So requests that search up through the parent directories (like
253
        find_repositoryV3) will give "not found" responses, rather than
254
        InvalidURLJoin or jail break errors.
255
        """
256
        t = self.get_transport()
257
        t.mkdir('server-root')
258
        self.run_bzr_serve_then_func(
259
            ['--port', '0', '--directory', t.local_abspath('server-root'),
260
             '--allow-writes'],
261
            self.when_server_started)
262
        # The when_server_started method issued a find_repositoryV3 that should
263
        # fail with 'norepository' because there are no repositories inside the
264
        # --directory.
265
        self.assertEqual(('norepository',), self.client_resp)
266
        
267
    def run_bzr_serve_then_func(self, serve_args, func, *func_args,
268
            **func_kwargs):
269
        """Run 'bzr serve', and run the given func in a thread once the server
270
        has started.
271
        
272
        When 'func' terminates, the server will be terminated too.
273
        """
274
        # install hook
4544.1.3 by Andrew Bennetts
Pass backing_urls to the new server_started_ex hook.
275
        def on_server_start(backing_urls, tcp_server):
276
            t = threading.Thread(
277
                target=on_server_start_thread, args=(tcp_server,))
4544.1.2 by Andrew Bennetts
Add test that would catch the lack of ChrootServer in cmd_serve.
278
            t.start()
279
        def on_server_start_thread(tcp_server):
280
            try:
281
                # Run func
282
                self.tcp_server = tcp_server
283
                try:
284
                    func(*func_args, **func_kwargs)
285
                except Exception, e:
286
                    # Log errors to make some test failures a little less
287
                    # mysterious.
288
                    mutter('func broke: %r', e)
289
            finally:
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')
296
        # start a TCP server
297
        try:
298
            self.run_bzr(['serve'] + list(serve_args))
299
        except KeyboardInterrupt:
300
            pass
301
302
    def when_server_started(self):
303
        # Connect to the TCP server and issue some requests and see what comes
304
        # back.
305
        client_medium = medium.SmartTCPClientMedium(
306
            '127.0.0.1', self.tcp_server.port,
307
            'bzr://localhost:%d/' % (self.tcp_server.port,))
308
        smart_client = client._SmartClient(client_medium)
309
        resp = smart_client.call('mkdir', 'foo', '')
310
        resp = smart_client.call('BzrDirFormat.initialize', 'foo/')
311
        try:
312
            resp = smart_client.call('BzrDir.find_repositoryV3', 'foo/')
313
        except errors.ErrorFromSmartServer, e:
314
            resp = e.error_tuple
315
        self.client_resp = resp
316
        client_medium.disconnect()
317
318
319