~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
33
33
    remote,
34
34
    repository,
35
35
    tests,
 
36
    urlutils,
36
37
    )
37
38
from bzrlib.branch import Branch
38
39
from bzrlib.bzrdir import BzrDir, BzrDirFormat
106
107
        self.assertStartsWith(str(b), 'RemoteBranch(')
107
108
 
108
109
 
 
110
class FakeRemoteTransport(object):
 
111
    """This class provides the minimum support for use in place of a RemoteTransport.
 
112
    
 
113
    It doesn't actually transmit requests, but rather expects them to be
 
114
    handled by a FakeClient which holds canned responses.  It does not allow
 
115
    any vfs access, therefore is not suitable for testing any operation that
 
116
    will fallback to vfs access.  Backing the test by an instance of this
 
117
    class guarantees that it's - done using non-vfs operations.
 
118
    """
 
119
 
 
120
    _default_url = 'fakeremotetransport://host/path/'
 
121
 
 
122
    def __init__(self, url=None):
 
123
        if url is None:
 
124
            url = self._default_url
 
125
        self.base = url
 
126
 
 
127
    def __repr__(self):
 
128
        return "%r(%r)" % (self.__class__.__name__,
 
129
            self.base)
 
130
 
 
131
    def clone(self, relpath):
 
132
        return FakeRemoteTransport(urlutils.join(self.base, relpath))
 
133
 
 
134
    def get(self, relpath):
 
135
        # only get is specifically stubbed out, because it's usually the first
 
136
        # thing we do.  anything else will fail with an AttributeError.
 
137
        raise AssertionError("%r doesn't support file access to %r"
 
138
            % (self, relpath))
 
139
 
 
140
 
 
141
 
109
142
class FakeProtocol(object):
110
143
    """Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
111
144
 
144
177
        self.responses = []
145
178
        self._calls = []
146
179
        self.expecting_body = False
 
180
        # if non-None, this is the list of expected calls, with only the
 
181
        # method name and arguments included.  the body might be hard to
 
182
        # compute so is not included
 
183
        self._expected_calls = None
147
184
        _SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
148
185
 
 
186
    def add_expected_call(self, call_name, call_args, response_type,
 
187
        response_args, response_body=None):
 
188
        if self._expected_calls is None:
 
189
            self._expected_calls = []
 
190
        self._expected_calls.append((call_name, call_args))
 
191
        self.responses.append((response_type, response_args, response_body))
 
192
 
149
193
    def add_success_response(self, *args):
150
194
        self.responses.append(('success', args, None))
151
195
 
158
202
    def add_unknown_method_response(self, verb):
159
203
        self.responses.append(('unknown', verb))
160
204
 
 
205
    def finished_test(self):
 
206
        if self._expected_calls:
 
207
            raise AssertionError("%r finished but was still expecting %r"
 
208
                % (self, self._expected_calls[0]))
 
209
 
161
210
    def _get_next_response(self):
162
 
        response_tuple = self.responses.pop(0)
 
211
        try:
 
212
            response_tuple = self.responses.pop(0)
 
213
        except IndexError, e:
 
214
            raise AssertionError("%r didn't expect any more calls"
 
215
                % (self,))
163
216
        if response_tuple[0] == 'unknown':
164
217
            raise errors.UnknownSmartMethod(response_tuple[1])
165
218
        elif response_tuple[0] == 'error':
166
219
            raise errors.ErrorFromSmartServer(response_tuple[1])
167
220
        return response_tuple
168
221
 
 
222
    def _check_call(self, method, args):
 
223
        if self._expected_calls is None:
 
224
            # the test should be updated to say what it expects
 
225
            return
 
226
        try:
 
227
            next_call = self._expected_calls.pop(0)
 
228
        except IndexError:
 
229
            raise AssertionError("%r didn't expect any more calls "
 
230
                "but got %r%r"
 
231
                % (self, method, args,))
 
232
        if method != next_call[0] or args != next_call[1]:
 
233
            raise AssertionError("%r expected %r%r "
 
234
                "but got %r%r"
 
235
                % (self, next_call[0], next_call[1], method, args,))
 
236
 
169
237
    def call(self, method, *args):
 
238
        self._check_call(method, args)
170
239
        self._calls.append(('call', method, args))
171
240
        return self._get_next_response()[1]
172
241
 
173
242
    def call_expecting_body(self, method, *args):
 
243
        self._check_call(method, args)
174
244
        self._calls.append(('call_expecting_body', method, args))
175
245
        result = self._get_next_response()
176
246
        self.expecting_body = True
177
247
        return result[1], FakeProtocol(result[2], self)
178
248
 
179
249
    def call_with_body_bytes_expecting_body(self, method, args, body):
 
250
        self._check_call(method, args)
180
251
        self._calls.append(('call_with_body_bytes_expecting_body', method,
181
252
            args, body))
182
253
        result = self._get_next_response()
288
359
        transport.mkdir('quack')
289
360
        transport = transport.clone('quack')
290
361
        client = FakeClient(transport.base)
291
 
        client.add_success_response('ok', '')
292
 
        client.add_success_response('ok', '', 'no', 'no', 'no')
 
362
        client.add_expected_call(
 
363
            'BzrDir.open_branch', ('quack/',),
 
364
            'success', ('ok', ''))
 
365
        client.add_expected_call(
 
366
            'BzrDir.find_repositoryV2', ('quack/',),
 
367
            'success', ('ok', '', 'no', 'no', 'no'))
 
368
        client.add_expected_call(
 
369
            'Branch.get_stacked_on_url', ('quack/',),
 
370
            'error', ('NotStacked',))
293
371
        bzrdir = RemoteBzrDir(transport, _client=client)
294
372
        result = bzrdir.open_branch()
295
 
        self.assertEqual(
296
 
            [('call', 'BzrDir.open_branch', ('quack/',)),
297
 
             ('call', 'BzrDir.find_repositoryV2', ('quack/',))],
298
 
            client._calls)
299
373
        self.assertIsInstance(result, RemoteBranch)
300
374
        self.assertEqual(bzrdir, result.bzrdir)
 
375
        client.finished_test()
301
376
 
302
377
    def test_branch_missing(self):
303
378
        transport = MemoryTransport()
333
408
        # transmitted as "~", not "%7E".
334
409
        transport = RemoteTCPTransport('bzr://localhost/~hello/')
335
410
        client = FakeClient(transport.base)
336
 
        client.add_success_response('ok', '')
337
 
        client.add_success_response('ok', '', 'no', 'no', 'no')
 
411
        client.add_expected_call(
 
412
            'BzrDir.open_branch', ('~hello/',),
 
413
            'success', ('ok', ''))
 
414
        client.add_expected_call(
 
415
            'BzrDir.find_repositoryV2', ('~hello/',),
 
416
            'success', ('ok', '', 'no', 'no', 'no'))
 
417
        client.add_expected_call(
 
418
            'Branch.get_stacked_on_url', ('~hello/',),
 
419
            'error', ('NotStacked',))
338
420
        bzrdir = RemoteBzrDir(transport, _client=client)
339
421
        result = bzrdir.open_branch()
340
 
        self.assertEqual(
341
 
            [('call', 'BzrDir.open_branch', ('~hello/',)),
342
 
             ('call', 'BzrDir.find_repositoryV2', ('~hello/',))],
343
 
            client._calls)
 
422
        client.finished_test()
344
423
 
345
424
    def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
346
425
        transport = MemoryTransport()
449
528
        # in an empty branch we decode the response properly
450
529
        transport = MemoryTransport()
451
530
        client = FakeClient(transport.base)
452
 
        client.add_success_response('ok', '0', 'null:')
 
531
        client.add_expected_call(
 
532
            'Branch.get_stacked_on_url', ('quack/',),
 
533
            'error', ('NotStacked',))
 
534
        client.add_expected_call(
 
535
            'Branch.last_revision_info', ('quack/',),
 
536
            'success', ('ok', '0', 'null:'))
453
537
        transport.mkdir('quack')
454
538
        transport = transport.clone('quack')
455
539
        branch = self.make_remote_branch(transport, client)
456
540
        result = branch.last_revision_info()
457
 
 
458
 
        self.assertEqual(
459
 
            [('call', 'Branch.last_revision_info', ('quack/',))],
460
 
            client._calls)
 
541
        client.finished_test()
461
542
        self.assertEqual((0, NULL_REVISION), result)
462
543
 
463
544
    def test_non_empty_branch(self):
465
546
        revid = u'\xc8'.encode('utf8')
466
547
        transport = MemoryTransport()
467
548
        client = FakeClient(transport.base)
468
 
        client.add_success_response('ok', '2', revid)
 
549
        client.add_expected_call(
 
550
            'Branch.get_stacked_on_url', ('kwaak/',),
 
551
            'error', ('NotStacked',))
 
552
        client.add_expected_call(
 
553
            'Branch.last_revision_info', ('kwaak/',),
 
554
            'success', ('ok', '2', revid))
469
555
        transport.mkdir('kwaak')
470
556
        transport = transport.clone('kwaak')
471
557
        branch = self.make_remote_branch(transport, client)
472
558
        result = branch.last_revision_info()
473
 
 
474
 
        self.assertEqual(
475
 
            [('call', 'Branch.last_revision_info', ('kwaak/',))],
476
 
            client._calls)
477
559
        self.assertEqual((2, revid), result)
478
560
 
479
561
 
 
562
class TestBranch_get_stacked_on_url(tests.TestCaseWithMemoryTransport):
 
563
    """Test Branch._get_stacked_on_url rpc"""
 
564
 
 
565
    def test_get_stacked_on_invalid_url(self):
 
566
        raise tests.KnownFailure('opening a branch requires the server to open the fallback repository')
 
567
        transport = FakeRemoteTransport('fakeremotetransport:///')
 
568
        client = FakeClient(transport.base)
 
569
        client.add_expected_call(
 
570
            'Branch.get_stacked_on_url', ('.',),
 
571
            'success', ('ok', 'file:///stacked/on'))
 
572
        bzrdir = RemoteBzrDir(transport, _client=client)
 
573
        branch = RemoteBranch(bzrdir, None, _client=client)
 
574
        result = branch.get_stacked_on_url()
 
575
        self.assertEqual(
 
576
            'file:///stacked/on', result)
 
577
 
 
578
    def test_backwards_compatible(self):
 
579
        # like with bzr1.6 with no Branch.get_stacked_on_url rpc
 
580
        base_branch = self.make_branch('base', format='1.6')
 
581
        stacked_branch = self.make_branch('stacked', format='1.6')
 
582
        stacked_branch.set_stacked_on_url('../base')
 
583
        client = FakeClient(self.get_url())
 
584
        client.add_expected_call(
 
585
            'BzrDir.open_branch', ('stacked/',),
 
586
            'success', ('ok', ''))
 
587
        client.add_expected_call(
 
588
            'BzrDir.find_repositoryV2', ('stacked/',),
 
589
            'success', ('ok', '', 'no', 'no', 'no'))
 
590
        # called twice, once from constructor and then again by us
 
591
        client.add_expected_call(
 
592
            'Branch.get_stacked_on_url', ('stacked/',),
 
593
            'unknown', ('Branch.get_stacked_on_url',))
 
594
        client.add_expected_call(
 
595
            'Branch.get_stacked_on_url', ('stacked/',),
 
596
            'unknown', ('Branch.get_stacked_on_url',))
 
597
        # this will also do vfs access, but that goes direct to the transport
 
598
        # and isn't seen by the FakeClient.
 
599
        bzrdir = RemoteBzrDir(self.get_transport('stacked'), _client=client)
 
600
        branch = bzrdir.open_branch()
 
601
        result = branch.get_stacked_on_url()
 
602
        self.assertEqual('../base', result)
 
603
        client.finished_test()
 
604
        # it's in the fallback list both for the RemoteRepository and its vfs
 
605
        # repository
 
606
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
607
        self.assertEqual(1,
 
608
            len(branch.repository._real_repository._fallback_repositories))
 
609
 
 
610
    def test_get_stacked_on_real_branch(self):
 
611
        base_branch = self.make_branch('base', format='1.6')
 
612
        stacked_branch = self.make_branch('stacked', format='1.6')
 
613
        stacked_branch.set_stacked_on_url('../base')
 
614
        client = FakeClient(self.get_url())
 
615
        client.add_expected_call(
 
616
            'BzrDir.open_branch', ('stacked/',),
 
617
            'success', ('ok', ''))
 
618
        client.add_expected_call(
 
619
            'BzrDir.find_repositoryV2', ('stacked/',),
 
620
            'success', ('ok', '', 'no', 'no', 'no'))
 
621
        # called twice, once from constructor and then again by us
 
622
        client.add_expected_call(
 
623
            'Branch.get_stacked_on_url', ('stacked/',),
 
624
            'success', ('ok', '../base'))
 
625
        client.add_expected_call(
 
626
            'Branch.get_stacked_on_url', ('stacked/',),
 
627
            'success', ('ok', '../base'))
 
628
        bzrdir = RemoteBzrDir(self.get_transport('stacked'), _client=client)
 
629
        branch = bzrdir.open_branch()
 
630
        result = branch.get_stacked_on_url()
 
631
        self.assertEqual('../base', result)
 
632
        client.finished_test()
 
633
        # it's in the fallback list both for the RemoteRepository and its vfs
 
634
        # repository
 
635
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
636
        self.assertEqual(1,
 
637
            len(branch.repository._real_repository._fallback_repositories))
 
638
 
 
639
 
480
640
class TestBranchSetLastRevision(RemoteBranchTestCase):
481
641
 
482
642
    def test_set_empty(self):
487
647
        transport = transport.clone('branch')
488
648
 
489
649
        client = FakeClient(transport.base)
490
 
        # lock_write
491
 
        client.add_success_response('ok', 'branch token', 'repo token')
492
 
        # set_last_revision
493
 
        client.add_success_response('ok')
494
 
        # unlock
495
 
        client.add_success_response('ok')
 
650
        client.add_expected_call(
 
651
            'Branch.get_stacked_on_url', ('branch/',),
 
652
            'error', ('NotStacked',))
 
653
        client.add_expected_call(
 
654
            'Branch.lock_write', ('branch/', '', ''),
 
655
            'success', ('ok', 'branch token', 'repo token'))
 
656
        client.add_expected_call(
 
657
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
 
658
            'success', ('ok',))
 
659
        client.add_expected_call(
 
660
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
661
            'success', ('ok',))
496
662
        branch = self.make_remote_branch(transport, client)
 
663
        # This is a hack to work around the problem that RemoteBranch currently
 
664
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
665
        branch._ensure_real = lambda: None
497
666
        branch.lock_write()
498
 
        client._calls = []
499
667
        result = branch.set_revision_history([])
500
 
        self.assertEqual(
501
 
            [('call', 'Branch.set_last_revision',
502
 
                ('branch/', 'branch token', 'repo token', 'null:'))],
503
 
            client._calls)
504
668
        branch.unlock()
505
669
        self.assertEqual(None, result)
 
670
        client.finished_test()
506
671
 
507
672
    def test_set_nonempty(self):
508
673
        # set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
512
677
        transport = transport.clone('branch')
513
678
 
514
679
        client = FakeClient(transport.base)
515
 
        # lock_write
516
 
        client.add_success_response('ok', 'branch token', 'repo token')
517
 
        # set_last_revision
518
 
        client.add_success_response('ok')
519
 
        # unlock
520
 
        client.add_success_response('ok')
 
680
        client.add_expected_call(
 
681
            'Branch.get_stacked_on_url', ('branch/',),
 
682
            'error', ('NotStacked',))
 
683
        client.add_expected_call(
 
684
            'Branch.lock_write', ('branch/', '', ''),
 
685
            'success', ('ok', 'branch token', 'repo token'))
 
686
        client.add_expected_call(
 
687
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
 
688
            'success', ('ok',))
 
689
        client.add_expected_call(
 
690
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
691
            'success', ('ok',))
521
692
        branch = self.make_remote_branch(transport, client)
 
693
        # This is a hack to work around the problem that RemoteBranch currently
 
694
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
695
        branch._ensure_real = lambda: None
522
696
        # Lock the branch, reset the record of remote calls.
523
697
        branch.lock_write()
524
 
        client._calls = []
525
 
 
526
698
        result = branch.set_revision_history(['rev-id1', 'rev-id2'])
527
 
        self.assertEqual(
528
 
            [('call', 'Branch.set_last_revision',
529
 
                ('branch/', 'branch token', 'repo token', 'rev-id2'))],
530
 
            client._calls)
531
699
        branch.unlock()
532
700
        self.assertEqual(None, result)
 
701
        client.finished_test()
533
702
 
534
703
    def test_no_such_revision(self):
535
704
        transport = MemoryTransport()
537
706
        transport = transport.clone('branch')
538
707
        # A response of 'NoSuchRevision' is translated into an exception.
539
708
        client = FakeClient(transport.base)
540
 
        # lock_write
541
 
        client.add_success_response('ok', 'branch token', 'repo token')
542
 
        # set_last_revision
543
 
        client.add_error_response('NoSuchRevision', 'rev-id')
544
 
        # unlock
545
 
        client.add_success_response('ok')
 
709
        client.add_expected_call(
 
710
            'Branch.get_stacked_on_url', ('branch/',),
 
711
            'error', ('NotStacked',))
 
712
        client.add_expected_call(
 
713
            'Branch.lock_write', ('branch/', '', ''),
 
714
            'success', ('ok', 'branch token', 'repo token'))
 
715
        client.add_expected_call(
 
716
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
 
717
            'error', ('NoSuchRevision', 'rev-id'))
 
718
        client.add_expected_call(
 
719
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
720
            'success', ('ok',))
546
721
 
547
722
        branch = self.make_remote_branch(transport, client)
548
723
        branch.lock_write()
549
 
        client._calls = []
550
 
 
551
724
        self.assertRaises(
552
725
            errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
553
726
        branch.unlock()
 
727
        client.finished_test()
554
728
 
555
729
    def test_tip_change_rejected(self):
556
730
        """TipChangeRejected responses cause a TipChangeRejected exception to
560
734
        transport.mkdir('branch')
561
735
        transport = transport.clone('branch')
562
736
        client = FakeClient(transport.base)
563
 
        # lock_write
564
 
        client.add_success_response('ok', 'branch token', 'repo token')
565
 
        # set_last_revision
566
737
        rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
567
738
        rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
568
 
        client.add_error_response('TipChangeRejected', rejection_msg_utf8)
569
 
        # unlock
570
 
        client.add_success_response('ok')
571
 
 
 
739
        client.add_expected_call(
 
740
            'Branch.get_stacked_on_url', ('branch/',),
 
741
            'error', ('NotStacked',))
 
742
        client.add_expected_call(
 
743
            'Branch.lock_write', ('branch/', '', ''),
 
744
            'success', ('ok', 'branch token', 'repo token'))
 
745
        client.add_expected_call(
 
746
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
 
747
            'error', ('TipChangeRejected', rejection_msg_utf8))
 
748
        client.add_expected_call(
 
749
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
750
            'success', ('ok',))
572
751
        branch = self.make_remote_branch(transport, client)
 
752
        branch._ensure_real = lambda: None
573
753
        branch.lock_write()
574
754
        self.addCleanup(branch.unlock)
575
 
        client._calls = []
576
 
 
577
755
        # The 'TipChangeRejected' error response triggered by calling
578
756
        # set_revision_history causes a TipChangeRejected exception.
579
757
        err = self.assertRaises(
582
760
        # object.
583
761
        self.assertIsInstance(err.msg, unicode)
584
762
        self.assertEqual(rejection_msg_unicode, err.msg)
 
763
        branch.unlock()
 
764
        client.finished_test()
585
765
 
586
766
 
587
767
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
593
773
        transport.mkdir('branch')
594
774
        transport = transport.clone('branch')
595
775
        client = FakeClient(transport.base)
 
776
        # get_stacked_on_url
 
777
        client.add_error_response('NotStacked')
596
778
        # lock_write
597
779
        client.add_success_response('ok', 'branch token', 'repo token')
598
780
        # set_last_revision
618
800
        transport.mkdir('branch')
619
801
        transport = transport.clone('branch')
620
802
        client = FakeClient(transport.base)
 
803
        # get_stacked_on_url
 
804
        client.add_error_response('NotStacked')
621
805
        # lock_write
622
806
        client.add_success_response('ok', 'branch token', 'repo token')
623
807
        # set_last_revision
660
844
        transport.mkdir('branch')
661
845
        transport = transport.clone('branch')
662
846
        client = FakeClient(transport.base)
663
 
        client.add_unknown_method_response('Branch.set_last_revision_info')
 
847
        client.add_expected_call(
 
848
            'Branch.get_stacked_on_url', ('branch/',),
 
849
            'error', ('NotStacked',))
 
850
        client.add_expected_call(
 
851
            'Branch.set_last_revision_info',
 
852
            ('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
 
853
            'unknown', 'Branch.set_last_revision_info')
 
854
 
664
855
        branch = self.make_remote_branch(transport, client)
665
856
        class StubRealBranch(object):
666
857
            def __init__(self):
677
868
        # Call set_last_revision_info, and verify it behaved as expected.
678
869
        result = branch.set_last_revision_info(1234, 'a-revision-id')
679
870
        self.assertEqual(
680
 
            [('call', 'Branch.set_last_revision_info',
681
 
                ('branch/', 'branch token', 'repo token',
682
 
                 '1234', 'a-revision-id')),],
683
 
            client._calls)
684
 
        self.assertEqual(
685
871
            [('set_last_revision_info', 1234, 'a-revision-id')],
686
872
            real_branch.calls)
 
873
        client.finished_test()
687
874
 
688
875
    def test_unexpected_error(self):
689
 
        # A response of 'NoSuchRevision' is translated into an exception.
 
876
        # If the server sends an error the client doesn't understand, it gets
 
877
        # turned into an UnknownErrorFromSmartServer, which is presented as a
 
878
        # non-internal error to the user.
690
879
        transport = MemoryTransport()
691
880
        transport.mkdir('branch')
692
881
        transport = transport.clone('branch')
693
882
        client = FakeClient(transport.base)
 
883
        # get_stacked_on_url
 
884
        client.add_error_response('NotStacked')
694
885
        # lock_write
695
886
        client.add_success_response('ok', 'branch token', 'repo token')
696
887
        # set_last_revision
717
908
        transport.mkdir('branch')
718
909
        transport = transport.clone('branch')
719
910
        client = FakeClient(transport.base)
 
911
        # get_stacked_on_url
 
912
        client.add_error_response('NotStacked')
720
913
        # lock_write
721
914
        client.add_success_response('ok', 'branch token', 'repo token')
722
915
        # set_last_revision
769
962
    def test_lock_write_unlockable(self):
770
963
        transport = MemoryTransport()
771
964
        client = FakeClient(transport.base)
772
 
        client.add_error_response('UnlockableTransport')
 
965
        client.add_expected_call(
 
966
            'Branch.get_stacked_on_url', ('quack/',),
 
967
            'error', ('NotStacked',),)
 
968
        client.add_expected_call(
 
969
            'Branch.lock_write', ('quack/', '', ''),
 
970
            'error', ('UnlockableTransport',))
773
971
        transport.mkdir('quack')
774
972
        transport = transport.clone('quack')
775
973
        branch = self.make_remote_branch(transport, client)
776
974
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
777
 
        self.assertEqual(
778
 
            [('call', 'Branch.lock_write', ('quack/', '', ''))],
779
 
            client._calls)
 
975
        client.finished_test()
780
976
 
781
977
 
782
978
class TestTransportIsReadonly(tests.TestCase):
1372
1568
            self._get_log(keep_log_file=True),
1373
1569
            "Missing key 'branch' in context")
1374
1570
        
 
1571
 
 
1572
class TestStacking(tests.TestCaseWithTransport):
 
1573
    """Tests for operations on stacked remote repositories.
 
1574
    
 
1575
    The underlying format type must support stacking.
 
1576
    """
 
1577
 
 
1578
    def test_access_stacked_remote(self):
 
1579
        # based on <http://launchpad.net/bugs/261315>
 
1580
        # make a branch stacked on another repository containing an empty
 
1581
        # revision, then open it over hpss - we should be able to see that
 
1582
        # revision.
 
1583
        base_transport = self.get_transport()
 
1584
        base_builder = self.make_branch_builder('base', format='1.6')
 
1585
        base_builder.start_series()
 
1586
        base_revid = base_builder.build_snapshot('rev-id', None,
 
1587
            [('add', ('', None, 'directory', None))],
 
1588
            'message')
 
1589
        base_builder.finish_series()
 
1590
        stacked_branch = self.make_branch('stacked', format='1.6')
 
1591
        stacked_branch.set_stacked_on_url('../base')
 
1592
        # start a server looking at this
 
1593
        smart_server = server.SmartTCPServer_for_testing()
 
1594
        smart_server.setUp()
 
1595
        self.addCleanup(smart_server.tearDown)
 
1596
        remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
 
1597
        # can get its branch and repository
 
1598
        remote_branch = remote_bzrdir.open_branch()
 
1599
        remote_repo = remote_branch.repository
 
1600
        remote_repo.lock_read()
 
1601
        try:
 
1602
            # it should have an appropriate fallback repository, which should also
 
1603
            # be a RemoteRepository
 
1604
            self.assertEquals(len(remote_repo._fallback_repositories), 1)
 
1605
            self.assertIsInstance(remote_repo._fallback_repositories[0],
 
1606
                RemoteRepository)
 
1607
            # and it has the revision committed to the underlying repository;
 
1608
            # these have varying implementations so we try several of them
 
1609
            self.assertTrue(remote_repo.has_revisions([base_revid]))
 
1610
            self.assertTrue(remote_repo.has_revision(base_revid))
 
1611
            self.assertEqual(remote_repo.get_revision(base_revid).message,
 
1612
                'message')
 
1613
        finally:
 
1614
            remote_repo.unlock()