~bzr-pqm/bzr/bzr.dev

1553.5.10 by Martin Pool
New DirectoryNotEmpty exception, and raise this from local and memory
1
# Copyright (C) 2004, 2005, 2006 by Canonical Ltd
1530.1.3 by Robert Collins
transport implementations now tested consistently.
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
"""Tests for Transport implementations.
18
19
Transport implementations tested here are supplied by
20
TransportTestProviderAdapter.
21
"""
22
23
import os
24
from cStringIO import StringIO
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
25
import stat
26
import sys
1530.1.3 by Robert Collins
transport implementations now tested consistently.
27
1553.5.10 by Martin Pool
New DirectoryNotEmpty exception, and raise this from local and memory
28
from bzrlib.errors import (DirectoryNotEmpty, NoSuchFile, FileExists,
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
29
                           LockError,
1553.5.10 by Martin Pool
New DirectoryNotEmpty exception, and raise this from local and memory
30
                           PathError,
1530.1.3 by Robert Collins
transport implementations now tested consistently.
31
                           TransportNotPossible, ConnectionError)
1530.1.9 by Robert Collins
Test bogus urls with http in the new infrastructure.
32
from bzrlib.tests import TestCaseInTempDir, TestSkipped
1530.1.3 by Robert Collins
transport implementations now tested consistently.
33
from bzrlib.transport import memory, urlescape
34
import bzrlib.transport
35
36
37
def _append(fn, txt):
38
    """Append the given text (file-like object) to the supplied filename."""
39
    f = open(fn, 'ab')
1530.1.21 by Robert Collins
Review feedback fixes.
40
    try:
41
        f.write(txt.read())
42
    finally:
43
        f.close()
1530.1.3 by Robert Collins
transport implementations now tested consistently.
44
45
46
class TestTransportImplementation(TestCaseInTempDir):
47
    """Implementation verification for transports.
48
    
49
    To verify a transport we need a server factory, which is a callable
50
    that accepts no parameters and returns an implementation of
51
    bzrlib.transport.Server.
52
    
53
    That Server is then used to construct transport instances and test
54
    the transport via loopback activity.
55
56
    Currently this assumes that the Transport object is connected to the 
57
    current working directory.  So that whatever is done 
58
    through the transport, should show up in the working 
59
    directory, and vice-versa. This is a bug, because its possible to have
60
    URL schemes which provide access to something that may not be 
61
    result in storage on the local disk, i.e. due to file system limits, or 
62
    due to it being a database or some other non-filesystem tool.
63
64
    This also tests to make sure that the functions work with both
65
    generators and lists (assuming iter(list) is effectively a generator)
66
    """
67
    
68
    def setUp(self):
69
        super(TestTransportImplementation, self).setUp()
70
        self._server = self.transport_server()
71
        self._server.setUp()
72
73
    def tearDown(self):
74
        super(TestTransportImplementation, self).tearDown()
75
        self._server.tearDown()
76
        
77
    def check_transport_contents(self, content, transport, relpath):
78
        """Check that transport.get(relpath).read() == content."""
1530.1.21 by Robert Collins
Review feedback fixes.
79
        self.assertEqualDiff(content, transport.get(relpath).read())
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
80
1530.1.3 by Robert Collins
transport implementations now tested consistently.
81
    def get_transport(self):
82
        """Return a connected transport to the local directory."""
83
        t = bzrlib.transport.get_transport(self._server.get_url())
84
        self.failUnless(isinstance(t, self.transport_class), 
85
                        "Got the wrong class from get_transport"
86
                        "(%r, expected %r)" % (t.__class__, 
87
                                               self.transport_class))
88
        return t
89
90
    def assertListRaises(self, excClass, func, *args, **kwargs):
1530.1.21 by Robert Collins
Review feedback fixes.
91
        """Fail unless excClass is raised when the iterator from func is used.
92
        
93
        Many transport functions can return generators this makes sure
1530.1.3 by Robert Collins
transport implementations now tested consistently.
94
        to wrap them in a list() call to make sure the whole generator
95
        is run, and that the proper exception is raised.
96
        """
97
        try:
98
            list(func(*args, **kwargs))
99
        except excClass:
100
            return
101
        else:
102
            if hasattr(excClass,'__name__'): excName = excClass.__name__
103
            else: excName = str(excClass)
104
            raise self.failureException, "%s not raised" % excName
105
106
    def test_has(self):
107
        t = self.get_transport()
108
109
        files = ['a', 'b', 'e', 'g', '%']
110
        self.build_tree(files, transport=t)
111
        self.assertEqual(True, t.has('a'))
112
        self.assertEqual(False, t.has('c'))
113
        self.assertEqual(True, t.has(urlescape('%')))
114
        self.assertEqual(list(t.has_multi(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])),
115
                [True, True, False, False, True, False, True, False])
116
        self.assertEqual(True, t.has_any(['a', 'b', 'c']))
117
        self.assertEqual(False, t.has_any(['c', 'd', 'f', urlescape('%%')]))
118
        self.assertEqual(list(t.has_multi(iter(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']))),
119
                [True, True, False, False, True, False, True, False])
120
        self.assertEqual(False, t.has_any(['c', 'c', 'c']))
121
        self.assertEqual(True, t.has_any(['b', 'b', 'b']))
122
123
    def test_get(self):
124
        t = self.get_transport()
125
126
        files = ['a', 'b', 'e', 'g']
127
        contents = ['contents of a\n',
128
                    'contents of b\n',
129
                    'contents of e\n',
130
                    'contents of g\n',
131
                    ]
132
        self.build_tree(files, transport=t)
133
        self.check_transport_contents('contents of a\n', t, 'a')
134
        content_f = t.get_multi(files)
135
        for content, f in zip(contents, content_f):
136
            self.assertEqual(content, f.read())
137
138
        content_f = t.get_multi(iter(files))
139
        for content, f in zip(contents, content_f):
140
            self.assertEqual(content, f.read())
141
142
        self.assertRaises(NoSuchFile, t.get, 'c')
143
        self.assertListRaises(NoSuchFile, t.get_multi, ['a', 'b', 'c'])
144
        self.assertListRaises(NoSuchFile, t.get_multi, iter(['a', 'b', 'c']))
145
146
    def test_put(self):
147
        t = self.get_transport()
148
149
        if t.is_readonly():
150
            self.assertRaises(TransportNotPossible,
151
                    t.put, 'a', 'some text for a\n')
152
            return
153
154
        t.put('a', StringIO('some text for a\n'))
155
        self.failUnless(t.has('a'))
156
        self.check_transport_contents('some text for a\n', t, 'a')
157
        # Make sure 'has' is updated
158
        self.assertEqual(list(t.has_multi(['a', 'b', 'c', 'd', 'e'])),
159
                [True, False, False, False, False])
160
        # Put also replaces contents
161
        self.assertEqual(t.put_multi([('a', StringIO('new\ncontents for\na\n')),
162
                                      ('d', StringIO('contents\nfor d\n'))]),
163
                         2)
164
        self.assertEqual(list(t.has_multi(['a', 'b', 'c', 'd', 'e'])),
165
                [True, False, False, True, False])
166
        self.check_transport_contents('new\ncontents for\na\n', t, 'a')
167
        self.check_transport_contents('contents\nfor d\n', t, 'd')
168
169
        self.assertEqual(
170
            t.put_multi(iter([('a', StringIO('diff\ncontents for\na\n')),
171
                              ('d', StringIO('another contents\nfor d\n'))])),
172
                        2)
173
        self.check_transport_contents('diff\ncontents for\na\n', t, 'a')
174
        self.check_transport_contents('another contents\nfor d\n', t, 'd')
175
176
        self.assertRaises(NoSuchFile,
177
                          t.put, 'path/doesnt/exist/c', 'contents')
178
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
179
    def test_put_permissions(self):
180
        t = self.get_transport()
181
182
        if t.is_readonly():
183
            return
184
        t.put('mode644', StringIO('test text\n'), mode=0644)
1530.1.21 by Robert Collins
Review feedback fixes.
185
        self.assertTransportMode(t, 'mode644', 0644)
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
186
        t.put('mode666', StringIO('test text\n'), mode=0666)
1530.1.21 by Robert Collins
Review feedback fixes.
187
        self.assertTransportMode(t, 'mode666', 0666)
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
188
        t.put('mode600', StringIO('test text\n'), mode=0600)
1530.1.21 by Robert Collins
Review feedback fixes.
189
        self.assertTransportMode(t, 'mode600', 0600)
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
190
        # Yes, you can put a file such that it becomes readonly
191
        t.put('mode400', StringIO('test text\n'), mode=0400)
1530.1.21 by Robert Collins
Review feedback fixes.
192
        self.assertTransportMode(t, 'mode400', 0400)
1530.1.15 by Robert Collins
Move put mode tests into test_transport_implementation.
193
        t.put_multi([('mmode644', StringIO('text\n'))], mode=0644)
1530.1.21 by Robert Collins
Review feedback fixes.
194
        self.assertTransportMode(t, 'mmode644', 0644)
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
195
        
1530.1.3 by Robert Collins
transport implementations now tested consistently.
196
    def test_mkdir(self):
197
        t = self.get_transport()
198
199
        if t.is_readonly():
200
            # cannot mkdir on readonly transports. We're not testing for 
201
            # cache coherency because cache behaviour is not currently
202
            # defined for the transport interface.
203
            self.assertRaises(TransportNotPossible, t.mkdir, '.')
204
            self.assertRaises(TransportNotPossible, t.mkdir, 'new_dir')
205
            self.assertRaises(TransportNotPossible, t.mkdir_multi, ['new_dir'])
206
            self.assertRaises(TransportNotPossible, t.mkdir, 'path/doesnt/exist')
207
            return
208
        # Test mkdir
209
        t.mkdir('dir_a')
210
        self.assertEqual(t.has('dir_a'), True)
211
        self.assertEqual(t.has('dir_b'), False)
212
213
        t.mkdir('dir_b')
214
        self.assertEqual(t.has('dir_b'), True)
215
216
        t.mkdir_multi(['dir_c', 'dir_d'])
217
218
        t.mkdir_multi(iter(['dir_e', 'dir_f']))
219
        self.assertEqual(list(t.has_multi(
220
            ['dir_a', 'dir_b', 'dir_c', 'dir_q',
221
             'dir_d', 'dir_e', 'dir_f', 'dir_b'])),
222
            [True, True, True, False,
223
             True, True, True, True])
224
225
        # we were testing that a local mkdir followed by a transport
226
        # mkdir failed thusly, but given that we * in one process * do not
227
        # concurrently fiddle with disk dirs and then use transport to do 
228
        # things, the win here seems marginal compared to the constraint on
229
        # the interface. RBC 20051227
230
        t.mkdir('dir_g')
231
        self.assertRaises(FileExists, t.mkdir, 'dir_g')
232
233
        # Test get/put in sub-directories
1563.2.3 by Robert Collins
Change the return signature of transport.append and append_multi to return the length of the pre-append content.
234
        self.assertEqual(2, 
1530.1.3 by Robert Collins
transport implementations now tested consistently.
235
            t.put_multi([('dir_a/a', StringIO('contents of dir_a/a')),
1563.2.3 by Robert Collins
Change the return signature of transport.append and append_multi to return the length of the pre-append content.
236
                         ('dir_b/b', StringIO('contents of dir_b/b'))]))
1530.1.3 by Robert Collins
transport implementations now tested consistently.
237
        self.check_transport_contents('contents of dir_a/a', t, 'dir_a/a')
238
        self.check_transport_contents('contents of dir_b/b', t, 'dir_b/b')
239
1530.1.4 by Robert Collins
integrate Memory tests into transport interface tests.
240
        # mkdir of a dir with an absent parent
241
        self.assertRaises(NoSuchFile, t.mkdir, 'missing/dir')
242
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
243
    def test_mkdir_permissions(self):
244
        t = self.get_transport()
245
        if t.is_readonly():
246
            return
247
        # Test mkdir with a mode
248
        t.mkdir('dmode755', mode=0755)
1530.1.21 by Robert Collins
Review feedback fixes.
249
        self.assertTransportMode(t, 'dmode755', 0755)
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
250
        t.mkdir('dmode555', mode=0555)
1530.1.21 by Robert Collins
Review feedback fixes.
251
        self.assertTransportMode(t, 'dmode555', 0555)
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
252
        t.mkdir('dmode777', mode=0777)
1530.1.21 by Robert Collins
Review feedback fixes.
253
        self.assertTransportMode(t, 'dmode777', 0777)
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
254
        t.mkdir('dmode700', mode=0700)
1530.1.21 by Robert Collins
Review feedback fixes.
255
        self.assertTransportMode(t, 'dmode700', 0700)
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
256
        # TODO: jam 20051215 test mkdir_multi with a mode
257
        t.mkdir_multi(['mdmode755'], mode=0755)
1530.1.21 by Robert Collins
Review feedback fixes.
258
        self.assertTransportMode(t, 'mdmode755', 0755)
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
259
1530.1.3 by Robert Collins
transport implementations now tested consistently.
260
    def test_copy_to(self):
1534.4.21 by Robert Collins
Extend the copy_to tests to smoke test server-to-same-server copies to catch optimised code paths, and fix sftps optimised code path by removing dead code.
261
        # FIXME: test:   same server to same server (partly done)
262
        # same protocol two servers
263
        # and    different protocols (done for now except for MemoryTransport.
264
        # - RBC 20060122
1530.1.3 by Robert Collins
transport implementations now tested consistently.
265
        from bzrlib.transport.memory import MemoryTransport
1534.4.21 by Robert Collins
Extend the copy_to tests to smoke test server-to-same-server copies to catch optimised code paths, and fix sftps optimised code path by removing dead code.
266
267
        def simple_copy_files(transport_from, transport_to):
268
            files = ['a', 'b', 'c', 'd']
269
            self.build_tree(files, transport=transport_from)
1563.2.3 by Robert Collins
Change the return signature of transport.append and append_multi to return the length of the pre-append content.
270
            self.assertEqual(4, transport_from.copy_to(files, transport_to))
1534.4.21 by Robert Collins
Extend the copy_to tests to smoke test server-to-same-server copies to catch optimised code paths, and fix sftps optimised code path by removing dead code.
271
            for f in files:
272
                self.check_transport_contents(transport_to.get(f).read(),
273
                                              transport_from, f)
274
1530.1.3 by Robert Collins
transport implementations now tested consistently.
275
        t = self.get_transport()
276
        temp_transport = MemoryTransport('memory:/')
1534.4.21 by Robert Collins
Extend the copy_to tests to smoke test server-to-same-server copies to catch optimised code paths, and fix sftps optimised code path by removing dead code.
277
        simple_copy_files(t, temp_transport)
278
        if not t.is_readonly():
279
            t.mkdir('copy_to_simple')
280
            t2 = t.clone('copy_to_simple')
281
            simple_copy_files(t, t2)
1530.1.3 by Robert Collins
transport implementations now tested consistently.
282
283
284
        # Test that copying into a missing directory raises
285
        # NoSuchFile
286
        if t.is_readonly():
1530.1.21 by Robert Collins
Review feedback fixes.
287
            self.build_tree(['e/', 'e/f'])
1530.1.3 by Robert Collins
transport implementations now tested consistently.
288
        else:
289
            t.mkdir('e')
290
            t.put('e/f', StringIO('contents of e'))
291
        self.assertRaises(NoSuchFile, t.copy_to, ['e/f'], temp_transport)
292
        temp_transport.mkdir('e')
293
        t.copy_to(['e/f'], temp_transport)
294
295
        del temp_transport
296
        temp_transport = MemoryTransport('memory:/')
297
298
        files = ['a', 'b', 'c', 'd']
299
        t.copy_to(iter(files), temp_transport)
300
        for f in files:
301
            self.check_transport_contents(temp_transport.get(f).read(),
302
                                          t, f)
303
        del temp_transport
304
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
305
        for mode in (0666, 0644, 0600, 0400):
306
            temp_transport = MemoryTransport("memory:/")
307
            t.copy_to(files, temp_transport, mode=mode)
308
            for f in files:
1530.1.21 by Robert Collins
Review feedback fixes.
309
                self.assertTransportMode(temp_transport, f, mode)
1530.1.16 by Robert Collins
Move mkdir and copy_to permissions tests to test_transport_impleentation.
310
1530.1.3 by Robert Collins
transport implementations now tested consistently.
311
    def test_append(self):
312
        t = self.get_transport()
313
314
        if t.is_readonly():
315
            open('a', 'wb').write('diff\ncontents for\na\n')
316
            open('b', 'wb').write('contents\nfor b\n')
317
        else:
318
            t.put_multi([
319
                    ('a', StringIO('diff\ncontents for\na\n')),
320
                    ('b', StringIO('contents\nfor b\n'))
321
                    ])
322
323
        if t.is_readonly():
324
            self.assertRaises(TransportNotPossible,
325
                    t.append, 'a', 'add\nsome\nmore\ncontents\n')
326
            _append('a', StringIO('add\nsome\nmore\ncontents\n'))
327
        else:
1563.2.3 by Robert Collins
Change the return signature of transport.append and append_multi to return the length of the pre-append content.
328
            self.assertEqual(20,
329
                t.append('a', StringIO('add\nsome\nmore\ncontents\n')))
1530.1.3 by Robert Collins
transport implementations now tested consistently.
330
331
        self.check_transport_contents(
332
            'diff\ncontents for\na\nadd\nsome\nmore\ncontents\n',
333
            t, 'a')
334
335
        if t.is_readonly():
336
            self.assertRaises(TransportNotPossible,
337
                    t.append_multi,
338
                        [('a', 'and\nthen\nsome\nmore\n'),
339
                         ('b', 'some\nmore\nfor\nb\n')])
340
            _append('a', StringIO('and\nthen\nsome\nmore\n'))
341
            _append('b', StringIO('some\nmore\nfor\nb\n'))
342
        else:
1563.2.3 by Robert Collins
Change the return signature of transport.append and append_multi to return the length of the pre-append content.
343
            self.assertEqual((43, 15), 
344
                t.append_multi([('a', StringIO('and\nthen\nsome\nmore\n')),
345
                                ('b', StringIO('some\nmore\nfor\nb\n'))]))
1530.1.3 by Robert Collins
transport implementations now tested consistently.
346
        self.check_transport_contents(
347
            'diff\ncontents for\na\n'
348
            'add\nsome\nmore\ncontents\n'
349
            'and\nthen\nsome\nmore\n',
350
            t, 'a')
351
        self.check_transport_contents(
352
                'contents\nfor b\n'
353
                'some\nmore\nfor\nb\n',
354
                t, 'b')
355
356
        if t.is_readonly():
357
            _append('a', StringIO('a little bit more\n'))
358
            _append('b', StringIO('from an iterator\n'))
359
        else:
1563.2.3 by Robert Collins
Change the return signature of transport.append and append_multi to return the length of the pre-append content.
360
            self.assertEqual((62, 31),
361
                t.append_multi(iter([('a', StringIO('a little bit more\n')),
362
                                     ('b', StringIO('from an iterator\n'))])))
1530.1.3 by Robert Collins
transport implementations now tested consistently.
363
        self.check_transport_contents(
364
            'diff\ncontents for\na\n'
365
            'add\nsome\nmore\ncontents\n'
366
            'and\nthen\nsome\nmore\n'
367
            'a little bit more\n',
368
            t, 'a')
369
        self.check_transport_contents(
370
                'contents\nfor b\n'
371
                'some\nmore\nfor\nb\n'
372
                'from an iterator\n',
373
                t, 'b')
374
375
        if t.is_readonly():
376
            _append('c', StringIO('some text\nfor a missing file\n'))
377
            _append('a', StringIO('some text in a\n'))
378
            _append('d', StringIO('missing file r\n'))
379
        else:
1563.2.3 by Robert Collins
Change the return signature of transport.append and append_multi to return the length of the pre-append content.
380
            self.assertEqual(0,
381
                t.append('c', StringIO('some text\nfor a missing file\n')))
382
            self.assertEqual((80, 0),
383
                t.append_multi([('a', StringIO('some text in a\n')),
384
                                ('d', StringIO('missing file r\n'))]))
1530.1.3 by Robert Collins
transport implementations now tested consistently.
385
        self.check_transport_contents(
386
            'diff\ncontents for\na\n'
387
            'add\nsome\nmore\ncontents\n'
388
            'and\nthen\nsome\nmore\n'
389
            'a little bit more\n'
390
            'some text in a\n',
391
            t, 'a')
392
        self.check_transport_contents('some text\nfor a missing file\n',
393
                                      t, 'c')
394
        self.check_transport_contents('missing file r\n', t, 'd')
1530.1.4 by Robert Collins
integrate Memory tests into transport interface tests.
395
        
396
        # a file with no parent should fail..
397
        if not t.is_readonly():
398
            self.assertRaises(NoSuchFile,
399
                              t.append, 'missing/path', 
400
                              StringIO('content'))
1530.1.3 by Robert Collins
transport implementations now tested consistently.
401
402
    def test_append_file(self):
403
        t = self.get_transport()
404
405
        contents = [
406
            ('f1', StringIO('this is a string\nand some more stuff\n')),
407
            ('f2', StringIO('here is some text\nand a bit more\n')),
408
            ('f3', StringIO('some text for the\nthird file created\n')),
409
            ('f4', StringIO('this is a string\nand some more stuff\n')),
410
            ('f5', StringIO('here is some text\nand a bit more\n')),
411
            ('f6', StringIO('some text for the\nthird file created\n'))
412
        ]
413
        
414
        if t.is_readonly():
415
            for f, val in contents:
416
                open(f, 'wb').write(val.read())
417
        else:
418
            t.put_multi(contents)
419
420
        a1 = StringIO('appending to\none\n')
421
        if t.is_readonly():
422
            _append('f1', a1)
423
        else:
424
            t.append('f1', a1)
425
426
        del a1
427
428
        self.check_transport_contents(
429
                'this is a string\nand some more stuff\n'
430
                'appending to\none\n',
431
                t, 'f1')
432
433
        a2 = StringIO('adding more\ntext to two\n')
434
        a3 = StringIO('some garbage\nto put in three\n')
435
436
        if t.is_readonly():
437
            _append('f2', a2)
438
            _append('f3', a3)
439
        else:
440
            t.append_multi([('f2', a2), ('f3', a3)])
441
442
        del a2, a3
443
444
        self.check_transport_contents(
445
                'here is some text\nand a bit more\n'
446
                'adding more\ntext to two\n',
447
                t, 'f2')
448
        self.check_transport_contents( 
449
                'some text for the\nthird file created\n'
450
                'some garbage\nto put in three\n',
451
                t, 'f3')
452
453
        # Test that an actual file object can be used with put
454
        a4 = t.get('f1')
455
        if t.is_readonly():
456
            _append('f4', a4)
457
        else:
458
            t.append('f4', a4)
459
460
        del a4
461
462
        self.check_transport_contents(
463
                'this is a string\nand some more stuff\n'
464
                'this is a string\nand some more stuff\n'
465
                'appending to\none\n',
466
                t, 'f4')
467
468
        a5 = t.get('f2')
469
        a6 = t.get('f3')
470
        if t.is_readonly():
471
            _append('f5', a5)
472
            _append('f6', a6)
473
        else:
474
            t.append_multi([('f5', a5), ('f6', a6)])
475
476
        del a5, a6
477
478
        self.check_transport_contents(
479
                'here is some text\nand a bit more\n'
480
                'here is some text\nand a bit more\n'
481
                'adding more\ntext to two\n',
482
                t, 'f5')
483
        self.check_transport_contents(
484
                'some text for the\nthird file created\n'
485
                'some text for the\nthird file created\n'
486
                'some garbage\nto put in three\n',
487
                t, 'f6')
488
489
        a5 = t.get('f2')
490
        a6 = t.get('f2')
491
        a7 = t.get('f3')
492
        if t.is_readonly():
493
            _append('c', a5)
494
            _append('a', a6)
495
            _append('d', a7)
496
        else:
497
            t.append('c', a5)
498
            t.append_multi([('a', a6), ('d', a7)])
499
        del a5, a6, a7
500
        self.check_transport_contents(t.get('f2').read(), t, 'c')
501
        self.check_transport_contents(t.get('f3').read(), t, 'd')
502
503
    def test_delete(self):
504
        # TODO: Test Transport.delete
505
        t = self.get_transport()
506
507
        # Not much to do with a readonly transport
508
        if t.is_readonly():
1534.4.9 by Robert Collins
Add a readonly decorator for transports.
509
            self.assertRaises(TransportNotPossible, t.delete, 'missing')
1530.1.3 by Robert Collins
transport implementations now tested consistently.
510
            return
511
512
        t.put('a', StringIO('a little bit of text\n'))
513
        self.failUnless(t.has('a'))
514
        t.delete('a')
515
        self.failIf(t.has('a'))
516
517
        self.assertRaises(NoSuchFile, t.delete, 'a')
518
519
        t.put('a', StringIO('a text\n'))
520
        t.put('b', StringIO('b text\n'))
521
        t.put('c', StringIO('c text\n'))
522
        self.assertEqual([True, True, True],
523
                list(t.has_multi(['a', 'b', 'c'])))
524
        t.delete_multi(['a', 'c'])
525
        self.assertEqual([False, True, False],
526
                list(t.has_multi(['a', 'b', 'c'])))
527
        self.failIf(t.has('a'))
528
        self.failUnless(t.has('b'))
529
        self.failIf(t.has('c'))
530
531
        self.assertRaises(NoSuchFile,
532
                t.delete_multi, ['a', 'b', 'c'])
533
534
        self.assertRaises(NoSuchFile,
535
                t.delete_multi, iter(['a', 'b', 'c']))
536
537
        t.put('a', StringIO('another a text\n'))
538
        t.put('c', StringIO('another c text\n'))
539
        t.delete_multi(iter(['a', 'b', 'c']))
540
541
        # We should have deleted everything
542
        # SftpServer creates control files in the
543
        # working directory, so we can just do a
544
        # plain "listdir".
545
        # self.assertEqual([], os.listdir('.'))
546
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
547
    def test_rmdir(self):
548
        t = self.get_transport()
549
        # Not much to do with a readonly transport
550
        if t.is_readonly():
551
            self.assertRaises(TransportNotPossible, t.rmdir, 'missing')
552
            return
553
        t.mkdir('adir')
554
        t.mkdir('adir/bdir')
555
        t.rmdir('adir/bdir')
556
        self.assertRaises(NoSuchFile, t.stat, 'adir/bdir')
557
        t.rmdir('adir')
558
        self.assertRaises(NoSuchFile, t.stat, 'adir')
559
1553.5.10 by Martin Pool
New DirectoryNotEmpty exception, and raise this from local and memory
560
    def test_rmdir_not_empty(self):
561
        """Deleting a non-empty directory raises an exception
562
        
563
        sftp (and possibly others) don't give us a specific "directory not
564
        empty" exception -- we can just see that the operation failed.
565
        """
566
        t = self.get_transport()
567
        if t.is_readonly():
568
            return
569
        t.mkdir('adir')
570
        t.mkdir('adir/bdir')
571
        self.assertRaises(PathError, t.rmdir, 'adir')
572
1553.5.13 by Martin Pool
New Transport.rename that mustn't overwrite
573
    def test_rename_dir_succeeds(self):
574
        t = self.get_transport()
575
        if t.is_readonly():
576
            raise TestSkipped("transport is readonly")
577
        t.mkdir('adir')
578
        t.mkdir('adir/asubdir')
579
        t.rename('adir', 'bdir')
580
        self.assertTrue(t.has('bdir/asubdir'))
581
        self.assertFalse(t.has('adir'))
582
583
    def test_rename_dir_nonempty(self):
584
        """Attempting to replace a nonemtpy directory should fail"""
585
        t = self.get_transport()
586
        if t.is_readonly():
587
            raise TestSkipped("transport is readonly")
588
        t.mkdir('adir')
589
        t.mkdir('adir/asubdir')
590
        t.mkdir('bdir')
591
        t.mkdir('bdir/bsubdir')
592
        self.assertRaises(PathError, t.rename, 'bdir', 'adir')
593
        # nothing was changed so it should still be as before
594
        self.assertTrue(t.has('bdir/bsubdir'))
595
        self.assertFalse(t.has('adir/bdir'))
596
        self.assertFalse(t.has('adir/bsubdir'))
597
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
598
    def test_delete_tree(self):
599
        t = self.get_transport()
600
601
        # Not much to do with a readonly transport
602
        if t.is_readonly():
603
            self.assertRaises(TransportNotPossible, t.delete_tree, 'missing')
604
            return
605
606
        # and does it like listing ?
607
        t.mkdir('adir')
608
        try:
609
            t.delete_tree('adir')
610
        except TransportNotPossible:
611
            # ok, this transport does not support delete_tree
612
            return
613
        
614
        # did it delete that trivial case?
615
        self.assertRaises(NoSuchFile, t.stat, 'adir')
616
617
        self.build_tree(['adir/',
618
                         'adir/file', 
619
                         'adir/subdir/', 
620
                         'adir/subdir/file', 
621
                         'adir/subdir2/',
622
                         'adir/subdir2/file',
623
                         ], transport=t)
624
625
        t.delete_tree('adir')
626
        # adir should be gone now.
627
        self.assertRaises(NoSuchFile, t.stat, 'adir')
628
1530.1.3 by Robert Collins
transport implementations now tested consistently.
629
    def test_move(self):
630
        t = self.get_transport()
631
632
        if t.is_readonly():
633
            return
634
635
        # TODO: I would like to use os.listdir() to
636
        # make sure there are no extra files, but SftpServer
637
        # creates control files in the working directory
638
        # perhaps all of this could be done in a subdirectory
639
640
        t.put('a', StringIO('a first file\n'))
641
        self.assertEquals([True, False], list(t.has_multi(['a', 'b'])))
642
643
        t.move('a', 'b')
644
        self.failUnless(t.has('b'))
645
        self.failIf(t.has('a'))
646
647
        self.check_transport_contents('a first file\n', t, 'b')
648
        self.assertEquals([False, True], list(t.has_multi(['a', 'b'])))
649
650
        # Overwrite a file
651
        t.put('c', StringIO('c this file\n'))
652
        t.move('c', 'b')
653
        self.failIf(t.has('c'))
654
        self.check_transport_contents('c this file\n', t, 'b')
655
656
        # TODO: Try to write a test for atomicity
657
        # TODO: Test moving into a non-existant subdirectory
658
        # TODO: Test Transport.move_multi
659
660
    def test_copy(self):
661
        t = self.get_transport()
662
663
        if t.is_readonly():
664
            return
665
666
        t.put('a', StringIO('a file\n'))
667
        t.copy('a', 'b')
668
        self.check_transport_contents('a file\n', t, 'b')
669
670
        self.assertRaises(NoSuchFile, t.copy, 'c', 'd')
671
        os.mkdir('c')
672
        # What should the assert be if you try to copy a
673
        # file over a directory?
674
        #self.assertRaises(Something, t.copy, 'a', 'c')
675
        t.put('d', StringIO('text in d\n'))
676
        t.copy('d', 'b')
677
        self.check_transport_contents('text in d\n', t, 'b')
678
679
        # TODO: test copy_multi
680
681
    def test_connection_error(self):
682
        """ConnectionError is raised when connection is impossible"""
1530.1.9 by Robert Collins
Test bogus urls with http in the new infrastructure.
683
        try:
684
            url = self._server.get_bogus_url()
685
        except NotImplementedError:
686
            raise TestSkipped("Transport %s has no bogus URL support." %
687
                              self._server.__class__)
688
        t = bzrlib.transport.get_transport(url)
1530.1.3 by Robert Collins
transport implementations now tested consistently.
689
        try:
690
            t.get('.bzr/branch')
691
        except (ConnectionError, NoSuchFile), e:
692
            pass
693
        except (Exception), e:
694
            self.failIf(True, 'Wrong exception thrown: %s' % e)
695
        else:
696
            self.failIf(True, 'Did not get the expected exception.')
697
698
    def test_stat(self):
699
        # TODO: Test stat, just try once, and if it throws, stop testing
700
        from stat import S_ISDIR, S_ISREG
701
702
        t = self.get_transport()
703
704
        try:
705
            st = t.stat('.')
706
        except TransportNotPossible, e:
707
            # This transport cannot stat
708
            return
709
710
        paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e']
711
        sizes = [14, 0, 16, 0, 18] 
712
        self.build_tree(paths, transport=t)
713
714
        for path, size in zip(paths, sizes):
715
            st = t.stat(path)
716
            if path.endswith('/'):
717
                self.failUnless(S_ISDIR(st.st_mode))
718
                # directory sizes are meaningless
719
            else:
720
                self.failUnless(S_ISREG(st.st_mode))
721
                self.assertEqual(size, st.st_size)
722
723
        remote_stats = list(t.stat_multi(paths))
724
        remote_iter_stats = list(t.stat_multi(iter(paths)))
725
726
        self.assertRaises(NoSuchFile, t.stat, 'q')
727
        self.assertRaises(NoSuchFile, t.stat, 'b/a')
728
729
        self.assertListRaises(NoSuchFile, t.stat_multi, ['a', 'c', 'd'])
730
        self.assertListRaises(NoSuchFile, t.stat_multi, iter(['a', 'c', 'd']))
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
731
        self.build_tree(['subdir/', 'subdir/file'], transport=t)
732
        subdir = t.clone('subdir')
733
        subdir.stat('./file')
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
734
        subdir.stat('.')
1530.1.3 by Robert Collins
transport implementations now tested consistently.
735
736
    def test_list_dir(self):
737
        # TODO: Test list_dir, just try once, and if it throws, stop testing
738
        t = self.get_transport()
739
        
740
        if not t.listable():
741
            self.assertRaises(TransportNotPossible, t.list_dir, '.')
742
            return
743
744
        def sorted_list(d):
745
            l = list(t.list_dir(d))
746
            l.sort()
747
            return l
748
749
        # SftpServer creates control files in the working directory
750
        # so lets move down a directory to avoid those.
1534.4.9 by Robert Collins
Add a readonly decorator for transports.
751
        if not t.is_readonly():
752
            t.mkdir('wd')
753
        else:
754
            os.mkdir('wd')
1530.1.3 by Robert Collins
transport implementations now tested consistently.
755
        t = t.clone('wd')
756
757
        self.assertEqual([], sorted_list(u'.'))
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
758
        # c2 is precisely one letter longer than c here to test that
759
        # suffixing is not confused.
1534.4.9 by Robert Collins
Add a readonly decorator for transports.
760
        if not t.is_readonly():
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
761
            self.build_tree(['a', 'b', 'c/', 'c/d', 'c/e', 'c2/'], transport=t)
1534.4.9 by Robert Collins
Add a readonly decorator for transports.
762
        else:
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
763
            self.build_tree(['wd/a', 'wd/b', 'wd/c/', 'wd/c/d', 'wd/c/e', 'wd/c2/'])
1530.1.3 by Robert Collins
transport implementations now tested consistently.
764
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
765
        self.assertEqual([u'a', u'b', u'c', u'c2'], sorted_list(u'.'))
1530.1.3 by Robert Collins
transport implementations now tested consistently.
766
        self.assertEqual([u'd', u'e'], sorted_list(u'c'))
767
1534.4.9 by Robert Collins
Add a readonly decorator for transports.
768
        if not t.is_readonly():
769
            t.delete('c/d')
770
            t.delete('b')
771
        else:
772
            os.unlink('wd/c/d')
773
            os.unlink('wd/b')
774
            
1534.4.15 by Robert Collins
Remove shutil dependency in upgrade - create a delete_tree method for transports.
775
        self.assertEqual([u'a', u'c', u'c2'], sorted_list('.'))
1530.1.3 by Robert Collins
transport implementations now tested consistently.
776
        self.assertEqual([u'e'], sorted_list(u'c'))
777
778
        self.assertListRaises(NoSuchFile, t.list_dir, 'q')
779
        self.assertListRaises(NoSuchFile, t.list_dir, 'c/f')
780
        self.assertListRaises(NoSuchFile, t.list_dir, 'a')
781
782
    def test_clone(self):
783
        # TODO: Test that clone moves up and down the filesystem
784
        t1 = self.get_transport()
785
786
        self.build_tree(['a', 'b/', 'b/c'], transport=t1)
787
788
        self.failUnless(t1.has('a'))
789
        self.failUnless(t1.has('b/c'))
790
        self.failIf(t1.has('c'))
791
792
        t2 = t1.clone('b')
793
        self.assertEqual(t1.base + 'b/', t2.base)
794
795
        self.failUnless(t2.has('c'))
796
        self.failIf(t2.has('a'))
797
798
        t3 = t2.clone('..')
799
        self.failUnless(t3.has('a'))
800
        self.failIf(t3.has('c'))
801
802
        self.failIf(t1.has('b/d'))
803
        self.failIf(t2.has('d'))
804
        self.failIf(t3.has('b/d'))
805
806
        if t1.is_readonly():
807
            open('b/d', 'wb').write('newfile\n')
808
        else:
809
            t2.put('d', StringIO('newfile\n'))
810
811
        self.failUnless(t1.has('b/d'))
812
        self.failUnless(t2.has('d'))
813
        self.failUnless(t3.has('b/d'))
814
815
    def test_relpath(self):
816
        t = self.get_transport()
817
        self.assertEqual('', t.relpath(t.base))
818
        # base ends with /
819
        self.assertEqual('', t.relpath(t.base[:-1]))
820
        # subdirs which dont exist should still give relpaths.
821
        self.assertEqual('foo', t.relpath(t.base + 'foo'))
822
        # trailing slash should be the same.
823
        self.assertEqual('foo', t.relpath(t.base + 'foo/'))
824
825
    def test_abspath(self):
826
        # smoke test for abspath. Corner cases for backends like unix fs's
827
        # that have aliasing problems like symlinks should go in backend
828
        # specific test cases.
829
        transport = self.get_transport()
830
        self.assertEqual(transport.base + 'relpath',
831
                         transport.abspath('relpath'))
832
833
    def test_iter_files_recursive(self):
1530.1.4 by Robert Collins
integrate Memory tests into transport interface tests.
834
        transport = self.get_transport()
835
        if not transport.listable():
1553.5.13 by Martin Pool
New Transport.rename that mustn't overwrite
836
            self.assertRaises(TransportNotPossible,
1530.1.4 by Robert Collins
integrate Memory tests into transport interface tests.
837
                              transport.iter_files_recursive)
838
            return
1553.5.13 by Martin Pool
New Transport.rename that mustn't overwrite
839
        self.build_tree(['isolated/',
1530.1.4 by Robert Collins
integrate Memory tests into transport interface tests.
840
                         'isolated/dir/',
841
                         'isolated/dir/foo',
842
                         'isolated/dir/bar',
843
                         'isolated/bar'],
844
                        transport=transport)
1530.1.3 by Robert Collins
transport implementations now tested consistently.
845
        paths = set(transport.iter_files_recursive())
1553.5.13 by Martin Pool
New Transport.rename that mustn't overwrite
846
        # nb the directories are not converted
847
        self.assertEqual(paths,
848
                    set(['isolated/dir/foo',
849
                         'isolated/dir/bar',
850
                         'isolated/bar']))
851
        sub_transport = transport.clone('isolated')
852
        paths = set(sub_transport.iter_files_recursive())
1530.1.3 by Robert Collins
transport implementations now tested consistently.
853
        self.assertEqual(set(['dir/foo', 'dir/bar', 'bar']), paths)
1534.4.26 by Robert Collins
Move working tree initialisation out from Branch.initialize, deprecated Branch.initialize to Branch.create.
854
855
    def test_connect_twice_is_same_content(self):
856
        # check that our server (whatever it is) is accessable reliably
857
        # via get_transport and multiple connections share content.
858
        transport = self.get_transport()
859
        if transport.is_readonly():
860
            return
861
        transport.put('foo', StringIO('bar'))
862
        transport2 = self.get_transport()
863
        self.check_transport_contents('bar', transport2, 'foo')
864
        # its base should be usable.
865
        transport2 = bzrlib.transport.get_transport(transport.base)
866
        self.check_transport_contents('bar', transport2, 'foo')
867
868
        # now opening at a relative url should give use a sane result:
869
        transport.mkdir('newdir')
870
        transport2 = bzrlib.transport.get_transport(transport.base + "newdir")
871
        transport2 = transport2.clone('..')
872
        self.check_transport_contents('bar', transport2, 'foo')
873
874
    def test_lock_write(self):
875
        transport = self.get_transport()
876
        if transport.is_readonly():
877
            self.assertRaises(TransportNotPossible, transport.lock_write, 'foo')
878
            return
879
        transport.put('lock', StringIO())
880
        lock = transport.lock_write('lock')
881
        # TODO make this consistent on all platforms:
882
        # self.assertRaises(LockError, transport.lock_write, 'lock')
883
        lock.unlock()
884
885
    def test_lock_read(self):
886
        transport = self.get_transport()
887
        if transport.is_readonly():
888
            file('lock', 'w').close()
889
        else:
890
            transport.put('lock', StringIO())
891
        lock = transport.lock_read('lock')
892
        # TODO make this consistent on all platforms:
893
        # self.assertRaises(LockError, transport.lock_read, 'lock')
894
        lock.unlock()