~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transport.py

  • Committer: Mark Hammond
  • Date: 2009-01-12 01:55:34 UTC
  • mto: (3995.8.2 prepare-1.12)
  • mto: This revision was merged to the branch mainline in revision 4007.
  • Revision ID: mhammond@skippinet.com.au-20090112015534-yfxg50p7mpds9j4v
Include all .html files from the tortoise doc directory.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Canonical Ltd
 
1
# Copyright (C) 2004, 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
18
from cStringIO import StringIO
19
 
import os
20
 
import subprocess
21
 
import sys
22
 
import threading
23
19
 
 
20
import bzrlib
24
21
from bzrlib import (
25
22
    errors,
26
23
    osutils,
27
 
    tests,
28
 
    transport as _mod_transport,
29
24
    urlutils,
30
25
    )
31
 
from bzrlib.transport import (
32
 
    fakenfs,
33
 
    memory,
34
 
    readonly,
35
 
    )
36
26
from bzrlib.errors import (DependencyNotPresent,
37
27
                           FileExists,
38
28
                           InvalidURLJoin,
41
31
                           ReadError,
42
32
                           UnsupportedProtocol,
43
33
                           )
44
 
from bzrlib.tests import features, TestCase, TestCaseInTempDir
 
34
from bzrlib.tests import TestCase, TestCaseInTempDir
45
35
from bzrlib.transport import (_clear_protocol_handlers,
46
36
                              _CoalescedOffset,
47
37
                              ConnectedTransport,
55
45
                              Transport,
56
46
                              )
57
47
from bzrlib.transport.chroot import ChrootServer
 
48
from bzrlib.transport.memory import MemoryTransport
58
49
from bzrlib.transport.local import (LocalTransport,
59
50
                                    EmulatedWin32LocalTransport)
60
 
from bzrlib.transport.pathfilter import PathFilteringServer
61
51
 
62
52
 
63
53
# TODO: Should possibly split transport-specific tests into their own files.
90
80
            register_lazy_transport('bar', 'bzrlib.tests.test_transport',
91
81
                                    'TestTransport.SampleHandler')
92
82
            self.assertEqual([SampleHandler.__module__,
93
 
                              'bzrlib.transport.chroot',
94
 
                              'bzrlib.transport.pathfilter'],
 
83
                              'bzrlib.transport.chroot'],
95
84
                             _get_transport_modules())
96
85
        finally:
97
86
            _set_protocol_handlers(handlers)
118
107
        finally:
119
108
            # restore original values
120
109
            _set_protocol_handlers(saved_handlers)
121
 
 
 
110
            
122
111
    def test_transport_fallback(self):
123
112
        """Transport with missing dependency causes no error"""
124
113
        saved_handlers = _get_protocol_handlers()
134
123
        finally:
135
124
            _set_protocol_handlers(saved_handlers)
136
125
 
137
 
    def test_ssh_hints(self):
138
 
        """Transport ssh:// should raise an error pointing out bzr+ssh://"""
139
 
        try:
140
 
            get_transport('ssh://fooserver/foo')
141
 
        except UnsupportedProtocol, e:
142
 
            e_str = str(e)
143
 
            self.assertEquals('Unsupported protocol'
144
 
                              ' for url "ssh://fooserver/foo":'
145
 
                              ' bzr supports bzr+ssh to operate over ssh, use "bzr+ssh://fooserver/foo".',
146
 
                              str(e))
147
 
        else:
148
 
            self.fail('Did not raise UnsupportedProtocol')
149
 
 
150
126
    def test_LateReadError(self):
151
127
        """The LateReadError helper should raise on read()."""
152
128
        a_file = LateReadError('a path')
170
146
 
171
147
    def test_local_abspath_non_local_transport(self):
172
148
        # the base implementation should throw
173
 
        t = memory.MemoryTransport()
 
149
        t = MemoryTransport()
174
150
        e = self.assertRaises(errors.NotLocalUrl, t.local_abspath, 't')
175
151
        self.assertEqual('memory:///t is not a local path.', str(e))
176
152
 
260
236
                   max_size=1*1024*1024*1024)
261
237
 
262
238
 
263
 
class TestMemoryServer(TestCase):
264
 
 
265
 
    def test_create_server(self):
266
 
        server = memory.MemoryServer()
267
 
        server.start_server()
268
 
        url = server.get_url()
269
 
        self.assertTrue(url in _mod_transport.transport_list_registry)
270
 
        t = _mod_transport.get_transport(url)
271
 
        del t
272
 
        server.stop_server()
273
 
        self.assertFalse(url in _mod_transport.transport_list_registry)
274
 
        self.assertRaises(errors.UnsupportedProtocol,
275
 
                          _mod_transport.get_transport, url)
276
 
 
277
 
 
278
239
class TestMemoryTransport(TestCase):
279
240
 
280
241
    def test_get_transport(self):
281
 
        memory.MemoryTransport()
 
242
        MemoryTransport()
282
243
 
283
244
    def test_clone(self):
284
 
        transport = memory.MemoryTransport()
285
 
        self.assertTrue(isinstance(transport, memory.MemoryTransport))
 
245
        transport = MemoryTransport()
 
246
        self.assertTrue(isinstance(transport, MemoryTransport))
286
247
        self.assertEqual("memory:///", transport.clone("/").base)
287
248
 
288
249
    def test_abspath(self):
289
 
        transport = memory.MemoryTransport()
 
250
        transport = MemoryTransport()
290
251
        self.assertEqual("memory:///relpath", transport.abspath('relpath'))
291
252
 
292
253
    def test_abspath_of_root(self):
293
 
        transport = memory.MemoryTransport()
 
254
        transport = MemoryTransport()
294
255
        self.assertEqual("memory:///", transport.base)
295
256
        self.assertEqual("memory:///", transport.abspath('/'))
296
257
 
297
258
    def test_abspath_of_relpath_starting_at_root(self):
298
 
        transport = memory.MemoryTransport()
 
259
        transport = MemoryTransport()
299
260
        self.assertEqual("memory:///foo", transport.abspath('/foo'))
300
261
 
301
262
    def test_append_and_get(self):
302
 
        transport = memory.MemoryTransport()
 
263
        transport = MemoryTransport()
303
264
        transport.append_bytes('path', 'content')
304
265
        self.assertEqual(transport.get('path').read(), 'content')
305
266
        transport.append_file('path', StringIO('content'))
306
267
        self.assertEqual(transport.get('path').read(), 'contentcontent')
307
268
 
308
269
    def test_put_and_get(self):
309
 
        transport = memory.MemoryTransport()
 
270
        transport = MemoryTransport()
310
271
        transport.put_file('path', StringIO('content'))
311
272
        self.assertEqual(transport.get('path').read(), 'content')
312
273
        transport.put_bytes('path', 'content')
313
274
        self.assertEqual(transport.get('path').read(), 'content')
314
275
 
315
276
    def test_append_without_dir_fails(self):
316
 
        transport = memory.MemoryTransport()
 
277
        transport = MemoryTransport()
317
278
        self.assertRaises(NoSuchFile,
318
279
                          transport.append_bytes, 'dir/path', 'content')
319
280
 
320
281
    def test_put_without_dir_fails(self):
321
 
        transport = memory.MemoryTransport()
 
282
        transport = MemoryTransport()
322
283
        self.assertRaises(NoSuchFile,
323
284
                          transport.put_file, 'dir/path', StringIO('content'))
324
285
 
325
286
    def test_get_missing(self):
326
 
        transport = memory.MemoryTransport()
 
287
        transport = MemoryTransport()
327
288
        self.assertRaises(NoSuchFile, transport.get, 'foo')
328
289
 
329
290
    def test_has_missing(self):
330
 
        transport = memory.MemoryTransport()
 
291
        transport = MemoryTransport()
331
292
        self.assertEquals(False, transport.has('foo'))
332
293
 
333
294
    def test_has_present(self):
334
 
        transport = memory.MemoryTransport()
 
295
        transport = MemoryTransport()
335
296
        transport.append_bytes('foo', 'content')
336
297
        self.assertEquals(True, transport.has('foo'))
337
298
 
338
299
    def test_list_dir(self):
339
 
        transport = memory.MemoryTransport()
 
300
        transport = MemoryTransport()
340
301
        transport.put_bytes('foo', 'content')
341
302
        transport.mkdir('dir')
342
303
        transport.put_bytes('dir/subfoo', 'content')
346
307
        self.assertEquals(['subfoo'], sorted(transport.list_dir('dir')))
347
308
 
348
309
    def test_mkdir(self):
349
 
        transport = memory.MemoryTransport()
 
310
        transport = MemoryTransport()
350
311
        transport.mkdir('dir')
351
312
        transport.append_bytes('dir/path', 'content')
352
313
        self.assertEqual(transport.get('dir/path').read(), 'content')
353
314
 
354
315
    def test_mkdir_missing_parent(self):
355
 
        transport = memory.MemoryTransport()
 
316
        transport = MemoryTransport()
356
317
        self.assertRaises(NoSuchFile,
357
318
                          transport.mkdir, 'dir/dir')
358
319
 
359
320
    def test_mkdir_twice(self):
360
 
        transport = memory.MemoryTransport()
 
321
        transport = MemoryTransport()
361
322
        transport.mkdir('dir')
362
323
        self.assertRaises(FileExists, transport.mkdir, 'dir')
363
324
 
364
325
    def test_parameters(self):
365
 
        transport = memory.MemoryTransport()
 
326
        transport = MemoryTransport()
366
327
        self.assertEqual(True, transport.listable())
367
328
        self.assertEqual(False, transport.is_readonly())
368
329
 
369
330
    def test_iter_files_recursive(self):
370
 
        transport = memory.MemoryTransport()
 
331
        transport = MemoryTransport()
371
332
        transport.mkdir('dir')
372
333
        transport.put_bytes('dir/foo', 'content')
373
334
        transport.put_bytes('dir/bar', 'content')
376
337
        self.assertEqual(set(['dir/foo', 'dir/bar', 'bar']), paths)
377
338
 
378
339
    def test_stat(self):
379
 
        transport = memory.MemoryTransport()
 
340
        transport = MemoryTransport()
380
341
        transport.put_bytes('foo', 'content')
381
342
        transport.put_bytes('bar', 'phowar')
382
343
        self.assertEqual(7, transport.stat('foo').st_size)
389
350
    def test_abspath(self):
390
351
        # The abspath is always relative to the chroot_url.
391
352
        server = ChrootServer(get_transport('memory:///foo/bar/'))
392
 
        self.start_server(server)
 
353
        server.setUp()
393
354
        transport = get_transport(server.get_url())
394
355
        self.assertEqual(server.get_url(), transport.abspath('/'))
395
356
 
396
357
        subdir_transport = transport.clone('subdir')
397
358
        self.assertEqual(server.get_url(), subdir_transport.abspath('/'))
 
359
        server.tearDown()
398
360
 
399
361
    def test_clone(self):
400
362
        server = ChrootServer(get_transport('memory:///foo/bar/'))
401
 
        self.start_server(server)
 
363
        server.setUp()
402
364
        transport = get_transport(server.get_url())
403
365
        # relpath from root and root path are the same
404
366
        relpath_cloned = transport.clone('foo')
405
367
        abspath_cloned = transport.clone('/foo')
406
368
        self.assertEqual(server, relpath_cloned.server)
407
369
        self.assertEqual(server, abspath_cloned.server)
408
 
 
 
370
        server.tearDown()
 
371
    
409
372
    def test_chroot_url_preserves_chroot(self):
410
373
        """Calling get_transport on a chroot transport's base should produce a
411
374
        transport with exactly the same behaviour as the original chroot
417
380
            new_transport = get_transport(parent_url)
418
381
        """
419
382
        server = ChrootServer(get_transport('memory:///path/subpath'))
420
 
        self.start_server(server)
 
383
        server.setUp()
421
384
        transport = get_transport(server.get_url())
422
385
        new_transport = get_transport(transport.base)
423
386
        self.assertEqual(transport.server, new_transport.server)
424
387
        self.assertEqual(transport.base, new_transport.base)
425
 
 
 
388
        server.tearDown()
 
389
        
426
390
    def test_urljoin_preserves_chroot(self):
427
391
        """Using urlutils.join(url, '..') on a chroot URL should not produce a
428
392
        URL that escapes the intended chroot.
433
397
            new_transport = get_transport(parent_url)
434
398
        """
435
399
        server = ChrootServer(get_transport('memory:///path/'))
436
 
        self.start_server(server)
 
400
        server.setUp()
437
401
        transport = get_transport(server.get_url())
438
402
        self.assertRaises(
439
403
            InvalidURLJoin, urlutils.join, transport.base, '..')
 
404
        server.tearDown()
440
405
 
441
406
 
442
407
class ChrootServerTest(TestCase):
443
408
 
444
409
    def test_construct(self):
445
 
        backing_transport = memory.MemoryTransport()
 
410
        backing_transport = MemoryTransport()
446
411
        server = ChrootServer(backing_transport)
447
412
        self.assertEqual(backing_transport, server.backing_transport)
448
413
 
449
414
    def test_setUp(self):
450
 
        backing_transport = memory.MemoryTransport()
 
415
        backing_transport = MemoryTransport()
451
416
        server = ChrootServer(backing_transport)
452
 
        server.start_server()
453
 
        try:
454
 
            self.assertTrue(server.scheme in _get_protocol_handlers().keys())
455
 
        finally:
456
 
            server.stop_server()
 
417
        server.setUp()
 
418
        self.assertTrue(server.scheme in _get_protocol_handlers().keys())
457
419
 
458
 
    def test_stop_server(self):
459
 
        backing_transport = memory.MemoryTransport()
 
420
    def test_tearDown(self):
 
421
        backing_transport = MemoryTransport()
460
422
        server = ChrootServer(backing_transport)
461
 
        server.start_server()
462
 
        server.stop_server()
 
423
        server.setUp()
 
424
        server.tearDown()
463
425
        self.assertFalse(server.scheme in _get_protocol_handlers().keys())
464
426
 
465
427
    def test_get_url(self):
466
 
        backing_transport = memory.MemoryTransport()
 
428
        backing_transport = MemoryTransport()
467
429
        server = ChrootServer(backing_transport)
468
 
        server.start_server()
469
 
        try:
470
 
            self.assertEqual('chroot-%d:///' % id(server), server.get_url())
471
 
        finally:
472
 
            server.stop_server()
473
 
 
474
 
 
475
 
class PathFilteringDecoratorTransportTest(TestCase):
476
 
    """Pathfilter decoration specific tests."""
477
 
 
478
 
    def test_abspath(self):
479
 
        # The abspath is always relative to the base of the backing transport.
480
 
        server = PathFilteringServer(get_transport('memory:///foo/bar/'),
481
 
            lambda x: x)
482
 
        server.start_server()
483
 
        transport = get_transport(server.get_url())
484
 
        self.assertEqual(server.get_url(), transport.abspath('/'))
485
 
 
486
 
        subdir_transport = transport.clone('subdir')
487
 
        self.assertEqual(server.get_url(), subdir_transport.abspath('/'))
488
 
        server.stop_server()
489
 
 
490
 
    def make_pf_transport(self, filter_func=None):
491
 
        """Make a PathFilteringTransport backed by a MemoryTransport.
492
 
        
493
 
        :param filter_func: by default this will be a no-op function.  Use this
494
 
            parameter to override it."""
495
 
        if filter_func is None:
496
 
            filter_func = lambda x: x
497
 
        server = PathFilteringServer(
498
 
            get_transport('memory:///foo/bar/'), filter_func)
499
 
        server.start_server()
500
 
        self.addCleanup(server.stop_server)
501
 
        return get_transport(server.get_url())
502
 
 
503
 
    def test__filter(self):
504
 
        # _filter (with an identity func as filter_func) always returns
505
 
        # paths relative to the base of the backing transport.
506
 
        transport = self.make_pf_transport()
507
 
        self.assertEqual('foo', transport._filter('foo'))
508
 
        self.assertEqual('foo/bar', transport._filter('foo/bar'))
509
 
        self.assertEqual('', transport._filter('..'))
510
 
        self.assertEqual('', transport._filter('/'))
511
 
        # The base of the pathfiltering transport is taken into account too.
512
 
        transport = transport.clone('subdir1/subdir2')
513
 
        self.assertEqual('subdir1/subdir2/foo', transport._filter('foo'))
514
 
        self.assertEqual(
515
 
            'subdir1/subdir2/foo/bar', transport._filter('foo/bar'))
516
 
        self.assertEqual('subdir1', transport._filter('..'))
517
 
        self.assertEqual('', transport._filter('/'))
518
 
 
519
 
    def test_filter_invocation(self):
520
 
        filter_log = []
521
 
        def filter(path):
522
 
            filter_log.append(path)
523
 
            return path
524
 
        transport = self.make_pf_transport(filter)
525
 
        transport.has('abc')
526
 
        self.assertEqual(['abc'], filter_log)
527
 
        del filter_log[:]
528
 
        transport.clone('abc').has('xyz')
529
 
        self.assertEqual(['abc/xyz'], filter_log)
530
 
        del filter_log[:]
531
 
        transport.has('/abc')
532
 
        self.assertEqual(['abc'], filter_log)
533
 
 
534
 
    def test_clone(self):
535
 
        transport = self.make_pf_transport()
536
 
        # relpath from root and root path are the same
537
 
        relpath_cloned = transport.clone('foo')
538
 
        abspath_cloned = transport.clone('/foo')
539
 
        self.assertEqual(transport.server, relpath_cloned.server)
540
 
        self.assertEqual(transport.server, abspath_cloned.server)
541
 
 
542
 
    def test_url_preserves_pathfiltering(self):
543
 
        """Calling get_transport on a pathfiltered transport's base should
544
 
        produce a transport with exactly the same behaviour as the original
545
 
        pathfiltered transport.
546
 
 
547
 
        This is so that it is not possible to escape (accidentally or
548
 
        otherwise) the filtering by doing::
549
 
            url = filtered_transport.base
550
 
            parent_url = urlutils.join(url, '..')
551
 
            new_transport = get_transport(parent_url)
552
 
        """
553
 
        transport = self.make_pf_transport()
554
 
        new_transport = get_transport(transport.base)
555
 
        self.assertEqual(transport.server, new_transport.server)
556
 
        self.assertEqual(transport.base, new_transport.base)
 
430
        server.setUp()
 
431
        self.assertEqual('chroot-%d:///' % id(server), server.get_url())
 
432
        server.tearDown()
557
433
 
558
434
 
559
435
class ReadonlyDecoratorTransportTest(TestCase):
560
436
    """Readonly decoration specific tests."""
561
437
 
562
438
    def test_local_parameters(self):
 
439
        import bzrlib.transport.readonly as readonly
563
440
        # connect to . in readonly mode
564
441
        transport = readonly.ReadonlyTransportDecorator('readonly+.')
565
442
        self.assertEqual(True, transport.listable())
567
444
 
568
445
    def test_http_parameters(self):
569
446
        from bzrlib.tests.http_server import HttpServer
 
447
        import bzrlib.transport.readonly as readonly
570
448
        # connect to '.' via http which is not listable
571
449
        server = HttpServer()
572
 
        self.start_server(server)
573
 
        transport = get_transport('readonly+' + server.get_url())
574
 
        self.failUnless(isinstance(transport,
575
 
                                   readonly.ReadonlyTransportDecorator))
576
 
        self.assertEqual(False, transport.listable())
577
 
        self.assertEqual(True, transport.is_readonly())
 
450
        server.setUp()
 
451
        try:
 
452
            transport = get_transport('readonly+' + server.get_url())
 
453
            self.failUnless(isinstance(transport,
 
454
                                       readonly.ReadonlyTransportDecorator))
 
455
            self.assertEqual(False, transport.listable())
 
456
            self.assertEqual(True, transport.is_readonly())
 
457
        finally:
 
458
            server.tearDown()
578
459
 
579
460
 
580
461
class FakeNFSDecoratorTests(TestCaseInTempDir):
581
462
    """NFS decorator specific tests."""
582
463
 
583
464
    def get_nfs_transport(self, url):
 
465
        import bzrlib.transport.fakenfs as fakenfs
584
466
        # connect to url with nfs decoration
585
467
        return fakenfs.FakeNFSTransportDecorator('fakenfs+' + url)
586
468
 
597
479
        from bzrlib.tests.http_server import HttpServer
598
480
        # connect to '.' via http which is not listable
599
481
        server = HttpServer()
600
 
        self.start_server(server)
601
 
        transport = self.get_nfs_transport(server.get_url())
602
 
        self.assertIsInstance(
603
 
            transport, fakenfs.FakeNFSTransportDecorator)
604
 
        self.assertEqual(False, transport.listable())
605
 
        self.assertEqual(True, transport.is_readonly())
 
482
        server.setUp()
 
483
        try:
 
484
            transport = self.get_nfs_transport(server.get_url())
 
485
            self.assertIsInstance(
 
486
                transport, bzrlib.transport.fakenfs.FakeNFSTransportDecorator)
 
487
            self.assertEqual(False, transport.listable())
 
488
            self.assertEqual(True, transport.is_readonly())
 
489
        finally:
 
490
            server.tearDown()
606
491
 
607
492
    def test_fakenfs_server_default(self):
608
493
        # a FakeNFSServer() should bring up a local relpath server for itself
 
494
        import bzrlib.transport.fakenfs as fakenfs
609
495
        server = fakenfs.FakeNFSServer()
610
 
        self.start_server(server)
611
 
        # the url should be decorated appropriately
612
 
        self.assertStartsWith(server.get_url(), 'fakenfs+')
613
 
        # and we should be able to get a transport for it
614
 
        transport = get_transport(server.get_url())
615
 
        # which must be a FakeNFSTransportDecorator instance.
616
 
        self.assertIsInstance(transport, fakenfs.FakeNFSTransportDecorator)
 
496
        server.setUp()
 
497
        try:
 
498
            # the url should be decorated appropriately
 
499
            self.assertStartsWith(server.get_url(), 'fakenfs+')
 
500
            # and we should be able to get a transport for it
 
501
            transport = get_transport(server.get_url())
 
502
            # which must be a FakeNFSTransportDecorator instance.
 
503
            self.assertIsInstance(
 
504
                transport, fakenfs.FakeNFSTransportDecorator)
 
505
        finally:
 
506
            server.tearDown()
617
507
 
618
508
    def test_fakenfs_rename_semantics(self):
619
509
        # a FakeNFS transport must mangle the way rename errors occur to
661
551
 
662
552
class TestTransportImplementation(TestCaseInTempDir):
663
553
    """Implementation verification for transports.
664
 
 
 
554
    
665
555
    To verify a transport we need a server factory, which is a callable
666
556
    that accepts no parameters and returns an implementation of
667
557
    bzrlib.transport.Server.
668
 
 
 
558
    
669
559
    That Server is then used to construct transport instances and test
670
560
    the transport via loopback activity.
671
561
 
672
 
    Currently this assumes that the Transport object is connected to the
673
 
    current working directory.  So that whatever is done
674
 
    through the transport, should show up in the working
 
562
    Currently this assumes that the Transport object is connected to the 
 
563
    current working directory.  So that whatever is done 
 
564
    through the transport, should show up in the working 
675
565
    directory, and vice-versa. This is a bug, because its possible to have
676
 
    URL schemes which provide access to something that may not be
677
 
    result in storage on the local disk, i.e. due to file system limits, or
 
566
    URL schemes which provide access to something that may not be 
 
567
    result in storage on the local disk, i.e. due to file system limits, or 
678
568
    due to it being a database or some other non-filesystem tool.
679
569
 
680
570
    This also tests to make sure that the functions work with both
681
571
    generators and lists (assuming iter(list) is effectively a generator)
682
572
    """
683
 
 
 
573
    
684
574
    def setUp(self):
685
575
        super(TestTransportImplementation, self).setUp()
686
576
        self._server = self.transport_server()
687
 
        self.start_server(self._server)
 
577
        self._server.setUp()
 
578
        self.addCleanup(self._server.tearDown)
688
579
 
689
580
    def get_transport(self, relpath=None):
690
581
        """Return a connected transport to the local directory.
896
787
        # readv records the supplied offset request
897
788
        expected_result.append(('readv', 'foo', [(0, 1), (3, 2)], True, 6))
898
789
        self.assertEqual(expected_result, transport._activity)
899
 
 
900
 
 
901
 
class TestSSHConnections(tests.TestCaseWithTransport):
902
 
 
903
 
    def test_bzr_connect_to_bzr_ssh(self):
904
 
        """User acceptance that get_transport of a bzr+ssh:// behaves correctly.
905
 
 
906
 
        bzr+ssh:// should cause bzr to run a remote bzr smart server over SSH.
907
 
        """
908
 
        # This test actually causes a bzr instance to be invoked, which is very
909
 
        # expensive: it should be the only such test in the test suite.
910
 
        # A reasonable evolution for this would be to simply check inside
911
 
        # check_channel_exec_request that the command is appropriate, and then
912
 
        # satisfy requests in-process.
913
 
        self.requireFeature(features.paramiko)
914
 
        # SFTPFullAbsoluteServer has a get_url method, and doesn't
915
 
        # override the interface (doesn't change self._vendor).
916
 
        # Note that this does encryption, so can be slow.
917
 
        from bzrlib.transport.sftp import SFTPFullAbsoluteServer
918
 
        from bzrlib.tests.stub_sftp import StubServer
919
 
 
920
 
        # Start an SSH server
921
 
        self.command_executed = []
922
 
        # XXX: This is horrible -- we define a really dumb SSH server that
923
 
        # executes commands, and manage the hooking up of stdin/out/err to the
924
 
        # SSH channel ourselves.  Surely this has already been implemented
925
 
        # elsewhere?
926
 
        started = []
927
 
        class StubSSHServer(StubServer):
928
 
 
929
 
            test = self
930
 
 
931
 
            def check_channel_exec_request(self, channel, command):
932
 
                self.test.command_executed.append(command)
933
 
                proc = subprocess.Popen(
934
 
                    command, shell=True, stdin=subprocess.PIPE,
935
 
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
936
 
 
937
 
                # XXX: horribly inefficient, not to mention ugly.
938
 
                # Start a thread for each of stdin/out/err, and relay bytes from
939
 
                # the subprocess to channel and vice versa.
940
 
                def ferry_bytes(read, write, close):
941
 
                    while True:
942
 
                        bytes = read(1)
943
 
                        if bytes == '':
944
 
                            close()
945
 
                            break
946
 
                        write(bytes)
947
 
 
948
 
                file_functions = [
949
 
                    (channel.recv, proc.stdin.write, proc.stdin.close),
950
 
                    (proc.stdout.read, channel.sendall, channel.close),
951
 
                    (proc.stderr.read, channel.sendall_stderr, channel.close)]
952
 
                started.append(proc)
953
 
                for read, write, close in file_functions:
954
 
                    t = threading.Thread(
955
 
                        target=ferry_bytes, args=(read, write, close))
956
 
                    t.start()
957
 
                    started.append(t)
958
 
 
959
 
                return True
960
 
 
961
 
        ssh_server = SFTPFullAbsoluteServer(StubSSHServer)
962
 
        # We *don't* want to override the default SSH vendor: the detected one
963
 
        # is the one to use.
964
 
        self.start_server(ssh_server)
965
 
        port = ssh_server._listener.port
966
 
 
967
 
        if sys.platform == 'win32':
968
 
            bzr_remote_path = sys.executable + ' ' + self.get_bzr_path()
969
 
        else:
970
 
            bzr_remote_path = self.get_bzr_path()
971
 
        os.environ['BZR_REMOTE_PATH'] = bzr_remote_path
972
 
 
973
 
        # Access the branch via a bzr+ssh URL.  The BZR_REMOTE_PATH environment
974
 
        # variable is used to tell bzr what command to run on the remote end.
975
 
        path_to_branch = osutils.abspath('.')
976
 
        if sys.platform == 'win32':
977
 
            # On Windows, we export all drives as '/C:/, etc. So we need to
978
 
            # prefix a '/' to get the right path.
979
 
            path_to_branch = '/' + path_to_branch
980
 
        url = 'bzr+ssh://fred:secret@localhost:%d%s' % (port, path_to_branch)
981
 
        t = get_transport(url)
982
 
        self.permit_url(t.base)
983
 
        t.mkdir('foo')
984
 
 
985
 
        self.assertEqual(
986
 
            ['%s serve --inet --directory=/ --allow-writes' % bzr_remote_path],
987
 
            self.command_executed)
988
 
        # Make sure to disconnect, so that the remote process can stop, and we
989
 
        # can cleanup. Then pause the test until everything is shutdown
990
 
        t._client._medium.disconnect()
991
 
        if not started:
992
 
            return
993
 
        # First wait for the subprocess
994
 
        started[0].wait()
995
 
        # And the rest are threads
996
 
        for t in started[1:]:
997
 
            t.join()