1
# Copyright (C) 2004, 2005 by 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 Transport implementations.
19
Transport implementations tested here are supplied by
20
TransportTestProviderAdapter.
24
from cStringIO import StringIO
26
from bzrlib.errors import (NoSuchFile, FileExists,
27
TransportNotPossible, ConnectionError)
28
from bzrlib.tests import TestCase, TestCaseInTempDir
29
from bzrlib.transport import memory, urlescape
30
import bzrlib.transport
34
"""Append the given text (file-like object) to the supplied filename."""
42
class TestTransportImplementation(TestCaseInTempDir):
43
"""Implementation verification for transports.
45
To verify a transport we need a server factory, which is a callable
46
that accepts no parameters and returns an implementation of
47
bzrlib.transport.Server.
49
That Server is then used to construct transport instances and test
50
the transport via loopback activity.
52
Currently this assumes that the Transport object is connected to the
53
current working directory. So that whatever is done
54
through the transport, should show up in the working
55
directory, and vice-versa. This is a bug, because its possible to have
56
URL schemes which provide access to something that may not be
57
result in storage on the local disk, i.e. due to file system limits, or
58
due to it being a database or some other non-filesystem tool.
60
This also tests to make sure that the functions work with both
61
generators and lists (assuming iter(list) is effectively a generator)
65
super(TestTransportImplementation, self).setUp()
66
self._server = self.transport_server()
70
super(TestTransportImplementation, self).tearDown()
71
self._server.tearDown()
73
def check_transport_contents(self, content, transport, relpath):
74
"""Check that transport.get(relpath).read() == content."""
75
self.assertEqual( content, transport.get(relpath).read())
77
def get_transport(self):
78
"""Return a connected transport to the local directory."""
79
t = bzrlib.transport.get_transport(self._server.get_url())
80
self.failUnless(isinstance(t, self.transport_class),
81
"Got the wrong class from get_transport"
82
"(%r, expected %r)" % (t.__class__,
83
self.transport_class))
86
def assertListRaises(self, excClass, func, *args, **kwargs):
87
"""Many transport functions can return generators this makes sure
88
to wrap them in a list() call to make sure the whole generator
89
is run, and that the proper exception is raised.
92
list(func(*args, **kwargs))
96
if hasattr(excClass,'__name__'): excName = excClass.__name__
97
else: excName = str(excClass)
98
raise self.failureException, "%s not raised" % excName
101
t = self.get_transport()
103
files = ['a', 'b', 'e', 'g', '%']
104
self.build_tree(files, transport=t)
105
self.assertEqual(True, t.has('a'))
106
self.assertEqual(False, t.has('c'))
107
self.assertEqual(True, t.has(urlescape('%')))
108
self.assertEqual(list(t.has_multi(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])),
109
[True, True, False, False, True, False, True, False])
110
self.assertEqual(True, t.has_any(['a', 'b', 'c']))
111
self.assertEqual(False, t.has_any(['c', 'd', 'f', urlescape('%%')]))
112
self.assertEqual(list(t.has_multi(iter(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']))),
113
[True, True, False, False, True, False, True, False])
114
self.assertEqual(False, t.has_any(['c', 'c', 'c']))
115
self.assertEqual(True, t.has_any(['b', 'b', 'b']))
118
t = self.get_transport()
120
files = ['a', 'b', 'e', 'g']
121
contents = ['contents of a\n',
126
self.build_tree(files, transport=t)
127
self.check_transport_contents('contents of a\n', t, 'a')
128
content_f = t.get_multi(files)
129
for content, f in zip(contents, content_f):
130
self.assertEqual(content, f.read())
132
content_f = t.get_multi(iter(files))
133
for content, f in zip(contents, content_f):
134
self.assertEqual(content, f.read())
136
self.assertRaises(NoSuchFile, t.get, 'c')
137
self.assertListRaises(NoSuchFile, t.get_multi, ['a', 'b', 'c'])
138
self.assertListRaises(NoSuchFile, t.get_multi, iter(['a', 'b', 'c']))
141
t = self.get_transport()
144
self.assertRaises(TransportNotPossible,
145
t.put, 'a', 'some text for a\n')
148
t.put('a', StringIO('some text for a\n'))
149
self.failUnless(t.has('a'))
150
self.check_transport_contents('some text for a\n', t, 'a')
151
# Make sure 'has' is updated
152
self.assertEqual(list(t.has_multi(['a', 'b', 'c', 'd', 'e'])),
153
[True, False, False, False, False])
154
# Put also replaces contents
155
self.assertEqual(t.put_multi([('a', StringIO('new\ncontents for\na\n')),
156
('d', StringIO('contents\nfor d\n'))]),
158
self.assertEqual(list(t.has_multi(['a', 'b', 'c', 'd', 'e'])),
159
[True, False, False, True, False])
160
self.check_transport_contents('new\ncontents for\na\n', t, 'a')
161
self.check_transport_contents('contents\nfor d\n', t, 'd')
164
t.put_multi(iter([('a', StringIO('diff\ncontents for\na\n')),
165
('d', StringIO('another contents\nfor d\n'))])),
167
self.check_transport_contents('diff\ncontents for\na\n', t, 'a')
168
self.check_transport_contents('another contents\nfor d\n', t, 'd')
170
self.assertRaises(NoSuchFile,
171
t.put, 'path/doesnt/exist/c', 'contents')
173
def test_mkdir(self):
174
t = self.get_transport()
177
# cannot mkdir on readonly transports. We're not testing for
178
# cache coherency because cache behaviour is not currently
179
# defined for the transport interface.
180
self.assertRaises(TransportNotPossible, t.mkdir, '.')
181
self.assertRaises(TransportNotPossible, t.mkdir, 'new_dir')
182
self.assertRaises(TransportNotPossible, t.mkdir_multi, ['new_dir'])
183
self.assertRaises(TransportNotPossible, t.mkdir, 'path/doesnt/exist')
187
self.assertEqual(t.has('dir_a'), True)
188
self.assertEqual(t.has('dir_b'), False)
191
self.assertEqual(t.has('dir_b'), True)
193
t.mkdir_multi(['dir_c', 'dir_d'])
195
t.mkdir_multi(iter(['dir_e', 'dir_f']))
196
self.assertEqual(list(t.has_multi(
197
['dir_a', 'dir_b', 'dir_c', 'dir_q',
198
'dir_d', 'dir_e', 'dir_f', 'dir_b'])),
199
[True, True, True, False,
200
True, True, True, True])
202
# we were testing that a local mkdir followed by a transport
203
# mkdir failed thusly, but given that we * in one process * do not
204
# concurrently fiddle with disk dirs and then use transport to do
205
# things, the win here seems marginal compared to the constraint on
206
# the interface. RBC 20051227
208
self.assertRaises(FileExists, t.mkdir, 'dir_g')
210
# Test get/put in sub-directories
212
t.put_multi([('dir_a/a', StringIO('contents of dir_a/a')),
213
('dir_b/b', StringIO('contents of dir_b/b'))])
215
self.check_transport_contents('contents of dir_a/a', t, 'dir_a/a')
216
self.check_transport_contents('contents of dir_b/b', t, 'dir_b/b')
218
def test_copy_to(self):
220
from bzrlib.transport.memory import MemoryTransport
222
t = self.get_transport()
224
files = ['a', 'b', 'c', 'd']
225
self.build_tree(files, transport=t)
227
temp_transport = MemoryTransport('memory:/')
229
t.copy_to(files, temp_transport)
231
self.check_transport_contents(temp_transport.get(f).read(),
234
# Test that copying into a missing directory raises
238
open('e/f', 'wb').write('contents of e')
241
t.put('e/f', StringIO('contents of e'))
242
self.assertRaises(NoSuchFile, t.copy_to, ['e/f'], temp_transport)
243
temp_transport.mkdir('e')
244
t.copy_to(['e/f'], temp_transport)
247
temp_transport = MemoryTransport('memory:/')
249
files = ['a', 'b', 'c', 'd']
250
t.copy_to(iter(files), temp_transport)
252
self.check_transport_contents(temp_transport.get(f).read(),
256
def test_append(self):
257
t = self.get_transport()
260
open('a', 'wb').write('diff\ncontents for\na\n')
261
open('b', 'wb').write('contents\nfor b\n')
264
('a', StringIO('diff\ncontents for\na\n')),
265
('b', StringIO('contents\nfor b\n'))
269
self.assertRaises(TransportNotPossible,
270
t.append, 'a', 'add\nsome\nmore\ncontents\n')
271
_append('a', StringIO('add\nsome\nmore\ncontents\n'))
273
t.append('a', StringIO('add\nsome\nmore\ncontents\n'))
275
self.check_transport_contents(
276
'diff\ncontents for\na\nadd\nsome\nmore\ncontents\n',
280
self.assertRaises(TransportNotPossible,
282
[('a', 'and\nthen\nsome\nmore\n'),
283
('b', 'some\nmore\nfor\nb\n')])
284
_append('a', StringIO('and\nthen\nsome\nmore\n'))
285
_append('b', StringIO('some\nmore\nfor\nb\n'))
287
t.append_multi([('a', StringIO('and\nthen\nsome\nmore\n')),
288
('b', StringIO('some\nmore\nfor\nb\n'))])
289
self.check_transport_contents(
290
'diff\ncontents for\na\n'
291
'add\nsome\nmore\ncontents\n'
292
'and\nthen\nsome\nmore\n',
294
self.check_transport_contents(
296
'some\nmore\nfor\nb\n',
300
_append('a', StringIO('a little bit more\n'))
301
_append('b', StringIO('from an iterator\n'))
303
t.append_multi(iter([('a', StringIO('a little bit more\n')),
304
('b', StringIO('from an iterator\n'))]))
305
self.check_transport_contents(
306
'diff\ncontents for\na\n'
307
'add\nsome\nmore\ncontents\n'
308
'and\nthen\nsome\nmore\n'
309
'a little bit more\n',
311
self.check_transport_contents(
313
'some\nmore\nfor\nb\n'
314
'from an iterator\n',
318
_append('c', StringIO('some text\nfor a missing file\n'))
319
_append('a', StringIO('some text in a\n'))
320
_append('d', StringIO('missing file r\n'))
322
t.append('c', StringIO('some text\nfor a missing file\n'))
323
t.append_multi([('a', StringIO('some text in a\n')),
324
('d', StringIO('missing file r\n'))])
325
self.check_transport_contents(
326
'diff\ncontents for\na\n'
327
'add\nsome\nmore\ncontents\n'
328
'and\nthen\nsome\nmore\n'
329
'a little bit more\n'
332
self.check_transport_contents('some text\nfor a missing file\n',
334
self.check_transport_contents('missing file r\n', t, 'd')
336
def test_append_file(self):
337
t = self.get_transport()
340
('f1', StringIO('this is a string\nand some more stuff\n')),
341
('f2', StringIO('here is some text\nand a bit more\n')),
342
('f3', StringIO('some text for the\nthird file created\n')),
343
('f4', StringIO('this is a string\nand some more stuff\n')),
344
('f5', StringIO('here is some text\nand a bit more\n')),
345
('f6', StringIO('some text for the\nthird file created\n'))
349
for f, val in contents:
350
open(f, 'wb').write(val.read())
352
t.put_multi(contents)
354
a1 = StringIO('appending to\none\n')
362
self.check_transport_contents(
363
'this is a string\nand some more stuff\n'
364
'appending to\none\n',
367
a2 = StringIO('adding more\ntext to two\n')
368
a3 = StringIO('some garbage\nto put in three\n')
374
t.append_multi([('f2', a2), ('f3', a3)])
378
self.check_transport_contents(
379
'here is some text\nand a bit more\n'
380
'adding more\ntext to two\n',
382
self.check_transport_contents(
383
'some text for the\nthird file created\n'
384
'some garbage\nto put in three\n',
387
# Test that an actual file object can be used with put
396
self.check_transport_contents(
397
'this is a string\nand some more stuff\n'
398
'this is a string\nand some more stuff\n'
399
'appending to\none\n',
408
t.append_multi([('f5', a5), ('f6', a6)])
412
self.check_transport_contents(
413
'here is some text\nand a bit more\n'
414
'here is some text\nand a bit more\n'
415
'adding more\ntext to two\n',
417
self.check_transport_contents(
418
'some text for the\nthird file created\n'
419
'some text for the\nthird file created\n'
420
'some garbage\nto put in three\n',
432
t.append_multi([('a', a6), ('d', a7)])
434
self.check_transport_contents(t.get('f2').read(), t, 'c')
435
self.check_transport_contents(t.get('f3').read(), t, 'd')
438
def test_delete(self):
439
# TODO: Test Transport.delete
440
t = self.get_transport()
442
# Not much to do with a readonly transport
446
t.put('a', StringIO('a little bit of text\n'))
447
self.failUnless(t.has('a'))
449
self.failIf(t.has('a'))
451
self.assertRaises(NoSuchFile, t.delete, 'a')
453
t.put('a', StringIO('a text\n'))
454
t.put('b', StringIO('b text\n'))
455
t.put('c', StringIO('c text\n'))
456
self.assertEqual([True, True, True],
457
list(t.has_multi(['a', 'b', 'c'])))
458
t.delete_multi(['a', 'c'])
459
self.assertEqual([False, True, False],
460
list(t.has_multi(['a', 'b', 'c'])))
461
self.failIf(t.has('a'))
462
self.failUnless(t.has('b'))
463
self.failIf(t.has('c'))
465
self.assertRaises(NoSuchFile,
466
t.delete_multi, ['a', 'b', 'c'])
468
self.assertRaises(NoSuchFile,
469
t.delete_multi, iter(['a', 'b', 'c']))
471
t.put('a', StringIO('another a text\n'))
472
t.put('c', StringIO('another c text\n'))
473
t.delete_multi(iter(['a', 'b', 'c']))
475
# We should have deleted everything
476
# SftpServer creates control files in the
477
# working directory, so we can just do a
479
# self.assertEqual([], os.listdir('.'))
482
t = self.get_transport()
487
# TODO: I would like to use os.listdir() to
488
# make sure there are no extra files, but SftpServer
489
# creates control files in the working directory
490
# perhaps all of this could be done in a subdirectory
492
t.put('a', StringIO('a first file\n'))
493
self.assertEquals([True, False], list(t.has_multi(['a', 'b'])))
496
self.failUnless(t.has('b'))
497
self.failIf(t.has('a'))
499
self.check_transport_contents('a first file\n', t, 'b')
500
self.assertEquals([False, True], list(t.has_multi(['a', 'b'])))
503
t.put('c', StringIO('c this file\n'))
505
self.failIf(t.has('c'))
506
self.check_transport_contents('c this file\n', t, 'b')
508
# TODO: Try to write a test for atomicity
509
# TODO: Test moving into a non-existant subdirectory
510
# TODO: Test Transport.move_multi
513
t = self.get_transport()
518
t.put('a', StringIO('a file\n'))
520
self.check_transport_contents('a file\n', t, 'b')
522
self.assertRaises(NoSuchFile, t.copy, 'c', 'd')
524
# What should the assert be if you try to copy a
525
# file over a directory?
526
#self.assertRaises(Something, t.copy, 'a', 'c')
527
t.put('d', StringIO('text in d\n'))
529
self.check_transport_contents('text in d\n', t, 'b')
531
# TODO: test copy_multi
533
def test_connection_error(self):
534
"""ConnectionError is raised when connection is impossible"""
535
if not hasattr(self, "get_bogus_transport"):
537
t = self.get_bogus_transport()
540
except (ConnectionError, NoSuchFile), e:
542
except (Exception), e:
543
self.failIf(True, 'Wrong exception thrown: %s' % e)
545
self.failIf(True, 'Did not get the expected exception.')
548
# TODO: Test stat, just try once, and if it throws, stop testing
549
from stat import S_ISDIR, S_ISREG
551
t = self.get_transport()
555
except TransportNotPossible, e:
556
# This transport cannot stat
559
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e']
560
sizes = [14, 0, 16, 0, 18]
561
self.build_tree(paths, transport=t)
563
for path, size in zip(paths, sizes):
565
if path.endswith('/'):
566
self.failUnless(S_ISDIR(st.st_mode))
567
# directory sizes are meaningless
569
self.failUnless(S_ISREG(st.st_mode))
570
self.assertEqual(size, st.st_size)
572
remote_stats = list(t.stat_multi(paths))
573
remote_iter_stats = list(t.stat_multi(iter(paths)))
575
self.assertRaises(NoSuchFile, t.stat, 'q')
576
self.assertRaises(NoSuchFile, t.stat, 'b/a')
578
self.assertListRaises(NoSuchFile, t.stat_multi, ['a', 'c', 'd'])
579
self.assertListRaises(NoSuchFile, t.stat_multi, iter(['a', 'c', 'd']))
581
def test_list_dir(self):
582
# TODO: Test list_dir, just try once, and if it throws, stop testing
583
t = self.get_transport()
586
self.assertRaises(TransportNotPossible, t.list_dir, '.')
590
l = list(t.list_dir(d))
594
# SftpServer creates control files in the working directory
595
# so lets move down a directory to avoid those.
599
self.assertEqual([], sorted_list(u'.'))
600
self.build_tree(['a', 'b', 'c/', 'c/d', 'c/e'], transport=t)
602
self.assertEqual([u'a', u'b', u'c'], sorted_list(u'.'))
603
self.assertEqual([u'd', u'e'], sorted_list(u'c'))
607
self.assertEqual([u'a', u'c'], sorted_list('.'))
608
self.assertEqual([u'e'], sorted_list(u'c'))
610
self.assertListRaises(NoSuchFile, t.list_dir, 'q')
611
self.assertListRaises(NoSuchFile, t.list_dir, 'c/f')
612
self.assertListRaises(NoSuchFile, t.list_dir, 'a')
614
def test_clone(self):
615
# TODO: Test that clone moves up and down the filesystem
616
t1 = self.get_transport()
618
self.build_tree(['a', 'b/', 'b/c'], transport=t1)
620
self.failUnless(t1.has('a'))
621
self.failUnless(t1.has('b/c'))
622
self.failIf(t1.has('c'))
625
self.assertEqual(t1.base + 'b/', t2.base)
627
self.failUnless(t2.has('c'))
628
self.failIf(t2.has('a'))
631
self.failUnless(t3.has('a'))
632
self.failIf(t3.has('c'))
634
self.failIf(t1.has('b/d'))
635
self.failIf(t2.has('d'))
636
self.failIf(t3.has('b/d'))
639
open('b/d', 'wb').write('newfile\n')
641
t2.put('d', StringIO('newfile\n'))
643
self.failUnless(t1.has('b/d'))
644
self.failUnless(t2.has('d'))
645
self.failUnless(t3.has('b/d'))
647
def test_relpath(self):
648
t = self.get_transport()
649
self.assertEqual('', t.relpath(t.base))
651
self.assertEqual('', t.relpath(t.base[:-1]))
652
# subdirs which dont exist should still give relpaths.
653
self.assertEqual('foo', t.relpath(t.base + 'foo'))
654
# trailing slash should be the same.
655
self.assertEqual('foo', t.relpath(t.base + 'foo/'))
657
def test_abspath(self):
658
# smoke test for abspath. Corner cases for backends like unix fs's
659
# that have aliasing problems like symlinks should go in backend
660
# specific test cases.
661
transport = self.get_transport()
662
self.assertEqual(transport.base + 'relpath',
663
transport.abspath('relpath'))
666
###class HttpTransportTest(TestCaseWithWebserver):
670
### def get_bogus_transport(self):
671
### from bzrlib.transport.http import HttpTransport
672
### return HttpTransport('http://jasldkjsalkdjalksjdkljasd')
675
class TestMemoryTransport(TestCase):
677
def test_get_transport(self):
678
memory.MemoryTransport()
680
def test_relpath(self):
681
transport = memory.MemoryTransport()
683
def test_append_and_get(self):
684
transport = memory.MemoryTransport()
685
transport.append('path', StringIO('content'))
686
self.assertEqual(transport.get('path').read(), 'content')
687
transport.append('path', StringIO('content'))
688
self.assertEqual(transport.get('path').read(), 'contentcontent')
690
def test_put_and_get(self):
691
transport = memory.MemoryTransport()
692
transport.put('path', StringIO('content'))
693
self.assertEqual(transport.get('path').read(), 'content')
694
transport.put('path', StringIO('content'))
695
self.assertEqual(transport.get('path').read(), 'content')
697
def test_append_without_dir_fails(self):
698
transport = memory.MemoryTransport()
699
self.assertRaises(NoSuchFile,
700
transport.append, 'dir/path', StringIO('content'))
702
def test_put_without_dir_fails(self):
703
transport = memory.MemoryTransport()
704
self.assertRaises(NoSuchFile,
705
transport.put, 'dir/path', StringIO('content'))
707
def test_get_missing(self):
708
transport = memory.MemoryTransport()
709
self.assertRaises(NoSuchFile, transport.get, 'foo')
711
def test_has_missing(self):
712
transport = memory.MemoryTransport()
713
self.assertEquals(False, transport.has('foo'))
715
def test_has_present(self):
716
transport = memory.MemoryTransport()
717
transport.append('foo', StringIO('content'))
718
self.assertEquals(True, transport.has('foo'))
720
def test_mkdir(self):
721
transport = memory.MemoryTransport()
722
transport.mkdir('dir')
723
transport.append('dir/path', StringIO('content'))
724
self.assertEqual(transport.get('dir/path').read(), 'content')
726
def test_mkdir_missing_parent(self):
727
transport = memory.MemoryTransport()
728
self.assertRaises(NoSuchFile,
729
transport.mkdir, 'dir/dir')
731
def test_mkdir_twice(self):
732
transport = memory.MemoryTransport()
733
transport.mkdir('dir')
734
self.assertRaises(FileExists, transport.mkdir, 'dir')
736
def test_parameters(self):
737
transport = memory.MemoryTransport()
738
self.assertEqual(True, transport.listable())
739
self.assertEqual(False, transport.should_cache())
741
def test_iter_files_recursive(self):
742
transport = memory.MemoryTransport()
743
transport.mkdir('dir')
744
transport.put('dir/foo', StringIO('content'))
745
transport.put('dir/bar', StringIO('content'))
746
transport.put('bar', StringIO('content'))
747
paths = set(transport.iter_files_recursive())
748
self.assertEqual(set(['dir/foo', 'dir/bar', 'bar']), paths)
751
transport = memory.MemoryTransport()
752
transport.put('foo', StringIO('content'))
753
transport.put('bar', StringIO('phowar'))
754
self.assertEqual(7, transport.stat('foo').st_size)
755
self.assertEqual(6, transport.stat('bar').st_size)