~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/blackbox/test_serve.py

Don't ignore bzrlib/plugins -- really bad idea because they can have bad 
old plugins lying around!

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 by Canonical Ltd
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
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
 
18
 
"""Tests of the bzr serve command."""
19
 
 
20
 
import os
21
 
import signal
22
 
import subprocess
23
 
import threading
24
 
 
25
 
from bzrlib import errors
26
 
from bzrlib.branch import Branch
27
 
from bzrlib.bzrdir import BzrDir
28
 
from bzrlib.errors import ParamikoNotPresent
29
 
from bzrlib.tests import TestCaseWithTransport, TestSkipped
30
 
from bzrlib.transport import get_transport, smart
31
 
 
32
 
 
33
 
class DoesNotCloseStdOutClient(smart.SmartStreamClient):
34
 
    """A client that doesn't close stdout upon disconnect().
35
 
    
36
 
    We wish to let stdout remain open so that we can see if the server writes
37
 
    anything to stdout during its shutdown.
38
 
    """
39
 
 
40
 
    def disconnect(self):
41
 
        if self._connected:
42
 
            self._connected = False
43
 
            # The client's out is the server's in.
44
 
            self._out.close()
45
 
 
46
 
 
47
 
class TestBzrServe(TestCaseWithTransport):
48
 
 
49
 
    def assertInetServerShutsdownCleanly(self, client, process):
50
 
        """Shutdown the server process looking for errors."""
51
 
        # Disconnect the client forcefully JUST IN CASE because of __del__'s use
52
 
        # in the smart module.
53
 
        client.disconnect()
54
 
 
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)
58
 
        # Hide stdin from the subprocess module, so it won't fail to close it.
59
 
        process.stdin = None
60
 
        result = self.finish_bzr_subprocess(process, retcode=0)
61
 
        self.assertEqual('', result[0])
62
 
        self.assertEqual('', result[1])
63
 
    
64
 
    def assertServerFinishesCleanly(self, process):
65
 
        """Shutdown the bzr serve instance process looking for errors."""
66
 
        # Shutdown the server
67
 
        result = self.finish_bzr_subprocess(process, retcode=3,
68
 
                                            send_signal=signal.SIGINT)
69
 
        self.assertEqual('', result[0])
70
 
        self.assertEqual('bzr: interrupted\n', result[1])
71
 
 
72
 
    def start_server_inet(self, extra_options=()):
73
 
        """Start a bzr server subprocess using the --inet option.
74
 
 
75
 
        :param extra_options: extra options to give the server.
76
 
        :return: a tuple with the bzr process handle for passing to
77
 
            finish_bzr_subprocess, a client for the server, and a transport.
78
 
        """
79
 
        # Serve from the current directory
80
 
        process = self.start_bzr_subprocess(['serve', '--inet'])
81
 
 
82
 
        # Connect to the server
83
 
        # We use this url because while this is no valid URL to connect to this
84
 
        # server instance, the transport needs a URL.
85
 
        client = DoesNotCloseStdOutClient(
86
 
            lambda: (process.stdout, process.stdin))
87
 
        transport = smart.SmartTransport('bzr://localhost/', client=client)
88
 
        return process, client, transport
89
 
 
90
 
    def start_server_port(self, extra_options=()):
91
 
        """Start a bzr server subprocess.
92
 
 
93
 
        :param extra_options: extra options to give the server.
94
 
        :return: a tuple with the bzr process handle for passing to
95
 
            finish_bzr_subprocess, and the base url for the server.
96
 
        """
97
 
        # Serve from the current directory
98
 
        args = ['serve', '--port', 'localhost:0']
99
 
        args.extend(extra_options)
100
 
        process = self.start_bzr_subprocess(args, skip_if_plan_to_signal=True)
101
 
        port_line = process.stdout.readline()
102
 
        prefix = 'listening on port: '
103
 
        self.assertStartsWith(port_line, prefix)
104
 
        port = int(port_line[len(prefix):])
105
 
        return process,'bzr://localhost:%d/' % port
106
 
 
107
 
    def test_bzr_serve_inet_readonly(self):
108
 
        """bzr server should provide a read only filesystem by default."""
109
 
        process, client, transport = self.start_server_inet()
110
 
        self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
111
 
        # finish with the transport
112
 
        del transport
113
 
        self.assertInetServerShutsdownCleanly(client, process)
114
 
 
115
 
    def test_bzr_serve_inet_readwrite(self):
116
 
        # Make a branch
117
 
        self.make_branch('.')
118
 
 
119
 
        process, client, transport = self.start_server_inet(['--allow-writes'])
120
 
 
121
 
        # We get a working branch
122
 
        branch = BzrDir.open_from_transport(transport).open_branch()
123
 
        branch.repository.get_revision_graph()
124
 
        self.assertEqual(None, branch.last_revision())
125
 
 
126
 
        # finish with the transport
127
 
        del transport
128
 
 
129
 
        self.assertInetServerShutsdownCleanly(client, process)
130
 
 
131
 
    def test_bzr_serve_port_readonly(self):
132
 
        """bzr server should provide a read only filesystem by default."""
133
 
        process, url = self.start_server_port()
134
 
        transport = get_transport(url)
135
 
        self.assertRaises(errors.TransportNotPossible, transport.mkdir, 'adir')
136
 
        self.assertServerFinishesCleanly(process)
137
 
 
138
 
    def test_bzr_serve_port_readwrite(self):
139
 
        # Make a branch
140
 
        self.make_branch('.')
141
 
 
142
 
        process, url = self.start_server_port(['--allow-writes'])
143
 
 
144
 
        # Connect to the server
145
 
        branch = Branch.open(url)
146
 
 
147
 
        # We get a working branch
148
 
        branch.repository.get_revision_graph()
149
 
        self.assertEqual(None, branch.last_revision())
150
 
 
151
 
        self.assertServerFinishesCleanly(process)
152
 
 
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')
157
 
 
158
 
    def test_bzr_connect_to_bzr_ssh(self):
159
 
        """User acceptance that get_transport of a bzr+ssh:// behaves correctly.
160
 
 
161
 
        bzr+ssh:// should cause bzr to run a remote bzr smart server over SSH.
162
 
        """
163
 
        try:
164
 
            from bzrlib.transport.sftp import SFTPServer
165
 
        except ParamikoNotPresent:
166
 
            raise TestSkipped('Paramiko not installed')
167
 
        from bzrlib.tests.stub_sftp import StubServer
168
 
        
169
 
        # Make a branch
170
 
        self.make_branch('a_branch')
171
 
 
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
177
 
        # elsewhere?
178
 
        class StubSSHServer(StubServer):
179
 
 
180
 
            test = self
181
 
 
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)
187
 
                
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):
192
 
                    while True:
193
 
                        bytes = read(1)
194
 
                        if bytes == '':
195
 
                            close()
196
 
                            break
197
 
                        write(bytes)
198
 
 
199
 
                file_functions = [
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))
206
 
                    t.start()
207
 
 
208
 
                return True
209
 
 
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.
213
 
        ssh_server.setUp()
214
 
        self.addCleanup(ssh_server.tearDown)
215
 
        port = ssh_server._listener.port
216
 
 
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')
220
 
        
221
 
        orig_bzr_remote_path = os.environ.get('BZR_REMOTE_PATH')
222
 
        os.environ['BZR_REMOTE_PATH'] = self.get_bzr_path()
223
 
        try:
224
 
            branch = Branch.open(
225
 
                'bzr+ssh://fred:secret@localhost:%d%s' % (port, path_to_branch))
226
 
            
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')
231
 
        finally:
232
 
            # Restore the BZR_REMOTE_PATH environment variable back to its
233
 
            # original state.
234
 
            if orig_bzr_remote_path is None:
235
 
                del os.environ['BZR_REMOTE_PATH']
236
 
            else:
237
 
                os.environ['BZR_REMOTE_PATH'] = orig_bzr_remote_path
238
 
 
239
 
        self.assertEqual(
240
 
            ['%s serve --inet --directory=/ --allow-writes'
241
 
             % self.get_bzr_path()],
242
 
            self.command_executed)
243