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