~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: John Arbash Meinel
  • Date: 2008-10-30 00:55:00 UTC
  • mto: (3815.2.5 prepare-1.9)
  • mto: This revision was merged to the branch mainline in revision 3811.
  • Revision ID: john@arbash-meinel.com-20081030005500-r5cej1cxflqhs3io
Switch so that we are using a simple timestamp as the first action.

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
from cStringIO import StringIO
28
28
 
29
29
from bzrlib import (
 
30
    config,
30
31
    errors,
31
32
    graph,
32
33
    pack,
33
34
    remote,
34
35
    repository,
35
36
    tests,
 
37
    urlutils,
36
38
    )
37
39
from bzrlib.branch import Branch
38
40
from bzrlib.bzrdir import BzrDir, BzrDirFormat
48
50
from bzrlib.symbol_versioning import one_four
49
51
from bzrlib.transport import get_transport, http
50
52
from bzrlib.transport.memory import MemoryTransport
51
 
from bzrlib.transport.remote import RemoteTransport, RemoteTCPTransport
 
53
from bzrlib.transport.remote import (
 
54
    RemoteTransport,
 
55
    RemoteSSHTransport,
 
56
    RemoteTCPTransport,
 
57
)
52
58
 
53
59
 
54
60
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
106
112
        self.assertStartsWith(str(b), 'RemoteBranch(')
107
113
 
108
114
 
 
115
class FakeRemoteTransport(object):
 
116
    """This class provides the minimum support for use in place of a RemoteTransport.
 
117
    
 
118
    It doesn't actually transmit requests, but rather expects them to be
 
119
    handled by a FakeClient which holds canned responses.  It does not allow
 
120
    any vfs access, therefore is not suitable for testing any operation that
 
121
    will fallback to vfs access.  Backing the test by an instance of this
 
122
    class guarantees that it's - done using non-vfs operations.
 
123
    """
 
124
 
 
125
    _default_url = 'fakeremotetransport://host/path/'
 
126
 
 
127
    def __init__(self, url=None):
 
128
        if url is None:
 
129
            url = self._default_url
 
130
        self.base = url
 
131
 
 
132
    def __repr__(self):
 
133
        return "%r(%r)" % (self.__class__.__name__,
 
134
            self.base)
 
135
 
 
136
    def clone(self, relpath):
 
137
        return FakeRemoteTransport(urlutils.join(self.base, relpath))
 
138
 
 
139
    def get(self, relpath):
 
140
        # only get is specifically stubbed out, because it's usually the first
 
141
        # thing we do.  anything else will fail with an AttributeError.
 
142
        raise AssertionError("%r doesn't support file access to %r"
 
143
            % (self, relpath))
 
144
 
 
145
 
 
146
 
109
147
class FakeProtocol(object):
110
148
    """Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
111
149
 
144
182
        self.responses = []
145
183
        self._calls = []
146
184
        self.expecting_body = False
 
185
        # if non-None, this is the list of expected calls, with only the
 
186
        # method name and arguments included.  the body might be hard to
 
187
        # compute so is not included
 
188
        self._expected_calls = None
147
189
        _SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
148
190
 
 
191
    def add_expected_call(self, call_name, call_args, response_type,
 
192
        response_args, response_body=None):
 
193
        if self._expected_calls is None:
 
194
            self._expected_calls = []
 
195
        self._expected_calls.append((call_name, call_args))
 
196
        self.responses.append((response_type, response_args, response_body))
 
197
 
149
198
    def add_success_response(self, *args):
150
199
        self.responses.append(('success', args, None))
151
200
 
158
207
    def add_unknown_method_response(self, verb):
159
208
        self.responses.append(('unknown', verb))
160
209
 
 
210
    def finished_test(self):
 
211
        if self._expected_calls:
 
212
            raise AssertionError("%r finished but was still expecting %r"
 
213
                % (self, self._expected_calls[0]))
 
214
 
161
215
    def _get_next_response(self):
162
 
        response_tuple = self.responses.pop(0)
 
216
        try:
 
217
            response_tuple = self.responses.pop(0)
 
218
        except IndexError, e:
 
219
            raise AssertionError("%r didn't expect any more calls"
 
220
                % (self,))
163
221
        if response_tuple[0] == 'unknown':
164
222
            raise errors.UnknownSmartMethod(response_tuple[1])
165
223
        elif response_tuple[0] == 'error':
166
224
            raise errors.ErrorFromSmartServer(response_tuple[1])
167
225
        return response_tuple
168
226
 
 
227
    def _check_call(self, method, args):
 
228
        if self._expected_calls is None:
 
229
            # the test should be updated to say what it expects
 
230
            return
 
231
        try:
 
232
            next_call = self._expected_calls.pop(0)
 
233
        except IndexError:
 
234
            raise AssertionError("%r didn't expect any more calls "
 
235
                "but got %r%r"
 
236
                % (self, method, args,))
 
237
        if method != next_call[0] or args != next_call[1]:
 
238
            raise AssertionError("%r expected %r%r "
 
239
                "but got %r%r"
 
240
                % (self, next_call[0], next_call[1], method, args,))
 
241
 
169
242
    def call(self, method, *args):
 
243
        self._check_call(method, args)
170
244
        self._calls.append(('call', method, args))
171
245
        return self._get_next_response()[1]
172
246
 
173
247
    def call_expecting_body(self, method, *args):
 
248
        self._check_call(method, args)
174
249
        self._calls.append(('call_expecting_body', method, args))
175
250
        result = self._get_next_response()
176
251
        self.expecting_body = True
177
252
        return result[1], FakeProtocol(result[2], self)
178
253
 
179
254
    def call_with_body_bytes_expecting_body(self, method, args, body):
 
255
        self._check_call(method, args)
180
256
        self._calls.append(('call_with_body_bytes_expecting_body', method,
181
257
            args, body))
182
258
        result = self._get_next_response()
187
263
class FakeMedium(medium.SmartClientMedium):
188
264
 
189
265
    def __init__(self, client_calls, base):
190
 
        self._remote_is_at_least_1_2 = True
 
266
        medium.SmartClientMedium.__init__(self, base)
191
267
        self._client_calls = client_calls
192
 
        self.base = base
193
268
 
194
269
    def disconnect(self):
195
270
        self._client_calls.append(('disconnect medium',))
254
329
                'xyz/', scheme + '//host/path', 'xyz/')
255
330
 
256
331
 
 
332
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
 
333
    """Tests for the behaviour of client_medium.remote_is_at_least."""
 
334
 
 
335
    def test_initially_unlimited(self):
 
336
        """A fresh medium assumes that the remote side supports all
 
337
        versions.
 
338
        """
 
339
        client_medium = medium.SmartClientMedium('dummy base')
 
340
        self.assertFalse(client_medium._is_remote_before((99, 99)))
 
341
    
 
342
    def test__remember_remote_is_before(self):
 
343
        """Calling _remember_remote_is_before ratchets down the known remote
 
344
        version.
 
345
        """
 
346
        client_medium = medium.SmartClientMedium('dummy base')
 
347
        # Mark the remote side as being less than 1.6.  The remote side may
 
348
        # still be 1.5.
 
349
        client_medium._remember_remote_is_before((1, 6))
 
350
        self.assertTrue(client_medium._is_remote_before((1, 6)))
 
351
        self.assertFalse(client_medium._is_remote_before((1, 5)))
 
352
        # Calling _remember_remote_is_before again with a lower value works.
 
353
        client_medium._remember_remote_is_before((1, 5))
 
354
        self.assertTrue(client_medium._is_remote_before((1, 5)))
 
355
        # You cannot call _remember_remote_is_before with a larger value.
 
356
        self.assertRaises(
 
357
            AssertionError, client_medium._remember_remote_is_before, (1, 9))
 
358
 
 
359
 
257
360
class TestBzrDirOpenBranch(tests.TestCase):
258
361
 
259
362
    def test_branch_present(self):
261
364
        transport.mkdir('quack')
262
365
        transport = transport.clone('quack')
263
366
        client = FakeClient(transport.base)
264
 
        client.add_success_response('ok', '')
265
 
        client.add_success_response('ok', '', 'no', 'no', 'no')
 
367
        client.add_expected_call(
 
368
            'BzrDir.open_branch', ('quack/',),
 
369
            'success', ('ok', ''))
 
370
        client.add_expected_call(
 
371
            'BzrDir.find_repositoryV2', ('quack/',),
 
372
            'success', ('ok', '', 'no', 'no', 'no'))
 
373
        client.add_expected_call(
 
374
            'Branch.get_stacked_on_url', ('quack/',),
 
375
            'error', ('NotStacked',))
266
376
        bzrdir = RemoteBzrDir(transport, _client=client)
267
377
        result = bzrdir.open_branch()
268
 
        self.assertEqual(
269
 
            [('call', 'BzrDir.open_branch', ('quack/',)),
270
 
             ('call', 'BzrDir.find_repositoryV2', ('quack/',))],
271
 
            client._calls)
272
378
        self.assertIsInstance(result, RemoteBranch)
273
379
        self.assertEqual(bzrdir, result.bzrdir)
 
380
        client.finished_test()
274
381
 
275
382
    def test_branch_missing(self):
276
383
        transport = MemoryTransport()
306
413
        # transmitted as "~", not "%7E".
307
414
        transport = RemoteTCPTransport('bzr://localhost/~hello/')
308
415
        client = FakeClient(transport.base)
309
 
        client.add_success_response('ok', '')
310
 
        client.add_success_response('ok', '', 'no', 'no', 'no')
 
416
        client.add_expected_call(
 
417
            'BzrDir.open_branch', ('~hello/',),
 
418
            'success', ('ok', ''))
 
419
        client.add_expected_call(
 
420
            'BzrDir.find_repositoryV2', ('~hello/',),
 
421
            'success', ('ok', '', 'no', 'no', 'no'))
 
422
        client.add_expected_call(
 
423
            'Branch.get_stacked_on_url', ('~hello/',),
 
424
            'error', ('NotStacked',))
311
425
        bzrdir = RemoteBzrDir(transport, _client=client)
312
426
        result = bzrdir.open_branch()
313
 
        self.assertEqual(
314
 
            [('call', 'BzrDir.open_branch', ('~hello/',)),
315
 
             ('call', 'BzrDir.find_repositoryV2', ('~hello/',))],
316
 
            client._calls)
 
427
        client.finished_test()
317
428
 
318
429
    def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
319
430
        transport = MemoryTransport()
400
511
        return OldSmartClient()
401
512
 
402
513
 
403
 
class TestBranchLastRevisionInfo(tests.TestCase):
 
514
class RemoteBranchTestCase(tests.TestCase):
 
515
 
 
516
    def make_remote_branch(self, transport, client):
 
517
        """Make a RemoteBranch using 'client' as its _SmartClient.
 
518
        
 
519
        A RemoteBzrDir and RemoteRepository will also be created to fill out
 
520
        the RemoteBranch, albeit with stub values for some of their attributes.
 
521
        """
 
522
        # we do not want bzrdir to make any remote calls, so use False as its
 
523
        # _client.  If it tries to make a remote call, this will fail
 
524
        # immediately.
 
525
        bzrdir = RemoteBzrDir(transport, _client=False)
 
526
        repo = RemoteRepository(bzrdir, None, _client=client)
 
527
        return RemoteBranch(bzrdir, repo, _client=client)
 
528
 
 
529
 
 
530
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
404
531
 
405
532
    def test_empty_branch(self):
406
533
        # in an empty branch we decode the response properly
407
534
        transport = MemoryTransport()
408
535
        client = FakeClient(transport.base)
409
 
        client.add_success_response('ok', '0', 'null:')
 
536
        client.add_expected_call(
 
537
            'Branch.get_stacked_on_url', ('quack/',),
 
538
            'error', ('NotStacked',))
 
539
        client.add_expected_call(
 
540
            'Branch.last_revision_info', ('quack/',),
 
541
            'success', ('ok', '0', 'null:'))
410
542
        transport.mkdir('quack')
411
543
        transport = transport.clone('quack')
412
 
        # we do not want bzrdir to make any remote calls
413
 
        bzrdir = RemoteBzrDir(transport, _client=False)
414
 
        branch = RemoteBranch(bzrdir, None, _client=client)
 
544
        branch = self.make_remote_branch(transport, client)
415
545
        result = branch.last_revision_info()
416
 
 
417
 
        self.assertEqual(
418
 
            [('call', 'Branch.last_revision_info', ('quack/',))],
419
 
            client._calls)
 
546
        client.finished_test()
420
547
        self.assertEqual((0, NULL_REVISION), result)
421
548
 
422
549
    def test_non_empty_branch(self):
424
551
        revid = u'\xc8'.encode('utf8')
425
552
        transport = MemoryTransport()
426
553
        client = FakeClient(transport.base)
427
 
        client.add_success_response('ok', '2', revid)
 
554
        client.add_expected_call(
 
555
            'Branch.get_stacked_on_url', ('kwaak/',),
 
556
            'error', ('NotStacked',))
 
557
        client.add_expected_call(
 
558
            'Branch.last_revision_info', ('kwaak/',),
 
559
            'success', ('ok', '2', revid))
428
560
        transport.mkdir('kwaak')
429
561
        transport = transport.clone('kwaak')
430
 
        # we do not want bzrdir to make any remote calls
431
 
        bzrdir = RemoteBzrDir(transport, _client=False)
432
 
        branch = RemoteBranch(bzrdir, None, _client=client)
 
562
        branch = self.make_remote_branch(transport, client)
433
563
        result = branch.last_revision_info()
434
 
 
435
 
        self.assertEqual(
436
 
            [('call', 'Branch.last_revision_info', ('kwaak/',))],
437
 
            client._calls)
438
564
        self.assertEqual((2, revid), result)
439
565
 
440
566
 
441
 
class TestBranchSetLastRevision(tests.TestCase):
 
567
class TestBranch_get_stacked_on_url(tests.TestCaseWithMemoryTransport):
 
568
    """Test Branch._get_stacked_on_url rpc"""
 
569
 
 
570
    def test_get_stacked_on_invalid_url(self):
 
571
        raise tests.KnownFailure('opening a branch requires the server to open the fallback repository')
 
572
        transport = FakeRemoteTransport('fakeremotetransport:///')
 
573
        client = FakeClient(transport.base)
 
574
        client.add_expected_call(
 
575
            'Branch.get_stacked_on_url', ('.',),
 
576
            'success', ('ok', 'file:///stacked/on'))
 
577
        bzrdir = RemoteBzrDir(transport, _client=client)
 
578
        branch = RemoteBranch(bzrdir, None, _client=client)
 
579
        result = branch.get_stacked_on_url()
 
580
        self.assertEqual(
 
581
            'file:///stacked/on', result)
 
582
 
 
583
    def test_backwards_compatible(self):
 
584
        # like with bzr1.6 with no Branch.get_stacked_on_url rpc
 
585
        base_branch = self.make_branch('base', format='1.6')
 
586
        stacked_branch = self.make_branch('stacked', format='1.6')
 
587
        stacked_branch.set_stacked_on_url('../base')
 
588
        client = FakeClient(self.get_url())
 
589
        client.add_expected_call(
 
590
            'BzrDir.open_branch', ('stacked/',),
 
591
            'success', ('ok', ''))
 
592
        client.add_expected_call(
 
593
            'BzrDir.find_repositoryV2', ('stacked/',),
 
594
            'success', ('ok', '', 'no', 'no', 'no'))
 
595
        # called twice, once from constructor and then again by us
 
596
        client.add_expected_call(
 
597
            'Branch.get_stacked_on_url', ('stacked/',),
 
598
            'unknown', ('Branch.get_stacked_on_url',))
 
599
        client.add_expected_call(
 
600
            'Branch.get_stacked_on_url', ('stacked/',),
 
601
            'unknown', ('Branch.get_stacked_on_url',))
 
602
        # this will also do vfs access, but that goes direct to the transport
 
603
        # and isn't seen by the FakeClient.
 
604
        bzrdir = RemoteBzrDir(self.get_transport('stacked'), _client=client)
 
605
        branch = bzrdir.open_branch()
 
606
        result = branch.get_stacked_on_url()
 
607
        self.assertEqual('../base', result)
 
608
        client.finished_test()
 
609
        # it's in the fallback list both for the RemoteRepository and its vfs
 
610
        # repository
 
611
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
612
        self.assertEqual(1,
 
613
            len(branch.repository._real_repository._fallback_repositories))
 
614
 
 
615
    def test_get_stacked_on_real_branch(self):
 
616
        base_branch = self.make_branch('base', format='1.6')
 
617
        stacked_branch = self.make_branch('stacked', format='1.6')
 
618
        stacked_branch.set_stacked_on_url('../base')
 
619
        client = FakeClient(self.get_url())
 
620
        client.add_expected_call(
 
621
            'BzrDir.open_branch', ('stacked/',),
 
622
            'success', ('ok', ''))
 
623
        client.add_expected_call(
 
624
            'BzrDir.find_repositoryV2', ('stacked/',),
 
625
            'success', ('ok', '', 'no', 'no', 'no'))
 
626
        # called twice, once from constructor and then again by us
 
627
        client.add_expected_call(
 
628
            'Branch.get_stacked_on_url', ('stacked/',),
 
629
            'success', ('ok', '../base'))
 
630
        client.add_expected_call(
 
631
            'Branch.get_stacked_on_url', ('stacked/',),
 
632
            'success', ('ok', '../base'))
 
633
        bzrdir = RemoteBzrDir(self.get_transport('stacked'), _client=client)
 
634
        branch = bzrdir.open_branch()
 
635
        result = branch.get_stacked_on_url()
 
636
        self.assertEqual('../base', result)
 
637
        client.finished_test()
 
638
        # it's in the fallback list both for the RemoteRepository and its vfs
 
639
        # repository
 
640
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
641
        self.assertEqual(1,
 
642
            len(branch.repository._real_repository._fallback_repositories))
 
643
 
 
644
 
 
645
class TestBranchSetLastRevision(RemoteBranchTestCase):
442
646
 
443
647
    def test_set_empty(self):
444
648
        # set_revision_history([]) is translated to calling
448
652
        transport = transport.clone('branch')
449
653
 
450
654
        client = FakeClient(transport.base)
451
 
        # lock_write
452
 
        client.add_success_response('ok', 'branch token', 'repo token')
453
 
        # set_last_revision
454
 
        client.add_success_response('ok')
455
 
        # unlock
456
 
        client.add_success_response('ok')
457
 
        bzrdir = RemoteBzrDir(transport, _client=False)
458
 
        branch = RemoteBranch(bzrdir, None, _client=client)
 
655
        client.add_expected_call(
 
656
            'Branch.get_stacked_on_url', ('branch/',),
 
657
            'error', ('NotStacked',))
 
658
        client.add_expected_call(
 
659
            'Branch.lock_write', ('branch/', '', ''),
 
660
            'success', ('ok', 'branch token', 'repo token'))
 
661
        client.add_expected_call(
 
662
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
 
663
            'success', ('ok',))
 
664
        client.add_expected_call(
 
665
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
666
            'success', ('ok',))
 
667
        branch = self.make_remote_branch(transport, client)
459
668
        # This is a hack to work around the problem that RemoteBranch currently
460
669
        # unnecessarily invokes _ensure_real upon a call to lock_write.
461
670
        branch._ensure_real = lambda: None
462
671
        branch.lock_write()
463
 
        client._calls = []
464
672
        result = branch.set_revision_history([])
465
 
        self.assertEqual(
466
 
            [('call', 'Branch.set_last_revision',
467
 
                ('branch/', 'branch token', 'repo token', 'null:'))],
468
 
            client._calls)
469
673
        branch.unlock()
470
674
        self.assertEqual(None, result)
 
675
        client.finished_test()
471
676
 
472
677
    def test_set_nonempty(self):
473
678
        # set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
477
682
        transport = transport.clone('branch')
478
683
 
479
684
        client = FakeClient(transport.base)
480
 
        # lock_write
481
 
        client.add_success_response('ok', 'branch token', 'repo token')
482
 
        # set_last_revision
483
 
        client.add_success_response('ok')
484
 
        # unlock
485
 
        client.add_success_response('ok')
486
 
        bzrdir = RemoteBzrDir(transport, _client=False)
487
 
        branch = RemoteBranch(bzrdir, None, _client=client)
 
685
        client.add_expected_call(
 
686
            'Branch.get_stacked_on_url', ('branch/',),
 
687
            'error', ('NotStacked',))
 
688
        client.add_expected_call(
 
689
            'Branch.lock_write', ('branch/', '', ''),
 
690
            'success', ('ok', 'branch token', 'repo token'))
 
691
        client.add_expected_call(
 
692
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
 
693
            'success', ('ok',))
 
694
        client.add_expected_call(
 
695
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
696
            'success', ('ok',))
 
697
        branch = self.make_remote_branch(transport, client)
488
698
        # This is a hack to work around the problem that RemoteBranch currently
489
699
        # unnecessarily invokes _ensure_real upon a call to lock_write.
490
700
        branch._ensure_real = lambda: None
491
701
        # Lock the branch, reset the record of remote calls.
492
702
        branch.lock_write()
493
 
        client._calls = []
494
 
 
495
703
        result = branch.set_revision_history(['rev-id1', 'rev-id2'])
496
 
        self.assertEqual(
497
 
            [('call', 'Branch.set_last_revision',
498
 
                ('branch/', 'branch token', 'repo token', 'rev-id2'))],
499
 
            client._calls)
500
704
        branch.unlock()
501
705
        self.assertEqual(None, result)
 
706
        client.finished_test()
502
707
 
503
708
    def test_no_such_revision(self):
504
709
        transport = MemoryTransport()
506
711
        transport = transport.clone('branch')
507
712
        # A response of 'NoSuchRevision' is translated into an exception.
508
713
        client = FakeClient(transport.base)
509
 
        # lock_write
510
 
        client.add_success_response('ok', 'branch token', 'repo token')
511
 
        # set_last_revision
512
 
        client.add_error_response('NoSuchRevision', 'rev-id')
513
 
        # unlock
514
 
        client.add_success_response('ok')
 
714
        client.add_expected_call(
 
715
            'Branch.get_stacked_on_url', ('branch/',),
 
716
            'error', ('NotStacked',))
 
717
        client.add_expected_call(
 
718
            'Branch.lock_write', ('branch/', '', ''),
 
719
            'success', ('ok', 'branch token', 'repo token'))
 
720
        client.add_expected_call(
 
721
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
 
722
            'error', ('NoSuchRevision', 'rev-id'))
 
723
        client.add_expected_call(
 
724
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
725
            'success', ('ok',))
515
726
 
516
 
        bzrdir = RemoteBzrDir(transport, _client=False)
517
 
        branch = RemoteBranch(bzrdir, None, _client=client)
518
 
        branch._ensure_real = lambda: None
 
727
        branch = self.make_remote_branch(transport, client)
519
728
        branch.lock_write()
520
 
        client._calls = []
521
 
 
522
729
        self.assertRaises(
523
730
            errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
524
731
        branch.unlock()
525
 
 
526
 
 
527
 
class TestBranchSetLastRevisionInfo(tests.TestCase):
 
732
        client.finished_test()
 
733
 
 
734
    def test_tip_change_rejected(self):
 
735
        """TipChangeRejected responses cause a TipChangeRejected exception to
 
736
        be raised.
 
737
        """
 
738
        transport = MemoryTransport()
 
739
        transport.mkdir('branch')
 
740
        transport = transport.clone('branch')
 
741
        client = FakeClient(transport.base)
 
742
        rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
 
743
        rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
 
744
        client.add_expected_call(
 
745
            'Branch.get_stacked_on_url', ('branch/',),
 
746
            'error', ('NotStacked',))
 
747
        client.add_expected_call(
 
748
            'Branch.lock_write', ('branch/', '', ''),
 
749
            'success', ('ok', 'branch token', 'repo token'))
 
750
        client.add_expected_call(
 
751
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
 
752
            'error', ('TipChangeRejected', rejection_msg_utf8))
 
753
        client.add_expected_call(
 
754
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
755
            'success', ('ok',))
 
756
        branch = self.make_remote_branch(transport, client)
 
757
        branch._ensure_real = lambda: None
 
758
        branch.lock_write()
 
759
        self.addCleanup(branch.unlock)
 
760
        # The 'TipChangeRejected' error response triggered by calling
 
761
        # set_revision_history causes a TipChangeRejected exception.
 
762
        err = self.assertRaises(
 
763
            errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
 
764
        # The UTF-8 message from the response has been decoded into a unicode
 
765
        # object.
 
766
        self.assertIsInstance(err.msg, unicode)
 
767
        self.assertEqual(rejection_msg_unicode, err.msg)
 
768
        branch.unlock()
 
769
        client.finished_test()
 
770
 
 
771
 
 
772
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
528
773
 
529
774
    def test_set_last_revision_info(self):
530
775
        # set_last_revision_info(num, 'rev-id') is translated to calling
533
778
        transport.mkdir('branch')
534
779
        transport = transport.clone('branch')
535
780
        client = FakeClient(transport.base)
 
781
        # get_stacked_on_url
 
782
        client.add_error_response('NotStacked')
536
783
        # lock_write
537
784
        client.add_success_response('ok', 'branch token', 'repo token')
538
785
        # set_last_revision
540
787
        # unlock
541
788
        client.add_success_response('ok')
542
789
 
543
 
        bzrdir = RemoteBzrDir(transport, _client=False)
544
 
        branch = RemoteBranch(bzrdir, None, _client=client)
545
 
        # This is a hack to work around the problem that RemoteBranch currently
546
 
        # unnecessarily invokes _ensure_real upon a call to lock_write.
547
 
        branch._ensure_real = lambda: None
 
790
        branch = self.make_remote_branch(transport, client)
548
791
        # Lock the branch, reset the record of remote calls.
549
792
        branch.lock_write()
550
793
        client._calls = []
562
805
        transport.mkdir('branch')
563
806
        transport = transport.clone('branch')
564
807
        client = FakeClient(transport.base)
 
808
        # get_stacked_on_url
 
809
        client.add_error_response('NotStacked')
565
810
        # lock_write
566
811
        client.add_success_response('ok', 'branch token', 'repo token')
567
812
        # set_last_revision
569
814
        # unlock
570
815
        client.add_success_response('ok')
571
816
 
572
 
        bzrdir = RemoteBzrDir(transport, _client=False)
573
 
        branch = RemoteBranch(bzrdir, None, _client=client)
574
 
        # This is a hack to work around the problem that RemoteBranch currently
575
 
        # unnecessarily invokes _ensure_real upon a call to lock_write.
576
 
        branch._ensure_real = lambda: None
 
817
        branch = self.make_remote_branch(transport, client)
577
818
        # Lock the branch, reset the record of remote calls.
578
819
        branch.lock_write()
579
820
        client._calls = []
588
829
        branch._lock_count = 2
589
830
        branch._lock_token = 'branch token'
590
831
        branch._repo_lock_token = 'repo token'
 
832
        branch.repository._lock_mode = 'w'
 
833
        branch.repository._lock_count = 2
 
834
        branch.repository._lock_token = 'repo token'
591
835
 
592
836
    def test_backwards_compatibility(self):
593
837
        """If the server does not support the Branch.set_last_revision_info
605
849
        transport.mkdir('branch')
606
850
        transport = transport.clone('branch')
607
851
        client = FakeClient(transport.base)
608
 
        client.add_unknown_method_response('Branch.set_last_revision_info')
609
 
        bzrdir = RemoteBzrDir(transport, _client=False)
610
 
        branch = RemoteBranch(bzrdir, None, _client=client)
 
852
        client.add_expected_call(
 
853
            'Branch.get_stacked_on_url', ('branch/',),
 
854
            'error', ('NotStacked',))
 
855
        client.add_expected_call(
 
856
            'Branch.set_last_revision_info',
 
857
            ('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
 
858
            'unknown', 'Branch.set_last_revision_info')
 
859
 
 
860
        branch = self.make_remote_branch(transport, client)
611
861
        class StubRealBranch(object):
612
862
            def __init__(self):
613
863
                self.calls = []
614
864
            def set_last_revision_info(self, revno, revision_id):
615
865
                self.calls.append(
616
866
                    ('set_last_revision_info', revno, revision_id))
 
867
            def _clear_cached_state(self):
 
868
                pass
617
869
        real_branch = StubRealBranch()
618
870
        branch._real_branch = real_branch
619
871
        self.lock_remote_branch(branch)
621
873
        # Call set_last_revision_info, and verify it behaved as expected.
622
874
        result = branch.set_last_revision_info(1234, 'a-revision-id')
623
875
        self.assertEqual(
624
 
            [('call', 'Branch.set_last_revision_info',
625
 
                ('branch/', 'branch token', 'repo token',
626
 
                 '1234', 'a-revision-id')),],
627
 
            client._calls)
628
 
        self.assertEqual(
629
876
            [('set_last_revision_info', 1234, 'a-revision-id')],
630
877
            real_branch.calls)
 
878
        client.finished_test()
631
879
 
632
880
    def test_unexpected_error(self):
633
 
        # A response of 'NoSuchRevision' is translated into an exception.
 
881
        # If the server sends an error the client doesn't understand, it gets
 
882
        # turned into an UnknownErrorFromSmartServer, which is presented as a
 
883
        # non-internal error to the user.
634
884
        transport = MemoryTransport()
635
885
        transport.mkdir('branch')
636
886
        transport = transport.clone('branch')
637
887
        client = FakeClient(transport.base)
 
888
        # get_stacked_on_url
 
889
        client.add_error_response('NotStacked')
638
890
        # lock_write
639
891
        client.add_success_response('ok', 'branch token', 'repo token')
640
892
        # set_last_revision
642
894
        # unlock
643
895
        client.add_success_response('ok')
644
896
 
645
 
        bzrdir = RemoteBzrDir(transport, _client=False)
646
 
        branch = RemoteBranch(bzrdir, None, _client=client)
647
 
        # This is a hack to work around the problem that RemoteBranch currently
648
 
        # unnecessarily invokes _ensure_real upon a call to lock_write.
649
 
        branch._ensure_real = lambda: None
 
897
        branch = self.make_remote_branch(transport, client)
650
898
        # Lock the branch, reset the record of remote calls.
651
899
        branch.lock_write()
652
900
        client._calls = []
653
901
 
654
902
        err = self.assertRaises(
655
 
            errors.ErrorFromSmartServer,
 
903
            errors.UnknownErrorFromSmartServer,
656
904
            branch.set_last_revision_info, 123, 'revid')
657
905
        self.assertEqual(('UnexpectedError',), err.error_tuple)
658
906
        branch.unlock()
659
907
 
 
908
    def test_tip_change_rejected(self):
 
909
        """TipChangeRejected responses cause a TipChangeRejected exception to
 
910
        be raised.
 
911
        """
 
912
        transport = MemoryTransport()
 
913
        transport.mkdir('branch')
 
914
        transport = transport.clone('branch')
 
915
        client = FakeClient(transport.base)
 
916
        # get_stacked_on_url
 
917
        client.add_error_response('NotStacked')
 
918
        # lock_write
 
919
        client.add_success_response('ok', 'branch token', 'repo token')
 
920
        # set_last_revision
 
921
        client.add_error_response('TipChangeRejected', 'rejection message')
 
922
        # unlock
 
923
        client.add_success_response('ok')
 
924
 
 
925
        branch = self.make_remote_branch(transport, client)
 
926
        # Lock the branch, reset the record of remote calls.
 
927
        branch.lock_write()
 
928
        self.addCleanup(branch.unlock)
 
929
        client._calls = []
 
930
 
 
931
        # The 'TipChangeRejected' error response triggered by calling
 
932
        # set_last_revision_info causes a TipChangeRejected exception.
 
933
        err = self.assertRaises(
 
934
            errors.TipChangeRejected,
 
935
            branch.set_last_revision_info, 123, 'revid')
 
936
        self.assertEqual('rejection message', err.msg)
 
937
 
660
938
 
661
939
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
662
940
    """Getting the branch configuration should use an abstract method not vfs.
684
962
        ##     client._calls)
685
963
 
686
964
 
687
 
class TestBranchLockWrite(tests.TestCase):
 
965
class TestBranchLockWrite(RemoteBranchTestCase):
688
966
 
689
967
    def test_lock_write_unlockable(self):
690
968
        transport = MemoryTransport()
691
969
        client = FakeClient(transport.base)
692
 
        client.add_error_response('UnlockableTransport')
 
970
        client.add_expected_call(
 
971
            'Branch.get_stacked_on_url', ('quack/',),
 
972
            'error', ('NotStacked',),)
 
973
        client.add_expected_call(
 
974
            'Branch.lock_write', ('quack/', '', ''),
 
975
            'error', ('UnlockableTransport',))
693
976
        transport.mkdir('quack')
694
977
        transport = transport.clone('quack')
695
 
        # we do not want bzrdir to make any remote calls
696
 
        bzrdir = RemoteBzrDir(transport, _client=False)
697
 
        branch = RemoteBranch(bzrdir, None, _client=client)
 
978
        branch = self.make_remote_branch(transport, client)
698
979
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
699
 
        self.assertEqual(
700
 
            [('call', 'Branch.lock_write', ('quack/', '', ''))],
701
 
            client._calls)
 
980
        client.finished_test()
702
981
 
703
982
 
704
983
class TestTransportIsReadonly(tests.TestCase):
740
1019
            client._calls)
741
1020
 
742
1021
 
 
1022
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
 
1023
 
 
1024
    def test_defaults_to_none(self):
 
1025
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
1026
        self.assertIs(None, t._get_credentials()[0])
 
1027
 
 
1028
    def test_uses_authentication_config(self):
 
1029
        conf = config.AuthenticationConfig()
 
1030
        conf._get_config().update(
 
1031
            {'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
 
1032
            'example.com'}})
 
1033
        conf._save()
 
1034
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
1035
        self.assertEqual('bar', t._get_credentials()[0])
 
1036
 
 
1037
 
743
1038
class TestRemoteRepository(tests.TestCase):
744
1039
    """Base for testing RemoteRepository protocol usage.
745
1040
    
883
1178
        repo, client = self.setup_fake_client_and_repository(transport_path)
884
1179
        client.add_unknown_method_response('Repository,get_parent_map')
885
1180
        client.add_success_response_with_body('', 'ok')
886
 
        self.assertTrue(client._medium._remote_is_at_least_1_2)
 
1181
        self.assertFalse(client._medium._is_remote_before((1, 2)))
887
1182
        rev_id = 'revision-id'
888
1183
        expected_deprecations = [
889
1184
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
898
1193
              ('quack/', ''))],
899
1194
            client._calls)
900
1195
        # The medium is now marked as being connected to an older server
901
 
        self.assertFalse(client._medium._remote_is_at_least_1_2)
 
1196
        self.assertTrue(client._medium._is_remote_before((1, 2)))
902
1197
 
903
1198
    def test_get_parent_map_fallback_parentless_node(self):
904
1199
        """get_parent_map falls back to get_revision_graph on old servers.  The
915
1210
        transport_path = 'quack'
916
1211
        repo, client = self.setup_fake_client_and_repository(transport_path)
917
1212
        client.add_success_response_with_body(rev_id, 'ok')
918
 
        client._medium._remote_is_at_least_1_2 = False
 
1213
        client._medium._remember_remote_is_before((1, 2))
919
1214
        expected_deprecations = [
920
1215
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
921
1216
            'in version 1.4.']
1002
1297
        transport_path = 'sinhala'
1003
1298
        repo, client = self.setup_fake_client_and_repository(transport_path)
1004
1299
        client.add_error_response('AnUnexpectedError')
1005
 
        e = self.assertRaises(errors.ErrorFromSmartServer,
 
1300
        e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1006
1301
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1007
1302
        self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1008
1303
 
1155
1450
        src_repo.copy_content_into(dest_repo)
1156
1451
 
1157
1452
 
1158
 
class TestRepositoryStreamKnitData(TestRemoteRepository):
1159
 
 
1160
 
    def make_pack_file(self, records):
1161
 
        pack_file = StringIO()
1162
 
        pack_writer = pack.ContainerWriter(pack_file.write)
1163
 
        pack_writer.begin()
1164
 
        for bytes, names in records:
1165
 
            pack_writer.add_bytes_record(bytes, names)
1166
 
        pack_writer.end()
1167
 
        pack_file.seek(0)
1168
 
        return pack_file
1169
 
 
1170
 
    def make_pack_stream(self, records):
1171
 
        pack_serialiser = pack.ContainerSerialiser()
1172
 
        yield pack_serialiser.begin()
1173
 
        for bytes, names in records:
1174
 
            yield pack_serialiser.bytes_record(bytes, names)
1175
 
        yield pack_serialiser.end()
1176
 
 
1177
 
    def test_bad_pack_from_server(self):
1178
 
        """A response with invalid data (e.g. it has a record with multiple
1179
 
        names) triggers an exception.
 
1453
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
 
1454
    """Base class for unit tests for bzrlib.remote._translate_error."""
 
1455
 
 
1456
    def translateTuple(self, error_tuple, **context):
 
1457
        """Call _translate_error with an ErrorFromSmartServer built from the
 
1458
        given error_tuple.
 
1459
 
 
1460
        :param error_tuple: A tuple of a smart server response, as would be
 
1461
            passed to an ErrorFromSmartServer.
 
1462
        :kwargs context: context items to call _translate_error with.
 
1463
 
 
1464
        :returns: The error raised by _translate_error.
 
1465
        """
 
1466
        # Raise the ErrorFromSmartServer before passing it as an argument,
 
1467
        # because _translate_error may need to re-raise it with a bare 'raise'
 
1468
        # statement.
 
1469
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
1470
        translated_error = self.translateErrorFromSmartServer(
 
1471
            server_error, **context)
 
1472
        return translated_error
 
1473
 
 
1474
    def translateErrorFromSmartServer(self, error_object, **context):
 
1475
        """Like translateTuple, but takes an already constructed
 
1476
        ErrorFromSmartServer rather than a tuple.
 
1477
        """
 
1478
        try:
 
1479
            raise error_object
 
1480
        except errors.ErrorFromSmartServer, server_error:
 
1481
            translated_error = self.assertRaises(
 
1482
                errors.BzrError, remote._translate_error, server_error,
 
1483
                **context)
 
1484
        return translated_error
 
1485
 
 
1486
    
 
1487
class TestErrorTranslationSuccess(TestErrorTranslationBase):
 
1488
    """Unit tests for bzrlib.remote._translate_error.
 
1489
    
 
1490
    Given an ErrorFromSmartServer (which has an error tuple from a smart
 
1491
    server) and some context, _translate_error raises more specific errors from
 
1492
    bzrlib.errors.
 
1493
 
 
1494
    This test case covers the cases where _translate_error succeeds in
 
1495
    translating an ErrorFromSmartServer to something better.  See
 
1496
    TestErrorTranslationRobustness for other cases.
 
1497
    """
 
1498
 
 
1499
    def test_NoSuchRevision(self):
 
1500
        branch = self.make_branch('')
 
1501
        revid = 'revid'
 
1502
        translated_error = self.translateTuple(
 
1503
            ('NoSuchRevision', revid), branch=branch)
 
1504
        expected_error = errors.NoSuchRevision(branch, revid)
 
1505
        self.assertEqual(expected_error, translated_error)
 
1506
 
 
1507
    def test_nosuchrevision(self):
 
1508
        repository = self.make_repository('')
 
1509
        revid = 'revid'
 
1510
        translated_error = self.translateTuple(
 
1511
            ('nosuchrevision', revid), repository=repository)
 
1512
        expected_error = errors.NoSuchRevision(repository, revid)
 
1513
        self.assertEqual(expected_error, translated_error)
 
1514
 
 
1515
    def test_nobranch(self):
 
1516
        bzrdir = self.make_bzrdir('')
 
1517
        translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
 
1518
        expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
 
1519
        self.assertEqual(expected_error, translated_error)
 
1520
 
 
1521
    def test_LockContention(self):
 
1522
        translated_error = self.translateTuple(('LockContention',))
 
1523
        expected_error = errors.LockContention('(remote lock)')
 
1524
        self.assertEqual(expected_error, translated_error)
 
1525
 
 
1526
    def test_UnlockableTransport(self):
 
1527
        bzrdir = self.make_bzrdir('')
 
1528
        translated_error = self.translateTuple(
 
1529
            ('UnlockableTransport',), bzrdir=bzrdir)
 
1530
        expected_error = errors.UnlockableTransport(bzrdir.root_transport)
 
1531
        self.assertEqual(expected_error, translated_error)
 
1532
 
 
1533
    def test_LockFailed(self):
 
1534
        lock = 'str() of a server lock'
 
1535
        why = 'str() of why'
 
1536
        translated_error = self.translateTuple(('LockFailed', lock, why))
 
1537
        expected_error = errors.LockFailed(lock, why)
 
1538
        self.assertEqual(expected_error, translated_error)
 
1539
 
 
1540
    def test_TokenMismatch(self):
 
1541
        token = 'a lock token'
 
1542
        translated_error = self.translateTuple(('TokenMismatch',), token=token)
 
1543
        expected_error = errors.TokenMismatch(token, '(remote token)')
 
1544
        self.assertEqual(expected_error, translated_error)
 
1545
 
 
1546
    def test_Diverged(self):
 
1547
        branch = self.make_branch('a')
 
1548
        other_branch = self.make_branch('b')
 
1549
        translated_error = self.translateTuple(
 
1550
            ('Diverged',), branch=branch, other_branch=other_branch)
 
1551
        expected_error = errors.DivergedBranches(branch, other_branch)
 
1552
        self.assertEqual(expected_error, translated_error)
 
1553
 
 
1554
 
 
1555
class TestErrorTranslationRobustness(TestErrorTranslationBase):
 
1556
    """Unit tests for bzrlib.remote._translate_error's robustness.
 
1557
    
 
1558
    TestErrorTranslationSuccess is for cases where _translate_error can
 
1559
    translate successfully.  This class about how _translate_err behaves when
 
1560
    it fails to translate: it re-raises the original error.
 
1561
    """
 
1562
 
 
1563
    def test_unrecognised_server_error(self):
 
1564
        """If the error code from the server is not recognised, the original
 
1565
        ErrorFromSmartServer is propagated unmodified.
 
1566
        """
 
1567
        error_tuple = ('An unknown error tuple',)
 
1568
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
1569
        translated_error = self.translateErrorFromSmartServer(server_error)
 
1570
        expected_error = errors.UnknownErrorFromSmartServer(server_error)
 
1571
        self.assertEqual(expected_error, translated_error)
 
1572
 
 
1573
    def test_context_missing_a_key(self):
 
1574
        """In case of a bug in the client, or perhaps an unexpected response
 
1575
        from a server, _translate_error returns the original error tuple from
 
1576
        the server and mutters a warning.
 
1577
        """
 
1578
        # To translate a NoSuchRevision error _translate_error needs a 'branch'
 
1579
        # in the context dict.  So let's give it an empty context dict instead
 
1580
        # to exercise its error recovery.
 
1581
        empty_context = {}
 
1582
        error_tuple = ('NoSuchRevision', 'revid')
 
1583
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
1584
        translated_error = self.translateErrorFromSmartServer(server_error)
 
1585
        self.assertEqual(server_error, translated_error)
 
1586
        # In addition to re-raising ErrorFromSmartServer, some debug info has
 
1587
        # been muttered to the log file for developer to look at.
 
1588
        self.assertContainsRe(
 
1589
            self._get_log(keep_log_file=True),
 
1590
            "Missing key 'branch' in context")
1180
1591
        
1181
 
        Not all possible errors will be caught at this stage, but obviously
1182
 
        malformed data should be.
1183
 
        """
1184
 
        record = ('bytes', [('name1',), ('name2',)])
1185
 
        pack_stream = self.make_pack_stream([record])
1186
 
        transport_path = 'quack'
1187
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1188
 
        client.add_success_response_with_body(pack_stream, 'ok')
1189
 
        search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
1190
 
        stream = repo.get_data_stream_for_search(search)
1191
 
        self.assertRaises(errors.SmartProtocolError, list, stream)
 
1592
 
 
1593
class TestStacking(tests.TestCaseWithTransport):
 
1594
    """Tests for operations on stacked remote repositories.
1192
1595
    
1193
 
    def test_backwards_compatibility(self):
1194
 
        """If the server doesn't recognise this request, fallback to VFS."""
1195
 
        repo, client = self.setup_fake_client_and_repository('path')
1196
 
        client.add_unknown_method_response(
1197
 
            'Repository.stream_revisions_chunked')
1198
 
        self.mock_called = False
1199
 
        repo._real_repository = MockRealRepository(self)
1200
 
        search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
1201
 
        repo.get_data_stream_for_search(search)
1202
 
        self.assertTrue(self.mock_called)
1203
 
        self.failIf(client.expecting_body,
1204
 
            "The protocol has been left in an unclean state that will cause "
1205
 
            "TooManyConcurrentRequests errors.")
1206
 
 
1207
 
 
1208
 
class MockRealRepository(object):
1209
 
    """Helper class for TestRepositoryStreamKnitData.test_unknown_method."""
1210
 
 
1211
 
    def __init__(self, test):
1212
 
        self.test = test
1213
 
 
1214
 
    def get_data_stream_for_search(self, search):
1215
 
        self.test.assertEqual(set(['revid']), search.get_keys())
1216
 
        self.test.mock_called = True
1217
 
 
1218
 
 
 
1596
    The underlying format type must support stacking.
 
1597
    """
 
1598
 
 
1599
    def test_access_stacked_remote(self):
 
1600
        # based on <http://launchpad.net/bugs/261315>
 
1601
        # make a branch stacked on another repository containing an empty
 
1602
        # revision, then open it over hpss - we should be able to see that
 
1603
        # revision.
 
1604
        base_transport = self.get_transport()
 
1605
        base_builder = self.make_branch_builder('base', format='1.6')
 
1606
        base_builder.start_series()
 
1607
        base_revid = base_builder.build_snapshot('rev-id', None,
 
1608
            [('add', ('', None, 'directory', None))],
 
1609
            'message')
 
1610
        base_builder.finish_series()
 
1611
        stacked_branch = self.make_branch('stacked', format='1.6')
 
1612
        stacked_branch.set_stacked_on_url('../base')
 
1613
        # start a server looking at this
 
1614
        smart_server = server.SmartTCPServer_for_testing()
 
1615
        smart_server.setUp()
 
1616
        self.addCleanup(smart_server.tearDown)
 
1617
        remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
 
1618
        # can get its branch and repository
 
1619
        remote_branch = remote_bzrdir.open_branch()
 
1620
        remote_repo = remote_branch.repository
 
1621
        remote_repo.lock_read()
 
1622
        try:
 
1623
            # it should have an appropriate fallback repository, which should also
 
1624
            # be a RemoteRepository
 
1625
            self.assertEquals(len(remote_repo._fallback_repositories), 1)
 
1626
            self.assertIsInstance(remote_repo._fallback_repositories[0],
 
1627
                RemoteRepository)
 
1628
            # and it has the revision committed to the underlying repository;
 
1629
            # these have varying implementations so we try several of them
 
1630
            self.assertTrue(remote_repo.has_revisions([base_revid]))
 
1631
            self.assertTrue(remote_repo.has_revision(base_revid))
 
1632
            self.assertEqual(remote_repo.get_revision(base_revid).message,
 
1633
                'message')
 
1634
        finally:
 
1635
            remote_repo.unlock()