20
20
TransportTestProviderAdapter.
24
25
from cStringIO import StringIO
25
26
from StringIO import StringIO as pyStringIO
29
31
from bzrlib import (
34
from bzrlib.errors import (DirectoryNotEmpty, NoSuchFile, FileExists,
35
LockError, NoSmartServer, PathError,
36
TransportNotPossible, ConnectionError,
37
from bzrlib.errors import (ConnectionError,
38
47
from bzrlib.osutils import getcwd
39
48
from bzrlib.smart import medium
40
from bzrlib.symbol_versioning import zero_eleven
41
from bzrlib.tests import TestCaseInTempDir, TestSkipped
49
from bzrlib.tests import (
42
55
from bzrlib.tests.test_transport import TestTransportImplementation
43
from bzrlib.transport import memory, remote
44
import bzrlib.transport
56
from bzrlib.transport import (
59
_get_transport_modules,
61
from bzrlib.transport.memory import MemoryTransport
64
def get_transport_test_permutations(module):
65
"""Get the permutations module wants to have tested."""
66
if getattr(module, 'get_test_permutations', None) is None:
68
"transport module %s doesn't provide get_test_permutations()"
71
return module.get_test_permutations()
74
def transport_test_permutations():
75
"""Return a list of the klass, server_factory pairs to test."""
77
for module in _get_transport_modules():
79
permutations = get_transport_test_permutations(
80
reduce(getattr, (module).split('.')[1:], __import__(module)))
81
for (klass, server_factory) in permutations:
82
scenario = ('%s,%s' % (klass.__name__, server_factory.__name__),
83
{"transport_class":klass,
84
"transport_server":server_factory})
85
result.append(scenario)
86
except errors.DependencyNotPresent, e:
87
# Continue even if a dependency prevents us
88
# from adding this test
93
def load_tests(standard_tests, module, loader):
94
"""Multiply tests for tranport implementations."""
95
result = loader.suiteClass()
96
scenarios = transport_test_permutations()
97
return multiply_tests(standard_tests, scenarios, result)
47
100
class TransportTests(TestTransportImplementation):
62
155
self.assertEqual(True, t.has('a'))
63
156
self.assertEqual(False, t.has('c'))
64
157
self.assertEqual(True, t.has(urlutils.escape('%')))
65
self.assertEqual(list(t.has_multi(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])),
66
[True, True, False, False, True, False, True, False])
158
self.assertEqual(list(t.has_multi(['a', 'b', 'c', 'd',
159
'e', 'f', 'g', 'h'])),
160
[True, True, False, False,
161
True, False, True, False])
67
162
self.assertEqual(True, t.has_any(['a', 'b', 'c']))
68
self.assertEqual(False, t.has_any(['c', 'd', 'f', urlutils.escape('%%')]))
69
self.assertEqual(list(t.has_multi(iter(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']))),
70
[True, True, False, False, True, False, True, False])
163
self.assertEqual(False, t.has_any(['c', 'd', 'f',
164
urlutils.escape('%%')]))
165
self.assertEqual(list(t.has_multi(iter(['a', 'b', 'c', 'd',
166
'e', 'f', 'g', 'h']))),
167
[True, True, False, False,
168
True, False, True, False])
71
169
self.assertEqual(False, t.has_any(['c', 'c', 'c']))
72
170
self.assertEqual(True, t.has_any(['b', 'b', 'b']))
74
172
def test_has_root_works(self):
173
from bzrlib.smart import server
174
if self.transport_server is server.SmartTCPServer_for_testing:
175
raise TestNotApplicable(
176
"SmartTCPServer_for_testing intentionally does not allow "
75
178
current_transport = self.get_transport()
76
179
self.assertTrue(current_transport.has('/'))
77
180
root = current_transport.clone('/')
89
192
self.build_tree(files, transport=t, line_endings='binary')
90
193
self.check_transport_contents('contents of a\n', t, 'a')
91
194
content_f = t.get_multi(files)
92
for content, f in zip(contents, content_f):
195
# Use itertools.izip() instead of use zip() or map(), since they fully
196
# evaluate their inputs, the transport requests should be issued and
197
# handled sequentially (we don't want to force transport to buffer).
198
for content, f in itertools.izip(contents, content_f):
93
199
self.assertEqual(content, f.read())
95
201
content_f = t.get_multi(iter(files))
96
for content, f in zip(contents, content_f):
202
# Use itertools.izip() for the same reason
203
for content, f in itertools.izip(contents, content_f):
97
204
self.assertEqual(content, f.read())
206
def test_get_unknown_file(self):
207
t = self.get_transport()
209
contents = ['contents of a\n',
212
self.build_tree(files, transport=t, line_endings='binary')
99
213
self.assertRaises(NoSuchFile, t.get, 'c')
100
214
self.assertListRaises(NoSuchFile, t.get_multi, ['a', 'b', 'c'])
101
215
self.assertListRaises(NoSuchFile, t.get_multi, iter(['a', 'b', 'c']))
217
def test_get_directory_read_gives_ReadError(self):
218
"""consistent errors for read() on a file returned by get()."""
219
t = self.get_transport()
221
self.build_tree(['a directory/'])
223
t.mkdir('a%20directory')
224
# getting the file must either work or fail with a PathError
226
a_file = t.get('a%20directory')
227
except (errors.PathError, errors.RedirectRequested):
228
# early failure return immediately.
230
# having got a file, read() must either work (i.e. http reading a dir
231
# listing) or fail with ReadError
234
except errors.ReadError:
103
237
def test_get_bytes(self):
104
238
t = self.get_transport()
115
249
for content, fname in zip(contents, files):
116
250
self.assertEqual(content, t.get_bytes(fname))
252
def test_get_bytes_unknown_file(self):
253
t = self.get_transport()
118
255
self.assertRaises(NoSuchFile, t.get_bytes, 'c')
121
t = self.get_transport()
126
self.applyDeprecated(zero_eleven, t.put, 'a', 'string\ncontents\n')
127
self.check_transport_contents('string\ncontents\n', t, 'a')
129
self.applyDeprecated(zero_eleven,
130
t.put, 'b', StringIO('file-like\ncontents\n'))
131
self.check_transport_contents('file-like\ncontents\n', t, 'b')
133
self.assertRaises(NoSuchFile,
134
self.applyDeprecated,
136
t.put, 'path/doesnt/exist/c', StringIO('contents'))
257
def test_get_with_open_write_stream_sees_all_content(self):
258
t = self.get_transport()
261
handle = t.open_write_stream('foo')
264
self.assertEqual('b', t.get('foo').read())
268
def test_get_bytes_with_open_write_stream_sees_all_content(self):
269
t = self.get_transport()
272
handle = t.open_write_stream('foo')
275
self.assertEqual('b', t.get_bytes('foo'))
276
self.assertEqual('b', t.get('foo').read())
138
280
def test_put_bytes(self):
139
281
t = self.get_transport()
257
399
t.put_file, 'a', StringIO('some text for a\n'))
260
t.put_file('a', StringIO('some text for a\n'))
402
result = t.put_file('a', StringIO('some text for a\n'))
403
# put_file returns the length of the data written
404
self.assertEqual(16, result)
261
405
self.failUnless(t.has('a'))
262
406
self.check_transport_contents('some text for a\n', t, 'a')
263
407
# Put also replaces contents
264
t.put_file('a', StringIO('new\ncontents for\na\n'))
408
result = t.put_file('a', StringIO('new\ncontents for\na\n'))
409
self.assertEqual(19, result)
265
410
self.check_transport_contents('new\ncontents for\na\n', t, 'a')
266
411
self.assertRaises(NoSuchFile,
267
412
t.put_file, 'path/doesnt/exist/c',
398
537
unicode_file = pyStringIO(u'\u1234')
399
538
self.assertRaises(UnicodeEncodeError, t.put_file, 'foo', unicode_file)
401
def test_put_multi(self):
402
t = self.get_transport()
406
self.assertEqual(2, self.applyDeprecated(zero_eleven,
407
t.put_multi, [('a', StringIO('new\ncontents for\na\n')),
408
('d', StringIO('contents\nfor d\n'))]
410
self.assertEqual(list(t.has_multi(['a', 'b', 'c', 'd'])),
411
[True, False, False, True])
412
self.check_transport_contents('new\ncontents for\na\n', t, 'a')
413
self.check_transport_contents('contents\nfor d\n', t, 'd')
415
self.assertEqual(2, self.applyDeprecated(zero_eleven,
416
t.put_multi, iter([('a', StringIO('diff\ncontents for\na\n')),
417
('d', StringIO('another contents\nfor d\n'))])
419
self.check_transport_contents('diff\ncontents for\na\n', t, 'a')
420
self.check_transport_contents('another contents\nfor d\n', t, 'd')
422
def test_put_permissions(self):
423
t = self.get_transport()
427
if not t._can_roundtrip_unix_modebits():
428
# Can't roundtrip, so no need to run this test
430
self.applyDeprecated(zero_eleven, t.put, 'mode644',
431
StringIO('test text\n'), mode=0644)
432
self.assertTransportMode(t, 'mode644', 0644)
433
self.applyDeprecated(zero_eleven, t.put, 'mode666',
434
StringIO('test text\n'), mode=0666)
435
self.assertTransportMode(t, 'mode666', 0666)
436
self.applyDeprecated(zero_eleven, t.put, 'mode600',
437
StringIO('test text\n'), mode=0600)
438
self.assertTransportMode(t, 'mode600', 0600)
439
# Yes, you can put a file such that it becomes readonly
440
self.applyDeprecated(zero_eleven, t.put, 'mode400',
441
StringIO('test text\n'), mode=0400)
442
self.assertTransportMode(t, 'mode400', 0400)
443
self.applyDeprecated(zero_eleven, t.put_multi,
444
[('mmode644', StringIO('text\n'))], mode=0644)
445
self.assertTransportMode(t, 'mmode644', 0644)
447
# The default permissions should be based on the current umask
448
umask = osutils.get_umask()
449
self.applyDeprecated(zero_eleven, t.put, 'nomode',
450
StringIO('test text\n'), mode=None)
451
self.assertTransportMode(t, 'nomode', 0666 & ~umask)
453
540
def test_mkdir(self):
454
541
t = self.get_transport()
456
543
if t.is_readonly():
457
# cannot mkdir on readonly transports. We're not testing for
544
# cannot mkdir on readonly transports. We're not testing for
458
545
# cache coherency because cache behaviour is not currently
459
546
# defined for the transport interface.
460
547
self.assertRaises(TransportNotPossible, t.mkdir, '.')
520
607
t.mkdir('dnomode', mode=None)
521
608
self.assertTransportMode(t, 'dnomode', 0777 & ~umask)
610
def test_opening_a_file_stream_creates_file(self):
611
t = self.get_transport()
614
handle = t.open_write_stream('foo')
616
self.assertEqual('', t.get_bytes('foo'))
620
def test_opening_a_file_stream_can_set_mode(self):
621
t = self.get_transport()
624
if not t._can_roundtrip_unix_modebits():
625
# Can't roundtrip, so no need to run this test
627
def check_mode(name, mode, expected):
628
handle = t.open_write_stream(name, mode=mode)
630
self.assertTransportMode(t, name, expected)
631
check_mode('mode644', 0644, 0644)
632
check_mode('mode666', 0666, 0666)
633
check_mode('mode600', 0600, 0600)
634
# The default permissions should be based on the current umask
635
check_mode('nomode', None, 0666 & ~osutils.get_umask())
523
637
def test_copy_to(self):
524
638
# FIXME: test: same server to same server (partly done)
525
639
# same protocol two servers
526
640
# and different protocols (done for now except for MemoryTransport.
528
from bzrlib.transport.memory import MemoryTransport
530
643
def simple_copy_files(transport_from, transport_to):
531
644
files = ['a', 'b', 'c', 'd']
572
685
self.assertTransportMode(temp_transport, f, mode)
574
def test_append(self):
687
def test_create_prefix(self):
575
688
t = self.get_transport()
579
t.put_bytes('a', 'diff\ncontents for\na\n')
580
t.put_bytes('b', 'contents\nfor b\n')
582
self.assertEqual(20, self.applyDeprecated(zero_eleven,
583
t.append, 'a', StringIO('add\nsome\nmore\ncontents\n')))
585
self.check_transport_contents(
586
'diff\ncontents for\na\nadd\nsome\nmore\ncontents\n',
589
# And we can create new files, too
590
self.assertEqual(0, self.applyDeprecated(zero_eleven,
591
t.append, 'c', StringIO('some text\nfor a missing file\n')))
592
self.check_transport_contents('some text\nfor a missing file\n',
689
sub = t.clone('foo').clone('bar')
692
except TransportNotPossible:
693
self.assertTrue(t.is_readonly())
695
self.assertTrue(t.has('foo/bar'))
594
697
def test_append_file(self):
595
698
t = self.get_transport()
913
1035
def test_connection_error(self):
914
1036
"""ConnectionError is raised when connection is impossible.
916
The error may be raised from either the constructor or the first
917
operation on the transport.
1038
The error should be raised from the first operation on the transport.
920
1041
url = self._server.get_bogus_url()
921
1042
except NotImplementedError:
922
1043
raise TestSkipped("Transport %s has no bogus URL support." %
923
1044
self._server.__class__)
924
# This should be: but SSH still connects on construction. No COOKIE!
925
# self.assertRaises((ConnectionError, NoSuchFile), t.get, '.bzr/branch')
927
t = bzrlib.transport.get_transport(url)
929
except (ConnectionError, NoSuchFile), e:
931
except (Exception), e:
932
self.fail('Wrong exception thrown (%s.%s): %s'
933
% (e.__class__.__module__, e.__class__.__name__, e))
935
self.fail('Did not get the expected ConnectionError or NoSuchFile.')
1045
t = get_transport(url)
1046
self.assertRaises((ConnectionError, NoSuchFile), t.get, '.bzr/branch')
937
1048
def test_stat(self):
938
1049
# TODO: Test stat, just try once, and if it throws, stop testing
1023
1140
self.build_tree(['a/', 'a/%'], transport=t)
1025
1142
self.build_tree(['a/', 'a/%'])
1027
1144
names = list(t.list_dir('a'))
1028
1145
self.assertEqual(['%25'], names)
1029
1146
self.assertIsInstance(names[0], str)
1148
def test_clone_preserve_info(self):
1149
t1 = self.get_transport()
1150
if not isinstance(t1, ConnectedTransport):
1151
raise TestSkipped("not a connected transport")
1153
t2 = t1.clone('subdir')
1154
self.assertEquals(t1._scheme, t2._scheme)
1155
self.assertEquals(t1._user, t2._user)
1156
self.assertEquals(t1._password, t2._password)
1157
self.assertEquals(t1._host, t2._host)
1158
self.assertEquals(t1._port, t2._port)
1160
def test__reuse_for(self):
1161
t = self.get_transport()
1162
if not isinstance(t, ConnectedTransport):
1163
raise TestSkipped("not a connected transport")
1165
def new_url(scheme=None, user=None, password=None,
1166
host=None, port=None, path=None):
1167
"""Build a new url from t.base changing only parts of it.
1169
Only the parameters different from None will be changed.
1171
if scheme is None: scheme = t._scheme
1172
if user is None: user = t._user
1173
if password is None: password = t._password
1174
if user is None: user = t._user
1175
if host is None: host = t._host
1176
if port is None: port = t._port
1177
if path is None: path = t._path
1178
return t._unsplit_url(scheme, user, password, host, port, path)
1180
if t._scheme == 'ftp':
1184
self.assertIsNot(t, t._reuse_for(new_url(scheme=scheme)))
1189
self.assertIsNot(t, t._reuse_for(new_url(user=user)))
1190
# passwords are not taken into account because:
1191
# - it makes no sense to have two different valid passwords for the
1193
# - _password in ConnectedTransport is intended to collect what the
1194
# user specified from the command-line and there are cases where the
1195
# new url can contain no password (if the url was built from an
1196
# existing transport.base for example)
1197
# - password are considered part of the credentials provided at
1198
# connection creation time and as such may not be present in the url
1199
# (they may be typed by the user when prompted for example)
1200
self.assertIs(t, t._reuse_for(new_url(password='from space')))
1201
# We will not connect, we can use a invalid host
1202
self.assertIsNot(t, t._reuse_for(new_url(host=t._host + 'bar')))
1207
self.assertIsNot(t, t._reuse_for(new_url(port=port)))
1208
# No point in trying to reuse a transport for a local URL
1209
self.assertIs(None, t._reuse_for('/valid_but_not_existing'))
1211
def test_connection_sharing(self):
1212
t = self.get_transport()
1213
if not isinstance(t, ConnectedTransport):
1214
raise TestSkipped("not a connected transport")
1216
c = t.clone('subdir')
1217
# Some transports will create the connection only when needed
1218
t.has('surely_not') # Force connection
1219
self.assertIs(t._get_connection(), c._get_connection())
1221
# Temporary failure, we need to create a new dummy connection
1222
new_connection = object()
1223
t._set_connection(new_connection)
1224
# Check that both transports use the same connection
1225
self.assertIs(new_connection, t._get_connection())
1226
self.assertIs(new_connection, c._get_connection())
1228
def test_reuse_connection_for_various_paths(self):
1229
t = self.get_transport()
1230
if not isinstance(t, ConnectedTransport):
1231
raise TestSkipped("not a connected transport")
1233
t.has('surely_not') # Force connection
1234
self.assertIsNot(None, t._get_connection())
1236
subdir = t._reuse_for(t.base + 'whatever/but/deep/down/the/path')
1237
self.assertIsNot(t, subdir)
1238
self.assertIs(t._get_connection(), subdir._get_connection())
1240
home = subdir._reuse_for(t.base + 'home')
1241
self.assertIs(t._get_connection(), home._get_connection())
1242
self.assertIs(subdir._get_connection(), home._get_connection())
1031
1244
def test_clone(self):
1032
1245
# TODO: Test that clone moves up and down the filesystem
1033
1246
t1 = self.get_transport()
1139
1352
self.assertEqual(transport.clone("/").abspath('foo'),
1140
1353
transport.abspath("/foo"))
1355
def test_win32_abspath(self):
1356
# Note: we tried to set sys.platform='win32' so we could test on
1357
# other platforms too, but then osutils does platform specific
1358
# things at import time which defeated us...
1359
if sys.platform != 'win32':
1361
'Testing drive letters in abspath implemented only for win32')
1363
# smoke test for abspath on win32.
1364
# a transport based on 'file:///' never fully qualifies the drive.
1365
transport = get_transport("file:///")
1366
self.failUnlessEqual(transport.abspath("/"), "file:///")
1368
# but a transport that starts with a drive spec must keep it.
1369
transport = get_transport("file:///C:/")
1370
self.failUnlessEqual(transport.abspath("/"), "file:///C:/")
1142
1372
def test_local_abspath(self):
1143
1373
transport = self.get_transport()
1145
1375
p = transport.local_abspath('.')
1146
except TransportNotPossible:
1147
pass # This is not a local transport
1376
except (errors.NotLocalUrl, TransportNotPossible), e:
1377
# should be formattable
1149
1380
self.assertEqual(getcwd(), p)
1251
1516
self.check_transport_contents(contents, t, urlutils.escape(fname))
1253
1518
def test_connect_twice_is_same_content(self):
1254
# check that our server (whatever it is) is accessable reliably
1519
# check that our server (whatever it is) is accessible reliably
1255
1520
# via get_transport and multiple connections share content.
1256
1521
transport = self.get_transport()
1257
1522
if transport.is_readonly():
1259
1524
transport.put_bytes('foo', 'bar')
1260
transport2 = self.get_transport()
1261
self.check_transport_contents('bar', transport2, 'foo')
1262
# its base should be usable.
1263
transport2 = bzrlib.transport.get_transport(transport.base)
1264
self.check_transport_contents('bar', transport2, 'foo')
1525
transport3 = self.get_transport()
1526
self.check_transport_contents('bar', transport3, 'foo')
1266
1528
# now opening at a relative url should give use a sane result:
1267
1529
transport.mkdir('newdir')
1268
transport2 = bzrlib.transport.get_transport(transport.base + "newdir")
1269
transport2 = transport2.clone('..')
1270
self.check_transport_contents('bar', transport2, 'foo')
1530
transport5 = self.get_transport('newdir')
1531
transport6 = transport5.clone('..')
1532
self.check_transport_contents('bar', transport6, 'foo')
1272
1534
def test_lock_write(self):
1273
1535
"""Test transport-level write locks.
1334
1596
self.assertEqual(d[2], (0, '0'))
1335
1597
self.assertEqual(d[3], (3, '34'))
1599
def test_readv_with_adjust_for_latency(self):
1600
transport = self.get_transport()
1601
# the adjust for latency flag expands the data region returned
1602
# according to a per-transport heuristic, so testing is a little
1603
# tricky as we need more data than the largest combining that our
1604
# transports do. To accomodate this we generate random data and cross
1605
# reference the returned data with the random data. To avoid doing
1606
# multiple large random byte look ups we do several tests on the same
1608
content = osutils.rand_bytes(200*1024)
1609
content_size = len(content)
1610
if transport.is_readonly():
1611
self.build_tree_contents([('a', content)])
1613
transport.put_bytes('a', content)
1614
def check_result_data(result_vector):
1615
for item in result_vector:
1616
data_len = len(item[1])
1617
self.assertEqual(content[item[0]:item[0] + data_len], item[1])
1620
result = list(transport.readv('a', ((0, 30),),
1621
adjust_for_latency=True, upper_limit=content_size))
1622
# we expect 1 result, from 0, to something > 30
1623
self.assertEqual(1, len(result))
1624
self.assertEqual(0, result[0][0])
1625
self.assertTrue(len(result[0][1]) >= 30)
1626
check_result_data(result)
1627
# end of file corner case
1628
result = list(transport.readv('a', ((204700, 100),),
1629
adjust_for_latency=True, upper_limit=content_size))
1630
# we expect 1 result, from 204800- its length, to the end
1631
self.assertEqual(1, len(result))
1632
data_len = len(result[0][1])
1633
self.assertEqual(204800-data_len, result[0][0])
1634
self.assertTrue(data_len >= 100)
1635
check_result_data(result)
1636
# out of order ranges are made in order
1637
result = list(transport.readv('a', ((204700, 100), (0, 50)),
1638
adjust_for_latency=True, upper_limit=content_size))
1639
# we expect 2 results, in order, start and end.
1640
self.assertEqual(2, len(result))
1642
data_len = len(result[0][1])
1643
self.assertEqual(0, result[0][0])
1644
self.assertTrue(data_len >= 30)
1646
data_len = len(result[1][1])
1647
self.assertEqual(204800-data_len, result[1][0])
1648
self.assertTrue(data_len >= 100)
1649
check_result_data(result)
1650
# close ranges get combined (even if out of order)
1651
for request_vector in [((400,50), (800, 234)), ((800, 234), (400,50))]:
1652
result = list(transport.readv('a', request_vector,
1653
adjust_for_latency=True, upper_limit=content_size))
1654
self.assertEqual(1, len(result))
1655
data_len = len(result[0][1])
1656
# minimum length is from 400 to 1034 - 634
1657
self.assertTrue(data_len >= 634)
1658
# must contain the region 400 to 1034
1659
self.assertTrue(result[0][0] <= 400)
1660
self.assertTrue(result[0][0] + data_len >= 1034)
1661
check_result_data(result)
1663
def test_readv_with_adjust_for_latency_with_big_file(self):
1664
transport = self.get_transport()
1665
# test from observed failure case.
1666
if transport.is_readonly():
1667
file('a', 'w').write('a'*1024*1024)
1669
transport.put_bytes('a', 'a'*1024*1024)
1670
broken_vector = [(465219, 800), (225221, 800), (445548, 800),
1671
(225037, 800), (221357, 800), (437077, 800), (947670, 800),
1672
(465373, 800), (947422, 800)]
1673
results = list(transport.readv('a', broken_vector, True, 1024*1024))
1674
found_items = [False]*9
1675
for pos, (start, length) in enumerate(broken_vector):
1676
# check the range is covered by the result
1677
for offset, data in results:
1678
if offset <= start and start + length <= offset + len(data):
1679
found_items[pos] = True
1680
self.assertEqual([True]*9, found_items)
1682
def test_get_with_open_write_stream_sees_all_content(self):
1683
t = self.get_transport()
1686
handle = t.open_write_stream('foo')
1689
self.assertEqual([(0, 'b'), (2, 'd')], list(t.readv('foo', ((0,1), (2,1)))))
1337
1693
def test_get_smart_medium(self):
1338
1694
"""All transports must either give a smart medium, or know they can't.