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."""
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()
427
506
return OldSmartClient()
430
class TestBranchLastRevisionInfo(tests.TestCase):
509
class RemoteBranchTestCase(tests.TestCase):
511
def make_remote_branch(self, transport, client):
512
"""Make a RemoteBranch using 'client' as its _SmartClient.
514
A RemoteBzrDir and RemoteRepository will also be created to fill out
515
the RemoteBranch, albeit with stub values for some of their attributes.
517
# we do not want bzrdir to make any remote calls, so use False as its
518
# _client. If it tries to make a remote call, this will fail
520
bzrdir = RemoteBzrDir(transport, _client=False)
521
repo = RemoteRepository(bzrdir, None, _client=client)
522
return RemoteBranch(bzrdir, repo, _client=client)
525
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
432
527
def test_empty_branch(self):
433
528
# in an empty branch we decode the response properly
434
529
transport = MemoryTransport()
435
530
client = FakeClient(transport.base)
436
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:'))
437
537
transport.mkdir('quack')
438
538
transport = transport.clone('quack')
439
# we do not want bzrdir to make any remote calls
440
bzrdir = RemoteBzrDir(transport, _client=False)
441
branch = RemoteBranch(bzrdir, None, _client=client)
539
branch = self.make_remote_branch(transport, client)
442
540
result = branch.last_revision_info()
445
[('call', 'Branch.last_revision_info', ('quack/',))],
541
client.finished_test()
447
542
self.assertEqual((0, NULL_REVISION), result)
449
544
def test_non_empty_branch(self):
451
546
revid = u'\xc8'.encode('utf8')
452
547
transport = MemoryTransport()
453
548
client = FakeClient(transport.base)
454
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))
455
555
transport.mkdir('kwaak')
456
556
transport = transport.clone('kwaak')
457
# we do not want bzrdir to make any remote calls
458
bzrdir = RemoteBzrDir(transport, _client=False)
459
branch = RemoteBranch(bzrdir, None, _client=client)
557
branch = self.make_remote_branch(transport, client)
460
558
result = branch.last_revision_info()
463
[('call', 'Branch.last_revision_info', ('kwaak/',))],
465
559
self.assertEqual((2, revid), result)
468
class TestBranchSetLastRevision(tests.TestCase):
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))
640
class TestBranchSetLastRevision(RemoteBranchTestCase):
470
642
def test_set_empty(self):
471
643
# set_revision_history([]) is translated to calling
475
647
transport = transport.clone('branch')
477
649
client = FakeClient(transport.base)
479
client.add_success_response('ok', 'branch token', 'repo token')
481
client.add_success_response('ok')
483
client.add_success_response('ok')
484
bzrdir = RemoteBzrDir(transport, _client=False)
485
branch = RemoteBranch(bzrdir, None, _client=client)
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'),
662
branch = self.make_remote_branch(transport, client)
486
663
# This is a hack to work around the problem that RemoteBranch currently
487
664
# unnecessarily invokes _ensure_real upon a call to lock_write.
488
665
branch._ensure_real = lambda: None
489
666
branch.lock_write()
491
667
result = branch.set_revision_history([])
493
[('call', 'Branch.set_last_revision',
494
('branch/', 'branch token', 'repo token', 'null:'))],
497
669
self.assertEqual(None, result)
670
client.finished_test()
499
672
def test_set_nonempty(self):
500
673
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
504
677
transport = transport.clone('branch')
506
679
client = FakeClient(transport.base)
508
client.add_success_response('ok', 'branch token', 'repo token')
510
client.add_success_response('ok')
512
client.add_success_response('ok')
513
bzrdir = RemoteBzrDir(transport, _client=False)
514
branch = RemoteBranch(bzrdir, None, _client=client)
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'),
692
branch = self.make_remote_branch(transport, client)
515
693
# This is a hack to work around the problem that RemoteBranch currently
516
694
# unnecessarily invokes _ensure_real upon a call to lock_write.
517
695
branch._ensure_real = lambda: None
518
696
# Lock the branch, reset the record of remote calls.
519
697
branch.lock_write()
522
698
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
524
[('call', 'Branch.set_last_revision',
525
('branch/', 'branch token', 'repo token', 'rev-id2'))],
528
700
self.assertEqual(None, result)
701
client.finished_test()
530
703
def test_no_such_revision(self):
531
704
transport = MemoryTransport()
533
706
transport = transport.clone('branch')
534
707
# A response of 'NoSuchRevision' is translated into an exception.
535
708
client = FakeClient(transport.base)
537
client.add_success_response('ok', 'branch token', 'repo token')
539
client.add_error_response('NoSuchRevision', 'rev-id')
541
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'),
543
bzrdir = RemoteBzrDir(transport, _client=False)
544
repo = RemoteRepository(bzrdir, None, _client=client)
545
branch = RemoteBranch(bzrdir, repo, _client=client)
546
branch._ensure_real = lambda: None
722
branch = self.make_remote_branch(transport, client)
547
723
branch.lock_write()
550
724
self.assertRaises(
551
725
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
727
client.finished_test()
554
729
def test_tip_change_rejected(self):
555
730
"""TipChangeRejected responses cause a TipChangeRejected exception to
559
734
transport.mkdir('branch')
560
735
transport = transport.clone('branch')
561
736
client = FakeClient(transport.base)
563
client.add_success_response('ok', 'branch token', 'repo token')
565
737
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
566
738
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
567
client.add_error_response('TipChangeRejected', rejection_msg_utf8)
569
client.add_success_response('ok')
571
bzrdir = RemoteBzrDir(transport, _client=False)
572
repo = RemoteRepository(bzrdir, None, _client=client)
573
branch = RemoteBranch(bzrdir, repo, _client=client)
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'),
751
branch = self.make_remote_branch(transport, client)
574
752
branch._ensure_real = lambda: None
575
753
branch.lock_write()
576
754
self.addCleanup(branch.unlock)
579
755
# The 'TipChangeRejected' error response triggered by calling
580
756
# set_revision_history causes a TipChangeRejected exception.
581
757
err = self.assertRaises(
668
844
transport.mkdir('branch')
669
845
transport = transport.clone('branch')
670
846
client = FakeClient(transport.base)
671
client.add_unknown_method_response('Branch.set_last_revision_info')
672
bzrdir = RemoteBzrDir(transport, _client=False)
673
branch = RemoteBranch(bzrdir, None, _client=client)
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')
855
branch = self.make_remote_branch(transport, client)
674
856
class StubRealBranch(object):
675
857
def __init__(self):
686
868
# Call set_last_revision_info, and verify it behaved as expected.
687
869
result = branch.set_last_revision_info(1234, 'a-revision-id')
688
870
self.assertEqual(
689
[('call', 'Branch.set_last_revision_info',
690
('branch/', 'branch token', 'repo token',
691
'1234', 'a-revision-id')),],
694
871
[('set_last_revision_info', 1234, 'a-revision-id')],
695
872
real_branch.calls)
873
client.finished_test()
697
875
def test_unexpected_error(self):
698
# 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.
699
879
transport = MemoryTransport()
700
880
transport.mkdir('branch')
701
881
transport = transport.clone('branch')
702
882
client = FakeClient(transport.base)
884
client.add_error_response('NotStacked')
704
886
client.add_success_response('ok', 'branch token', 'repo token')
705
887
# set_last_revision
783
957
## client._calls)
786
class TestBranchLockWrite(tests.TestCase):
960
class TestBranchLockWrite(RemoteBranchTestCase):
788
962
def test_lock_write_unlockable(self):
789
963
transport = MemoryTransport()
790
964
client = FakeClient(transport.base)
791
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',))
792
971
transport.mkdir('quack')
793
972
transport = transport.clone('quack')
794
# we do not want bzrdir to make any remote calls
795
bzrdir = RemoteBzrDir(transport, _client=False)
796
repo = RemoteRepository(bzrdir, None, _client=client)
797
branch = RemoteBranch(bzrdir, repo, _client=client)
973
branch = self.make_remote_branch(transport, client)
798
974
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
800
[('call', 'Branch.lock_write', ('quack/', '', ''))],
975
client.finished_test()
804
978
class TestTransportIsReadonly(tests.TestCase):
1393
1568
self._get_log(keep_log_file=True),
1394
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()