106
107
self.assertStartsWith(str(b), 'RemoteBranch(')
110
class FakeRemoteTransport(object):
111
"""This class provides the minimum support for use in place of a RemoteTransport.
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.
120
_default_url = 'fakeremotetransport://host/path/'
122
def __init__(self, url=None):
124
url = self._default_url
128
return "%r(%r)" % (self.__class__.__name__,
131
def clone(self, relpath):
132
return FakeRemoteTransport(urlutils.join(self.base, relpath))
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"
109
142
class FakeProtocol(object):
110
143
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
144
177
self.responses = []
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))
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))
149
193
def add_success_response(self, *args):
150
194
self.responses.append(('success', args, None))
158
202
def add_unknown_method_response(self, verb):
159
203
self.responses.append(('unknown', verb))
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]))
161
210
def _get_next_response(self):
162
response_tuple = self.responses.pop(0)
212
response_tuple = self.responses.pop(0)
213
except IndexError, e:
214
raise AssertionError("%r didn't expect any more calls"
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
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
227
next_call = self._expected_calls.pop(0)
229
raise AssertionError("%r didn't expect any more calls "
231
% (self, method, args,))
232
if method != next_call[0] or args != next_call[1]:
233
raise AssertionError("%r expected %r%r "
235
% (self, next_call[0], next_call[1], method, args,))
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]
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)
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,
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()
296
[('call', 'BzrDir.open_branch', ('quack/',)),
297
('call', 'BzrDir.find_repositoryV2', ('quack/',))],
299
373
self.assertIsInstance(result, RemoteBranch)
300
374
self.assertEqual(bzrdir, result.bzrdir)
375
client.finished_test()
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()
341
[('call', 'BzrDir.open_branch', ('~hello/',)),
342
('call', 'BzrDir.find_repositoryV2', ('~hello/',))],
422
client.finished_test()
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()
459
[('call', 'Branch.last_revision_info', ('quack/',))],
541
client.finished_test()
461
542
self.assertEqual((0, NULL_REVISION), result)
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()
475
[('call', 'Branch.last_revision_info', ('kwaak/',))],
477
559
self.assertEqual((2, revid), result)
562
class TestBranch_get_stacked_on_url(tests.TestCaseWithMemoryTransport):
563
"""Test Branch._get_stacked_on_url rpc"""
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()
576
'file:///stacked/on', result)
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
606
self.assertEqual(1, len(branch.repository._fallback_repositories))
608
len(branch.repository._real_repository._fallback_repositories))
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
635
self.assertEqual(1, len(branch.repository._fallback_repositories))
637
len(branch.repository._real_repository._fallback_repositories))
480
640
class TestBranchSetLastRevision(RemoteBranchTestCase):
482
642
def test_set_empty(self):
487
647
transport = transport.clone('branch')
489
649
client = FakeClient(transport.base)
491
client.add_success_response('ok', 'branch token', 'repo token')
493
client.add_success_response('ok')
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:',),
659
client.add_expected_call(
660
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
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()
499
667
result = branch.set_revision_history([])
501
[('call', 'Branch.set_last_revision',
502
('branch/', 'branch token', 'repo token', 'null:'))],
505
669
self.assertEqual(None, result)
670
client.finished_test()
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')
514
679
client = FakeClient(transport.base)
516
client.add_success_response('ok', 'branch token', 'repo token')
518
client.add_success_response('ok')
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',),
689
client.add_expected_call(
690
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
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()
526
698
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
528
[('call', 'Branch.set_last_revision',
529
('branch/', 'branch token', 'repo token', 'rev-id2'))],
532
700
self.assertEqual(None, result)
701
client.finished_test()
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)
541
client.add_success_response('ok', 'branch token', 'repo token')
543
client.add_error_response('NoSuchRevision', 'rev-id')
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'),
547
722
branch = self.make_remote_branch(transport, client)
548
723
branch.lock_write()
551
724
self.assertRaises(
552
725
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
727
client.finished_test()
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)
564
client.add_success_response('ok', 'branch token', 'repo token')
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)
570
client.add_success_response('ok')
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'),
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)
577
755
# The 'TipChangeRejected' error response triggered by calling
578
756
# set_revision_history causes a TipChangeRejected exception.
579
757
err = self.assertRaises(
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')),],
685
871
[('set_last_revision_info', 1234, 'a-revision-id')],
686
872
real_branch.calls)
873
client.finished_test()
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)
884
client.add_error_response('NotStacked')
695
886
client.add_success_response('ok', 'branch token', 'repo token')
696
887
# 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)
778
[('call', 'Branch.lock_write', ('quack/', '', ''))],
975
client.finished_test()
782
978
class TestTransportIsReadonly(tests.TestCase):
1372
1568
self._get_log(keep_log_file=True),
1373
1569
"Missing key 'branch' in context")
1572
class TestStacking(tests.TestCaseWithTransport):
1573
"""Tests for operations on stacked remote repositories.
1575
The underlying format type must support stacking.
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
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))],
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()
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],
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,
1614
remote_repo.unlock()