1
# Copyright (C) 2006 Canonical Ltd
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.
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.
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
17
"""Tests for smart transport"""
19
# all of this deals with byte strings so this is safe
20
from cStringIO import StringIO
25
from bzrlib import tests, errors, bzrdir
26
from bzrlib.transport import local, memory, smart, get_transport
28
## class SmartURLTests(tests.TestCase):
29
## """Tests for handling of URLs and detection of smart servers"""
31
## def test_bzr_url_is_smart(self):
34
class SmartClientTests(tests.TestCase):
36
def test_construct_smart_stream_client(self):
37
# make a new client; this really wants two fifos or sockets
38
# but the constructor should not do any IO
39
client = smart.SmartStreamClient(None, None)
42
class TCPClientTests(tests.TestCaseWithTransport):
45
super(TCPClientTests, self).setUp()
46
# We're allowed to set the transport class here, so that we don't use
47
# the default or a parameterized class, but rather use the
48
# TestCaseWithTransport infrastructure to set up a smart server and
50
self.transport_server = smart.SmartTCPServer_for_testing
52
def test_plausible_url(self):
53
self.assert_(self.get_url().startswith('bzr://'))
55
def test_probe_transport(self):
56
t = self.get_transport()
57
self.assertIsInstance(t, smart.SmartTransport)
59
def test_get_client_from_transport(self):
60
t = self.get_transport()
61
client = t.get_smart_client()
62
self.assertIsInstance(client, smart.SmartStreamClient)
65
class BasicSmartTests(tests.TestCase):
67
def test_smart_query_version(self):
68
"""Feed a canned query version to a server"""
69
to_server = StringIO('hello\n')
70
from_server = StringIO()
71
server = smart.SmartStreamServer(to_server, from_server, local.LocalTransport('file:///'))
72
server._serve_one_request()
73
self.assertEqual('ok\0011\n',
74
from_server.getvalue())
76
def test_canned_get_response(self):
77
transport = memory.MemoryTransport('memory:///')
78
transport.put('testfile', StringIO('contents\nof\nfile\n'))
79
to_server = StringIO('get\001./testfile\n')
80
from_server = StringIO()
81
server = smart.SmartStreamServer(to_server, from_server, transport)
82
server._serve_one_request()
83
self.assertEqual('ok\n'
85
'contents\nof\nfile\n'
87
from_server.getvalue())
89
def test_get_error_unexpected(self):
90
"""Error reported by server with no specific representation"""
91
class FlakyTransport(object):
93
raise Exception("some random exception from inside server")
94
server = smart.SmartTCPServer(backing_transport=FlakyTransport())
95
server.start_background_thread()
97
transport = smart.SmartTCPTransport(server.get_url())
99
transport.get('something')
100
except errors.TransportError, e:
101
self.assertContainsRe(str(e), 'some random exception')
103
self.fail("get did not raise expected error")
105
server.stop_background_thread()
107
def test_server_subprocess(self):
108
"""Talk to a server started as a subprocess
110
This is similar to running it over ssh, except that it runs in the same machine
111
without ssh intermediating.
113
args = [sys.executable, sys.argv[0], 'serve', '--inet']
114
child = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
116
conn = smart.SmartStreamClient(to_server=child.stdin, from_server=child.stdout)
120
returncode = child.wait()
121
self.assertEquals(0, returncode)
124
class SmartTCPTests(tests.TestCase):
125
"""Tests for connection to TCP server.
127
All of these tests are run with a server running on another thread serving
128
a MemoryTransport, and a connection to it already open.
132
super(SmartTCPTests, self).setUp()
133
self.backing_transport = memory.MemoryTransport()
134
self.server = smart.SmartTCPServer(self.backing_transport)
135
self.server.start_background_thread()
136
self.transport = smart.SmartTCPTransport(self.server.get_url())
139
if getattr(self, 'transport', None):
140
self.transport.disconnect()
141
if getattr(self, 'server', None):
142
self.server.stop_background_thread()
143
super(SmartTCPTests, self).tearDown()
145
def test_start_tcp_server(self):
146
url = self.server.get_url()
147
self.assertContainsRe(url, r'^bzr://127\.0\.0\.1:[0-9]{2,}/')
149
def test_smart_transport_has(self):
150
"""Checking for file existence over smart."""
151
self.backing_transport.put("foo", StringIO("contents of foo\n"))
152
self.assertTrue(self.transport.has("foo"))
153
self.assertFalse(self.transport.has("non-foo"))
155
def test_smart_transport_get(self):
156
"""Read back a file over smart."""
157
self.backing_transport.put("foo", StringIO("contents\nof\nfoo\n"))
158
fp = self.transport.get("foo")
159
self.assertEqual('contents\nof\nfoo\n', fp.read())
161
def test_get_error_enoent(self):
162
"""Error reported from server getting nonexistent file."""
164
self.transport.get('not a file')
165
except errors.NoSuchFile, e:
166
self.assertEqual('/not a file', e.path)
168
self.fail("get did not raise expected error")
170
def test_simple_clone_conn(self):
171
"""Test that cloning reuses the same connection."""
172
# we create a real connection not a loopback one, but it will use the
173
# same server and pipes
174
conn2 = smart.SmartTransport(self.transport.base, clone_from=self.transport)
176
def test_remote_path(self):
177
self.assertEquals('/foo/bar',
178
self.transport._remote_path('foo/bar'))
180
def test_clone_changes_base(self):
181
"""Cloning transport produces one with a new base location"""
182
conn2 = self.transport.clone('subdir')
183
self.assertEquals(self.transport.base + 'subdir/',
186
def test_open_dir(self):
187
"""Test changing directory"""
188
transport = self.transport
189
self.backing_transport.mkdir('toffee')
190
self.backing_transport.mkdir('toffee/apple')
191
self.assertEquals('/toffee', transport._remote_path('toffee'))
192
self.assertTrue(transport.has('toffee'))
193
sub_conn = transport.clone('toffee')
194
self.assertTrue(sub_conn.has('apple'))
196
def test_open_bzrdir(self):
197
"""Open an existing bzrdir over smart transport"""
198
transport = self.transport
199
t = self.backing_transport
200
bzrdir.BzrDirFormat.get_default_format().initialize_on_transport(t)
201
result_dir = bzrdir.BzrDir.open_containing_from_transport(transport)
204
class SmartServerTests(tests.TestCaseWithTransport):
205
"""Test that call directly into the server logic, bypassing the network."""
207
def test_hello(self):
208
server = smart.SmartServer(None)
209
response = server.dispatch_command('hello', ())
210
self.assertEqual(('ok', '1'), response.args)
211
self.assertEqual(None, response.body)
213
def test_get_bundle(self):
214
from bzrlib.bundle import serializer
215
wt = self.make_branch_and_tree('.')
217
file('hello', 'w').write('hello world')
219
wt.commit(message='add hello', rev_id='rev-1')
221
server = smart.SmartServer(self.get_transport())
222
response = server.dispatch_command('get_bundle', ('.', 'rev-1'))
223
self.assert_(response.body.startswith('# Bazaar revision bundle '),
224
"doesn't look like a bundle: %r" % response.body)
225
bundle = serializer.read_bundle(StringIO(response.body))
227
# TODO: Client feature that does get_bundle and then installs that into a
228
# branch; this can be used in place of the regular pull/fetch operation when
229
# coming from a smart server.
231
# TODO: Eventually, want to do a 'branch' command by fetching the whole
232
# history as one big bundle. How?
234
# The branch command does 'br_from.sprout', which tries to preserve the same
235
# format. We don't necessarily even want that.
237
# It might be simpler to handle cmd_pull first, which does a simpler fetch()
238
# operation from one branch into another. It already has some code for
239
# pulling from a bundle, which it does by trying to see if the destination is
240
# a bundle file. So it seems the logic for pull ought to be:
242
# - if it's a smart server, get a bundle from there and install that
243
# - if it's a bundle, install that
244
# - if it's a branch, pull from there
246
# Getting a bundle from a smart server is a bit different from reading a
249
# - we can reasonably remember the URL we last read from
250
# - you can specify a revision number to pull, and we need to pass it across
251
# to the server as a limit on what will be requested
253
# TODO: Given a URL, determine whether it is a smart server or not (or perhaps
254
# otherwise whether it's a bundle?) Should this be a property or method of
255
# the transport? For the ssh protocol, we always know it's a smart server.
256
# For http, we potentially need to probe. But if we're explicitly given
257
# bzr+http:// then we can skip that for now.