254
387
'xyz/', scheme + '//host/path', 'xyz/')
257
class TestBzrDirOpenBranch(tests.TestCase):
390
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
391
"""Tests for the behaviour of client_medium.remote_is_at_least."""
393
def test_initially_unlimited(self):
394
"""A fresh medium assumes that the remote side supports all
397
client_medium = medium.SmartClientMedium('dummy base')
398
self.assertFalse(client_medium._is_remote_before((99, 99)))
400
def test__remember_remote_is_before(self):
401
"""Calling _remember_remote_is_before ratchets down the known remote
404
client_medium = medium.SmartClientMedium('dummy base')
405
# Mark the remote side as being less than 1.6. The remote side may
407
client_medium._remember_remote_is_before((1, 6))
408
self.assertTrue(client_medium._is_remote_before((1, 6)))
409
self.assertFalse(client_medium._is_remote_before((1, 5)))
410
# Calling _remember_remote_is_before again with a lower value works.
411
client_medium._remember_remote_is_before((1, 5))
412
self.assertTrue(client_medium._is_remote_before((1, 5)))
413
# You cannot call _remember_remote_is_before with a larger value.
415
AssertionError, client_medium._remember_remote_is_before, (1, 9))
418
class TestBzrDirCloningMetaDir(TestRemote):
420
def test_backwards_compat(self):
421
self.setup_smart_server_with_call_log()
422
a_dir = self.make_bzrdir('.')
423
self.reset_smart_call_log()
424
verb = 'BzrDir.cloning_metadir'
425
self.disable_verb(verb)
426
format = a_dir.cloning_metadir()
427
call_count = len([call for call in self.hpss_calls if
428
call.call.method == verb])
429
self.assertEqual(1, call_count)
431
def test_branch_reference(self):
432
transport = self.get_transport('quack')
433
referenced = self.make_branch('referenced')
434
expected = referenced.bzrdir.cloning_metadir()
435
client = FakeClient(transport.base)
436
client.add_expected_call(
437
'BzrDir.cloning_metadir', ('quack/', 'False'),
438
'error', ('BranchReference',)),
439
client.add_expected_call(
440
'BzrDir.open_branchV2', ('quack/',),
441
'success', ('ref', self.get_url('referenced'))),
442
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
444
result = a_bzrdir.cloning_metadir()
445
# We should have got a control dir matching the referenced branch.
446
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
447
self.assertEqual(expected._repository_format, result._repository_format)
448
self.assertEqual(expected._branch_format, result._branch_format)
449
client.finished_test()
451
def test_current_server(self):
452
transport = self.get_transport('.')
453
transport = transport.clone('quack')
454
self.make_bzrdir('quack')
455
client = FakeClient(transport.base)
456
reference_bzrdir_format = bzrdir.format_registry.get('default')()
457
control_name = reference_bzrdir_format.network_name()
458
client.add_expected_call(
459
'BzrDir.cloning_metadir', ('quack/', 'False'),
460
'success', (control_name, '', ('branch', ''))),
461
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
463
result = a_bzrdir.cloning_metadir()
464
# We should have got a reference control dir with default branch and
465
# repository formats.
466
# This pokes a little, just to be sure.
467
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
468
self.assertEqual(None, result._repository_format)
469
self.assertEqual(None, result._branch_format)
470
client.finished_test()
473
class TestBzrDirOpenBranch(TestRemote):
475
def test_backwards_compat(self):
476
self.setup_smart_server_with_call_log()
477
self.make_branch('.')
478
a_dir = BzrDir.open(self.get_url('.'))
479
self.reset_smart_call_log()
480
verb = 'BzrDir.open_branchV2'
481
self.disable_verb(verb)
482
format = a_dir.open_branch()
483
call_count = len([call for call in self.hpss_calls if
484
call.call.method == verb])
485
self.assertEqual(1, call_count)
259
487
def test_branch_present(self):
488
reference_format = self.get_repo_format()
489
network_name = reference_format.network_name()
490
branch_network_name = self.get_branch_format().network_name()
260
491
transport = MemoryTransport()
261
492
transport.mkdir('quack')
262
493
transport = transport.clone('quack')
263
494
client = FakeClient(transport.base)
264
client.add_success_response('ok', '')
265
client.add_success_response('ok', '', 'no', 'no', 'no')
266
bzrdir = RemoteBzrDir(transport, _client=client)
495
client.add_expected_call(
496
'BzrDir.open_branchV2', ('quack/',),
497
'success', ('branch', branch_network_name))
498
client.add_expected_call(
499
'BzrDir.find_repositoryV3', ('quack/',),
500
'success', ('ok', '', 'no', 'no', 'no', network_name))
501
client.add_expected_call(
502
'Branch.get_stacked_on_url', ('quack/',),
503
'error', ('NotStacked',))
504
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
267
506
result = bzrdir.open_branch()
269
[('call', 'BzrDir.open_branch', ('quack/',)),
270
('call', 'BzrDir.find_repositoryV2', ('quack/',))],
272
507
self.assertIsInstance(result, RemoteBranch)
273
508
self.assertEqual(bzrdir, result.bzrdir)
509
client.finished_test()
275
511
def test_branch_missing(self):
276
512
transport = MemoryTransport()
355
605
RemoteBzrDirFormat.probe_transport, OldServerTransport())
358
class TestBzrDirOpenRepository(tests.TestCase):
360
def test_backwards_compat_1_2(self):
361
transport = MemoryTransport()
362
transport.mkdir('quack')
363
transport = transport.clone('quack')
364
client = FakeClient(transport.base)
365
client.add_unknown_method_response('RemoteRepository.find_repositoryV2')
608
class TestBzrDirCreateBranch(TestRemote):
610
def test_backwards_compat(self):
611
self.setup_smart_server_with_call_log()
612
repo = self.make_repository('.')
613
self.reset_smart_call_log()
614
self.disable_verb('BzrDir.create_branch')
615
branch = repo.bzrdir.create_branch()
616
create_branch_call_count = len([call for call in self.hpss_calls if
617
call.call.method == 'BzrDir.create_branch'])
618
self.assertEqual(1, create_branch_call_count)
620
def test_current_server(self):
621
transport = self.get_transport('.')
622
transport = transport.clone('quack')
623
self.make_repository('quack')
624
client = FakeClient(transport.base)
625
reference_bzrdir_format = bzrdir.format_registry.get('default')()
626
reference_format = reference_bzrdir_format.get_branch_format()
627
network_name = reference_format.network_name()
628
reference_repo_fmt = reference_bzrdir_format.repository_format
629
reference_repo_name = reference_repo_fmt.network_name()
630
client.add_expected_call(
631
'BzrDir.create_branch', ('quack/', network_name),
632
'success', ('ok', network_name, '', 'no', 'no', 'yes',
633
reference_repo_name))
634
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
636
branch = a_bzrdir.create_branch()
637
# We should have got a remote branch
638
self.assertIsInstance(branch, remote.RemoteBranch)
639
# its format should have the settings from the response
640
format = branch._format
641
self.assertEqual(network_name, format.network_name())
644
class TestBzrDirCreateRepository(TestRemote):
646
def test_backwards_compat(self):
647
self.setup_smart_server_with_call_log()
648
bzrdir = self.make_bzrdir('.')
649
self.reset_smart_call_log()
650
self.disable_verb('BzrDir.create_repository')
651
repo = bzrdir.create_repository()
652
create_repo_call_count = len([call for call in self.hpss_calls if
653
call.call.method == 'BzrDir.create_repository'])
654
self.assertEqual(1, create_repo_call_count)
656
def test_current_server(self):
657
transport = self.get_transport('.')
658
transport = transport.clone('quack')
659
self.make_bzrdir('quack')
660
client = FakeClient(transport.base)
661
reference_bzrdir_format = bzrdir.format_registry.get('default')()
662
reference_format = reference_bzrdir_format.repository_format
663
network_name = reference_format.network_name()
664
client.add_expected_call(
665
'BzrDir.create_repository', ('quack/',
666
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
667
'success', ('ok', 'no', 'no', 'no', network_name))
668
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
670
repo = a_bzrdir.create_repository()
671
# We should have got a remote repository
672
self.assertIsInstance(repo, remote.RemoteRepository)
673
# its format should have the settings from the response
674
format = repo._format
675
self.assertFalse(format.rich_root_data)
676
self.assertFalse(format.supports_tree_reference)
677
self.assertFalse(format.supports_external_lookups)
678
self.assertEqual(network_name, format.network_name())
681
class TestBzrDirOpenRepository(TestRemote):
683
def test_backwards_compat_1_2_3(self):
684
# fallback all the way to the first version.
685
reference_format = self.get_repo_format()
686
network_name = reference_format.network_name()
687
client = FakeClient('bzr://example.com/')
688
client.add_unknown_method_response('BzrDir.find_repositoryV3')
689
client.add_unknown_method_response('BzrDir.find_repositoryV2')
366
690
client.add_success_response('ok', '', 'no', 'no')
367
bzrdir = RemoteBzrDir(transport, _client=client)
368
repo = bzrdir.open_repository()
370
[('call', 'BzrDir.find_repositoryV2', ('quack/',)),
371
('call', 'BzrDir.find_repository', ('quack/',))],
691
# A real repository instance will be created to determine the network
693
client.add_success_response_with_body(
694
"Bazaar-NG meta directory, format 1\n", 'ok')
695
client.add_success_response_with_body(
696
reference_format.get_format_string(), 'ok')
697
# PackRepository wants to do a stat
698
client.add_success_response('stat', '0', '65535')
699
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
701
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
703
repo = bzrdir.open_repository()
705
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
706
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
707
('call', 'BzrDir.find_repository', ('quack/',)),
708
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
709
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
710
('call', 'stat', ('/quack/.bzr/repository',)),
713
self.assertEqual(network_name, repo._format.network_name())
715
def test_backwards_compat_2(self):
716
# fallback to find_repositoryV2
717
reference_format = self.get_repo_format()
718
network_name = reference_format.network_name()
719
client = FakeClient('bzr://example.com/')
720
client.add_unknown_method_response('BzrDir.find_repositoryV3')
721
client.add_success_response('ok', '', 'no', 'no', 'no')
722
# A real repository instance will be created to determine the network
724
client.add_success_response_with_body(
725
"Bazaar-NG meta directory, format 1\n", 'ok')
726
client.add_success_response_with_body(
727
reference_format.get_format_string(), 'ok')
728
# PackRepository wants to do a stat
729
client.add_success_response('stat', '0', '65535')
730
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
732
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
734
repo = bzrdir.open_repository()
736
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
737
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
738
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
739
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
740
('call', 'stat', ('/quack/.bzr/repository',)),
743
self.assertEqual(network_name, repo._format.network_name())
745
def test_current_server(self):
746
reference_format = self.get_repo_format()
747
network_name = reference_format.network_name()
748
transport = MemoryTransport()
749
transport.mkdir('quack')
750
transport = transport.clone('quack')
751
client = FakeClient(transport.base)
752
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
753
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
755
repo = bzrdir.open_repository()
757
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
759
self.assertEqual(network_name, repo._format.network_name())
762
class TestBzrDirFormatInitializeEx(TestRemote):
764
def test_success(self):
765
"""Simple test for typical successful call."""
766
fmt = bzrdir.RemoteBzrDirFormat()
767
default_format_name = BzrDirFormat.get_default_format().network_name()
768
transport = self.get_transport()
769
client = FakeClient(transport.base)
770
client.add_expected_call(
771
'BzrDirFormat.initialize_ex_1.16',
772
(default_format_name, 'path', 'False', 'False', 'False', '',
773
'', '', '', 'False'),
775
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
776
'bzrdir fmt', 'False', '', '', 'repo lock token'))
777
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
778
# it's currently hard to test that without supplying a real remote
779
# transport connected to a real server.
780
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
781
transport, False, False, False, None, None, None, None, False)
782
client.finished_test()
784
def test_error(self):
785
"""Error responses are translated, e.g. 'PermissionDenied' raises the
786
corresponding error from the client.
788
fmt = bzrdir.RemoteBzrDirFormat()
789
default_format_name = BzrDirFormat.get_default_format().network_name()
790
transport = self.get_transport()
791
client = FakeClient(transport.base)
792
client.add_expected_call(
793
'BzrDirFormat.initialize_ex_1.16',
794
(default_format_name, 'path', 'False', 'False', 'False', '',
795
'', '', '', 'False'),
797
('PermissionDenied', 'path', 'extra info'))
798
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
799
# it's currently hard to test that without supplying a real remote
800
# transport connected to a real server.
801
err = self.assertRaises(errors.PermissionDenied,
802
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
803
False, False, False, None, None, None, None, False)
804
self.assertEqual('path', err.path)
805
self.assertEqual(': extra info', err.extra)
806
client.finished_test()
808
def test_error_from_real_server(self):
809
"""Integration test for error translation."""
810
transport = self.make_smart_server('foo')
811
transport = transport.clone('no-such-path')
812
fmt = bzrdir.RemoteBzrDirFormat()
813
err = self.assertRaises(errors.NoSuchFile,
814
fmt.initialize_on_transport_ex, transport, create_prefix=False)
375
817
class OldSmartClient(object):
400
842
return OldSmartClient()
403
class TestBranchLastRevisionInfo(tests.TestCase):
845
class RemoteBzrDirTestCase(TestRemote):
847
def make_remote_bzrdir(self, transport, client):
848
"""Make a RemotebzrDir using 'client' as the _client."""
849
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
853
class RemoteBranchTestCase(RemoteBzrDirTestCase):
855
def make_remote_branch(self, transport, client):
856
"""Make a RemoteBranch using 'client' as its _SmartClient.
858
A RemoteBzrDir and RemoteRepository will also be created to fill out
859
the RemoteBranch, albeit with stub values for some of their attributes.
861
# we do not want bzrdir to make any remote calls, so use False as its
862
# _client. If it tries to make a remote call, this will fail
864
bzrdir = self.make_remote_bzrdir(transport, False)
865
repo = RemoteRepository(bzrdir, None, _client=client)
866
branch_format = self.get_branch_format()
867
format = RemoteBranchFormat(network_name=branch_format.network_name())
868
return RemoteBranch(bzrdir, repo, _client=client, format=format)
871
class TestBranchGetParent(RemoteBranchTestCase):
873
def test_no_parent(self):
874
# in an empty branch we decode the response properly
875
transport = MemoryTransport()
876
client = FakeClient(transport.base)
877
client.add_expected_call(
878
'Branch.get_stacked_on_url', ('quack/',),
879
'error', ('NotStacked',))
880
client.add_expected_call(
881
'Branch.get_parent', ('quack/',),
883
transport.mkdir('quack')
884
transport = transport.clone('quack')
885
branch = self.make_remote_branch(transport, client)
886
result = branch.get_parent()
887
client.finished_test()
888
self.assertEqual(None, result)
890
def test_parent_relative(self):
891
transport = MemoryTransport()
892
client = FakeClient(transport.base)
893
client.add_expected_call(
894
'Branch.get_stacked_on_url', ('kwaak/',),
895
'error', ('NotStacked',))
896
client.add_expected_call(
897
'Branch.get_parent', ('kwaak/',),
898
'success', ('../foo/',))
899
transport.mkdir('kwaak')
900
transport = transport.clone('kwaak')
901
branch = self.make_remote_branch(transport, client)
902
result = branch.get_parent()
903
self.assertEqual(transport.clone('../foo').base, result)
905
def test_parent_absolute(self):
906
transport = MemoryTransport()
907
client = FakeClient(transport.base)
908
client.add_expected_call(
909
'Branch.get_stacked_on_url', ('kwaak/',),
910
'error', ('NotStacked',))
911
client.add_expected_call(
912
'Branch.get_parent', ('kwaak/',),
913
'success', ('http://foo/',))
914
transport.mkdir('kwaak')
915
transport = transport.clone('kwaak')
916
branch = self.make_remote_branch(transport, client)
917
result = branch.get_parent()
918
self.assertEqual('http://foo/', result)
919
client.finished_test()
922
class TestBranchSetParentLocation(RemoteBranchTestCase):
924
def test_no_parent(self):
925
# We call the verb when setting parent to None
926
transport = MemoryTransport()
927
client = FakeClient(transport.base)
928
client.add_expected_call(
929
'Branch.get_stacked_on_url', ('quack/',),
930
'error', ('NotStacked',))
931
client.add_expected_call(
932
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
934
transport.mkdir('quack')
935
transport = transport.clone('quack')
936
branch = self.make_remote_branch(transport, client)
937
branch._lock_token = 'b'
938
branch._repo_lock_token = 'r'
939
branch._set_parent_location(None)
940
client.finished_test()
942
def test_parent(self):
943
transport = MemoryTransport()
944
client = FakeClient(transport.base)
945
client.add_expected_call(
946
'Branch.get_stacked_on_url', ('kwaak/',),
947
'error', ('NotStacked',))
948
client.add_expected_call(
949
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
951
transport.mkdir('kwaak')
952
transport = transport.clone('kwaak')
953
branch = self.make_remote_branch(transport, client)
954
branch._lock_token = 'b'
955
branch._repo_lock_token = 'r'
956
branch._set_parent_location('foo')
957
client.finished_test()
959
def test_backwards_compat(self):
960
self.setup_smart_server_with_call_log()
961
branch = self.make_branch('.')
962
self.reset_smart_call_log()
963
verb = 'Branch.set_parent_location'
964
self.disable_verb(verb)
965
branch.set_parent('http://foo/')
966
self.assertLength(12, self.hpss_calls)
969
class TestBranchGetTagsBytes(RemoteBranchTestCase):
971
def test_backwards_compat(self):
972
self.setup_smart_server_with_call_log()
973
branch = self.make_branch('.')
974
self.reset_smart_call_log()
975
verb = 'Branch.get_tags_bytes'
976
self.disable_verb(verb)
977
branch.tags.get_tag_dict()
978
call_count = len([call for call in self.hpss_calls if
979
call.call.method == verb])
980
self.assertEqual(1, call_count)
982
def test_trivial(self):
983
transport = MemoryTransport()
984
client = FakeClient(transport.base)
985
client.add_expected_call(
986
'Branch.get_stacked_on_url', ('quack/',),
987
'error', ('NotStacked',))
988
client.add_expected_call(
989
'Branch.get_tags_bytes', ('quack/',),
991
transport.mkdir('quack')
992
transport = transport.clone('quack')
993
branch = self.make_remote_branch(transport, client)
994
result = branch.tags.get_tag_dict()
995
client.finished_test()
996
self.assertEqual({}, result)
999
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
405
1001
def test_empty_branch(self):
406
1002
# in an empty branch we decode the response properly
407
1003
transport = MemoryTransport()
408
1004
client = FakeClient(transport.base)
409
client.add_success_response('ok', '0', 'null:')
1005
client.add_expected_call(
1006
'Branch.get_stacked_on_url', ('quack/',),
1007
'error', ('NotStacked',))
1008
client.add_expected_call(
1009
'Branch.last_revision_info', ('quack/',),
1010
'success', ('ok', '0', 'null:'))
410
1011
transport.mkdir('quack')
411
1012
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)
1013
branch = self.make_remote_branch(transport, client)
415
1014
result = branch.last_revision_info()
418
[('call', 'Branch.last_revision_info', ('quack/',))],
1015
client.finished_test()
420
1016
self.assertEqual((0, NULL_REVISION), result)
422
1018
def test_non_empty_branch(self):
424
1020
revid = u'\xc8'.encode('utf8')
425
1021
transport = MemoryTransport()
426
1022
client = FakeClient(transport.base)
427
client.add_success_response('ok', '2', revid)
1023
client.add_expected_call(
1024
'Branch.get_stacked_on_url', ('kwaak/',),
1025
'error', ('NotStacked',))
1026
client.add_expected_call(
1027
'Branch.last_revision_info', ('kwaak/',),
1028
'success', ('ok', '2', revid))
428
1029
transport.mkdir('kwaak')
429
1030
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)
1031
branch = self.make_remote_branch(transport, client)
433
1032
result = branch.last_revision_info()
436
[('call', 'Branch.last_revision_info', ('kwaak/',))],
438
1033
self.assertEqual((2, revid), result)
441
class TestBranchSetLastRevision(tests.TestCase):
1036
class TestBranch_get_stacked_on_url(TestRemote):
1037
"""Test Branch._get_stacked_on_url rpc"""
1039
def test_get_stacked_on_invalid_url(self):
1040
# test that asking for a stacked on url the server can't access works.
1041
# This isn't perfect, but then as we're in the same process there
1042
# really isn't anything we can do to be 100% sure that the server
1043
# doesn't just open in - this test probably needs to be rewritten using
1044
# a spawn()ed server.
1045
stacked_branch = self.make_branch('stacked', format='1.9')
1046
memory_branch = self.make_branch('base', format='1.9')
1047
vfs_url = self.get_vfs_only_url('base')
1048
stacked_branch.set_stacked_on_url(vfs_url)
1049
transport = stacked_branch.bzrdir.root_transport
1050
client = FakeClient(transport.base)
1051
client.add_expected_call(
1052
'Branch.get_stacked_on_url', ('stacked/',),
1053
'success', ('ok', vfs_url))
1054
# XXX: Multiple calls are bad, this second call documents what is
1056
client.add_expected_call(
1057
'Branch.get_stacked_on_url', ('stacked/',),
1058
'success', ('ok', vfs_url))
1059
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1061
repo_fmt = remote.RemoteRepositoryFormat()
1062
repo_fmt._custom_format = stacked_branch.repository._format
1063
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1065
result = branch.get_stacked_on_url()
1066
self.assertEqual(vfs_url, result)
1068
def test_backwards_compatible(self):
1069
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1070
base_branch = self.make_branch('base', format='1.6')
1071
stacked_branch = self.make_branch('stacked', format='1.6')
1072
stacked_branch.set_stacked_on_url('../base')
1073
client = FakeClient(self.get_url())
1074
branch_network_name = self.get_branch_format().network_name()
1075
client.add_expected_call(
1076
'BzrDir.open_branchV2', ('stacked/',),
1077
'success', ('branch', branch_network_name))
1078
client.add_expected_call(
1079
'BzrDir.find_repositoryV3', ('stacked/',),
1080
'success', ('ok', '', 'no', 'no', 'yes',
1081
stacked_branch.repository._format.network_name()))
1082
# called twice, once from constructor and then again by us
1083
client.add_expected_call(
1084
'Branch.get_stacked_on_url', ('stacked/',),
1085
'unknown', ('Branch.get_stacked_on_url',))
1086
client.add_expected_call(
1087
'Branch.get_stacked_on_url', ('stacked/',),
1088
'unknown', ('Branch.get_stacked_on_url',))
1089
# this will also do vfs access, but that goes direct to the transport
1090
# and isn't seen by the FakeClient.
1091
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1092
remote.RemoteBzrDirFormat(), _client=client)
1093
branch = bzrdir.open_branch()
1094
result = branch.get_stacked_on_url()
1095
self.assertEqual('../base', result)
1096
client.finished_test()
1097
# it's in the fallback list both for the RemoteRepository and its vfs
1099
self.assertEqual(1, len(branch.repository._fallback_repositories))
1101
len(branch.repository._real_repository._fallback_repositories))
1103
def test_get_stacked_on_real_branch(self):
1104
base_branch = self.make_branch('base', format='1.6')
1105
stacked_branch = self.make_branch('stacked', format='1.6')
1106
stacked_branch.set_stacked_on_url('../base')
1107
reference_format = self.get_repo_format()
1108
network_name = reference_format.network_name()
1109
client = FakeClient(self.get_url())
1110
branch_network_name = self.get_branch_format().network_name()
1111
client.add_expected_call(
1112
'BzrDir.open_branchV2', ('stacked/',),
1113
'success', ('branch', branch_network_name))
1114
client.add_expected_call(
1115
'BzrDir.find_repositoryV3', ('stacked/',),
1116
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1117
# called twice, once from constructor and then again by us
1118
client.add_expected_call(
1119
'Branch.get_stacked_on_url', ('stacked/',),
1120
'success', ('ok', '../base'))
1121
client.add_expected_call(
1122
'Branch.get_stacked_on_url', ('stacked/',),
1123
'success', ('ok', '../base'))
1124
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1125
remote.RemoteBzrDirFormat(), _client=client)
1126
branch = bzrdir.open_branch()
1127
result = branch.get_stacked_on_url()
1128
self.assertEqual('../base', result)
1129
client.finished_test()
1130
# it's in the fallback list both for the RemoteRepository.
1131
self.assertEqual(1, len(branch.repository._fallback_repositories))
1132
# And we haven't had to construct a real repository.
1133
self.assertEqual(None, branch.repository._real_repository)
1136
class TestBranchSetLastRevision(RemoteBranchTestCase):
443
1138
def test_set_empty(self):
444
1139
# set_revision_history([]) is translated to calling
506
1213
transport = transport.clone('branch')
507
1214
# A response of 'NoSuchRevision' is translated into an exception.
508
1215
client = FakeClient(transport.base)
510
client.add_success_response('ok', 'branch token', 'repo token')
512
client.add_error_response('NoSuchRevision', 'rev-id')
514
client.add_success_response('ok')
1216
client.add_expected_call(
1217
'Branch.get_stacked_on_url', ('branch/',),
1218
'error', ('NotStacked',))
1219
client.add_expected_call(
1220
'Branch.lock_write', ('branch/', '', ''),
1221
'success', ('ok', 'branch token', 'repo token'))
1222
client.add_expected_call(
1223
'Branch.last_revision_info',
1225
'success', ('ok', '0', 'null:'))
1226
# get_graph calls to construct the revision history, for the set_rh
1229
encoded_body = bz2.compress('\n'.join(lines))
1230
client.add_success_response_with_body(encoded_body, 'ok')
1231
client.add_expected_call(
1232
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1233
'error', ('NoSuchRevision', 'rev-id'))
1234
client.add_expected_call(
1235
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
516
bzrdir = RemoteBzrDir(transport, _client=False)
517
branch = RemoteBranch(bzrdir, None, _client=client)
518
branch._ensure_real = lambda: None
1238
branch = self.make_remote_branch(transport, client)
519
1239
branch.lock_write()
522
1240
self.assertRaises(
523
1241
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
527
class TestBranchSetLastRevisionInfo(tests.TestCase):
1243
client.finished_test()
1245
def test_tip_change_rejected(self):
1246
"""TipChangeRejected responses cause a TipChangeRejected exception to
1249
transport = MemoryTransport()
1250
transport.mkdir('branch')
1251
transport = transport.clone('branch')
1252
client = FakeClient(transport.base)
1253
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1254
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1255
client.add_expected_call(
1256
'Branch.get_stacked_on_url', ('branch/',),
1257
'error', ('NotStacked',))
1258
client.add_expected_call(
1259
'Branch.lock_write', ('branch/', '', ''),
1260
'success', ('ok', 'branch token', 'repo token'))
1261
client.add_expected_call(
1262
'Branch.last_revision_info',
1264
'success', ('ok', '0', 'null:'))
1266
encoded_body = bz2.compress('\n'.join(lines))
1267
client.add_success_response_with_body(encoded_body, 'ok')
1268
client.add_expected_call(
1269
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1270
'error', ('TipChangeRejected', rejection_msg_utf8))
1271
client.add_expected_call(
1272
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1274
branch = self.make_remote_branch(transport, client)
1275
branch._ensure_real = lambda: None
1277
# The 'TipChangeRejected' error response triggered by calling
1278
# set_revision_history causes a TipChangeRejected exception.
1279
err = self.assertRaises(
1280
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1281
# The UTF-8 message from the response has been decoded into a unicode
1283
self.assertIsInstance(err.msg, unicode)
1284
self.assertEqual(rejection_msg_unicode, err.msg)
1286
client.finished_test()
1289
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
529
1291
def test_set_last_revision_info(self):
530
1292
# set_last_revision_info(num, 'rev-id') is translated to calling
643
1419
client.add_success_response('ok')
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
1421
branch = self.make_remote_branch(transport, client)
650
1422
# Lock the branch, reset the record of remote calls.
651
1423
branch.lock_write()
652
1424
client._calls = []
654
1426
err = self.assertRaises(
655
errors.ErrorFromSmartServer,
1427
errors.UnknownErrorFromSmartServer,
656
1428
branch.set_last_revision_info, 123, 'revid')
657
1429
self.assertEqual(('UnexpectedError',), err.error_tuple)
661
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
662
"""Getting the branch configuration should use an abstract method not vfs.
1432
def test_tip_change_rejected(self):
1433
"""TipChangeRejected responses cause a TipChangeRejected exception to
1436
transport = MemoryTransport()
1437
transport.mkdir('branch')
1438
transport = transport.clone('branch')
1439
client = FakeClient(transport.base)
1440
# get_stacked_on_url
1441
client.add_error_response('NotStacked')
1443
client.add_success_response('ok', 'branch token', 'repo token')
1445
client.add_error_response('TipChangeRejected', 'rejection message')
1447
client.add_success_response('ok')
1449
branch = self.make_remote_branch(transport, client)
1450
# Lock the branch, reset the record of remote calls.
1452
self.addCleanup(branch.unlock)
1455
# The 'TipChangeRejected' error response triggered by calling
1456
# set_last_revision_info causes a TipChangeRejected exception.
1457
err = self.assertRaises(
1458
errors.TipChangeRejected,
1459
branch.set_last_revision_info, 123, 'revid')
1460
self.assertEqual('rejection message', err.msg)
1463
class TestBranchGetSetConfig(RemoteBranchTestCase):
665
1465
def test_get_branch_conf(self):
666
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
667
# We should see that branch.get_config() does a single rpc to get the
668
# remote configuration file, abstracting away where that is stored on
669
# the server. However at the moment it always falls back to using the
670
# vfs, and this would need some changes in config.py.
672
1466
# in an empty branch we decode the response properly
673
client = FakeClient(self.get_url())
1467
client = FakeClient()
1468
client.add_expected_call(
1469
'Branch.get_stacked_on_url', ('memory:///',),
1470
'error', ('NotStacked',),)
674
1471
client.add_success_response_with_body('# config file body', 'ok')
675
# we need to make a real branch because the remote_branch.control_files
676
# will trigger _ensure_real.
677
branch = self.make_branch('quack')
678
transport = branch.bzrdir.root_transport
679
# we do not want bzrdir to make any remote calls
680
bzrdir = RemoteBzrDir(transport, _client=False)
681
branch = RemoteBranch(bzrdir, None, _client=client)
1472
transport = MemoryTransport()
1473
branch = self.make_remote_branch(transport, client)
682
1474
config = branch.get_config()
1475
config.has_explicit_nickname()
683
1476
self.assertEqual(
684
[('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1477
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1478
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
688
class TestBranchLockWrite(tests.TestCase):
1481
def test_get_multi_line_branch_conf(self):
1482
# Make sure that multiple-line branch.conf files are supported
1484
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1485
client = FakeClient()
1486
client.add_expected_call(
1487
'Branch.get_stacked_on_url', ('memory:///',),
1488
'error', ('NotStacked',),)
1489
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1490
transport = MemoryTransport()
1491
branch = self.make_remote_branch(transport, client)
1492
config = branch.get_config()
1493
self.assertEqual(u'2', config.get_user_option('b'))
1495
def test_set_option(self):
1496
client = FakeClient()
1497
client.add_expected_call(
1498
'Branch.get_stacked_on_url', ('memory:///',),
1499
'error', ('NotStacked',),)
1500
client.add_expected_call(
1501
'Branch.lock_write', ('memory:///', '', ''),
1502
'success', ('ok', 'branch token', 'repo token'))
1503
client.add_expected_call(
1504
'Branch.set_config_option', ('memory:///', 'branch token',
1505
'repo token', 'foo', 'bar', ''),
1507
client.add_expected_call(
1508
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1510
transport = MemoryTransport()
1511
branch = self.make_remote_branch(transport, client)
1513
config = branch._get_config()
1514
config.set_option('foo', 'bar')
1516
client.finished_test()
1518
def test_backwards_compat_set_option(self):
1519
self.setup_smart_server_with_call_log()
1520
branch = self.make_branch('.')
1521
verb = 'Branch.set_config_option'
1522
self.disable_verb(verb)
1524
self.addCleanup(branch.unlock)
1525
self.reset_smart_call_log()
1526
branch._get_config().set_option('value', 'name')
1527
self.assertLength(10, self.hpss_calls)
1528
self.assertEqual('value', branch._get_config().get_option('name'))
1531
class TestBranchLockWrite(RemoteBranchTestCase):
690
1533
def test_lock_write_unlockable(self):
691
1534
transport = MemoryTransport()
692
1535
client = FakeClient(transport.base)
693
client.add_error_response('UnlockableTransport')
1536
client.add_expected_call(
1537
'Branch.get_stacked_on_url', ('quack/',),
1538
'error', ('NotStacked',),)
1539
client.add_expected_call(
1540
'Branch.lock_write', ('quack/', '', ''),
1541
'error', ('UnlockableTransport',))
694
1542
transport.mkdir('quack')
695
1543
transport = transport.clone('quack')
696
# we do not want bzrdir to make any remote calls
697
bzrdir = RemoteBzrDir(transport, _client=False)
698
branch = RemoteBranch(bzrdir, None, _client=client)
1544
branch = self.make_remote_branch(transport, client)
699
1545
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1546
client.finished_test()
1549
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1551
def test__get_config(self):
1552
client = FakeClient()
1553
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1554
transport = MemoryTransport()
1555
bzrdir = self.make_remote_bzrdir(transport, client)
1556
config = bzrdir.get_config()
1557
self.assertEqual('/', config.get_default_stack_on())
700
1558
self.assertEqual(
701
[('call', 'Branch.lock_write', ('quack/', '', ''))],
1559
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1562
def test_set_option_uses_vfs(self):
1563
self.setup_smart_server_with_call_log()
1564
bzrdir = self.make_bzrdir('.')
1565
self.reset_smart_call_log()
1566
config = bzrdir.get_config()
1567
config.set_default_stack_on('/')
1568
self.assertLength(3, self.hpss_calls)
1570
def test_backwards_compat_get_option(self):
1571
self.setup_smart_server_with_call_log()
1572
bzrdir = self.make_bzrdir('.')
1573
verb = 'BzrDir.get_config_file'
1574
self.disable_verb(verb)
1575
self.reset_smart_call_log()
1576
self.assertEqual(None,
1577
bzrdir._get_config().get_option('default_stack_on'))
1578
self.assertLength(3, self.hpss_calls)
705
1581
class TestTransportIsReadonly(tests.TestCase):
935
1850
errors.UnexpectedSmartServerResponse,
936
1851
repo.get_parent_map, ['a-revision-id'])
1853
def test_get_parent_map_negative_caches_missing_keys(self):
1854
self.setup_smart_server_with_call_log()
1855
repo = self.make_repository('foo')
1856
self.assertIsInstance(repo, RemoteRepository)
1858
self.addCleanup(repo.unlock)
1859
self.reset_smart_call_log()
1860
graph = repo.get_graph()
1861
self.assertEqual({},
1862
graph.get_parent_map(['some-missing', 'other-missing']))
1863
self.assertLength(1, self.hpss_calls)
1864
# No call if we repeat this
1865
self.reset_smart_call_log()
1866
graph = repo.get_graph()
1867
self.assertEqual({},
1868
graph.get_parent_map(['some-missing', 'other-missing']))
1869
self.assertLength(0, self.hpss_calls)
1870
# Asking for more unknown keys makes a request.
1871
self.reset_smart_call_log()
1872
graph = repo.get_graph()
1873
self.assertEqual({},
1874
graph.get_parent_map(['some-missing', 'other-missing',
1876
self.assertLength(1, self.hpss_calls)
1878
def disableExtraResults(self):
1879
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1880
SmartServerRepositoryGetParentMap.no_extra_results = True
1882
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1883
self.addCleanup(reset_values)
1885
def test_null_cached_missing_and_stop_key(self):
1886
self.setup_smart_server_with_call_log()
1887
# Make a branch with a single revision.
1888
builder = self.make_branch_builder('foo')
1889
builder.start_series()
1890
builder.build_snapshot('first', None, [
1891
('add', ('', 'root-id', 'directory', ''))])
1892
builder.finish_series()
1893
branch = builder.get_branch()
1894
repo = branch.repository
1895
self.assertIsInstance(repo, RemoteRepository)
1896
# Stop the server from sending extra results.
1897
self.disableExtraResults()
1899
self.addCleanup(repo.unlock)
1900
self.reset_smart_call_log()
1901
graph = repo.get_graph()
1902
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1903
# 'first' it will be a candidate for the stop_keys of subsequent
1904
# requests, and because 'null:' was queried but not returned it will be
1905
# cached as missing.
1906
self.assertEqual({'first': ('null:',)},
1907
graph.get_parent_map(['first', 'null:']))
1908
# Now query for another key. This request will pass along a recipe of
1909
# start and stop keys describing the already cached results, and this
1910
# recipe's revision count must be correct (or else it will trigger an
1911
# error from the server).
1912
self.assertEqual({}, graph.get_parent_map(['another-key']))
1913
# This assertion guards against disableExtraResults silently failing to
1914
# work, thus invalidating the test.
1915
self.assertLength(2, self.hpss_calls)
1917
def test_get_parent_map_gets_ghosts_from_result(self):
1918
# asking for a revision should negatively cache close ghosts in its
1920
self.setup_smart_server_with_call_log()
1921
tree = self.make_branch_and_memory_tree('foo')
1924
builder = treebuilder.TreeBuilder()
1925
builder.start_tree(tree)
1927
builder.finish_tree()
1928
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1929
rev_id = tree.commit('')
1933
self.addCleanup(tree.unlock)
1934
repo = tree.branch.repository
1935
self.assertIsInstance(repo, RemoteRepository)
1937
repo.get_parent_map([rev_id])
1938
self.reset_smart_call_log()
1939
# Now asking for rev_id's ghost parent should not make calls
1940
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1941
self.assertLength(0, self.hpss_calls)
1944
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1946
def test_allows_new_revisions(self):
1947
"""get_parent_map's results can be updated by commit."""
1948
smart_server = server.SmartTCPServer_for_testing()
1949
smart_server.setUp()
1950
self.addCleanup(smart_server.tearDown)
1951
self.make_branch('branch')
1952
branch = Branch.open(smart_server.get_url() + '/branch')
1953
tree = branch.create_checkout('tree', lightweight=True)
1955
self.addCleanup(tree.unlock)
1956
graph = tree.branch.repository.get_graph()
1957
# This provides an opportunity for the missing rev-id to be cached.
1958
self.assertEqual({}, graph.get_parent_map(['rev1']))
1959
tree.commit('message', rev_id='rev1')
1960
graph = tree.branch.repository.get_graph()
1961
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
939
1964
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
941
1966
def test_null_revision(self):
942
1967
# a null revision has the predictable result {}, we should have no wire
943
1968
# traffic when calling it with this argument
944
1969
transport_path = 'empty'
945
1970
repo, client = self.setup_fake_client_and_repository(transport_path)
946
1971
client.add_success_response('notused')
947
result = self.applyDeprecated(one_four, repo.get_revision_graph,
1972
# actual RemoteRepository.get_revision_graph is gone, but there's an
1973
# equivalent private method for testing
1974
result = repo._get_revision_graph(NULL_REVISION)
949
1975
self.assertEqual([], client._calls)
950
1976
self.assertEqual({}, result)
1003
2031
transport_path = 'sinhala'
1004
2032
repo, client = self.setup_fake_client_and_repository(transport_path)
1005
2033
client.add_error_response('AnUnexpectedError')
1006
e = self.assertRaises(errors.ErrorFromSmartServer,
1007
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
2034
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2035
repo._get_revision_graph, revid)
1008
2036
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2039
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2042
repo, client = self.setup_fake_client_and_repository('quack')
2043
client.add_expected_call(
2044
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2045
'success', ('ok', 'rev-five'))
2046
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2047
self.assertEqual((True, 'rev-five'), result)
2048
client.finished_test()
2050
def test_history_incomplete(self):
2051
repo, client = self.setup_fake_client_and_repository('quack')
2052
client.add_expected_call(
2053
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2054
'success', ('history-incomplete', 10, 'rev-ten'))
2055
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2056
self.assertEqual((False, (10, 'rev-ten')), result)
2057
client.finished_test()
2059
def test_history_incomplete_with_fallback(self):
2060
"""A 'history-incomplete' response causes the fallback repository to be
2061
queried too, if one is set.
2063
# Make a repo with a fallback repo, both using a FakeClient.
2064
format = remote.response_tuple_to_repo_format(
2065
('yes', 'no', 'yes', 'fake-network-name'))
2066
repo, client = self.setup_fake_client_and_repository('quack')
2067
repo._format = format
2068
fallback_repo, ignored = self.setup_fake_client_and_repository(
2070
fallback_repo._client = client
2071
repo.add_fallback_repository(fallback_repo)
2072
# First the client should ask the primary repo
2073
client.add_expected_call(
2074
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2075
'success', ('history-incomplete', 2, 'rev-two'))
2076
# Then it should ask the fallback, using revno/revid from the
2077
# history-incomplete response as the known revno/revid.
2078
client.add_expected_call(
2079
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2080
'success', ('ok', 'rev-one'))
2081
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2082
self.assertEqual((True, 'rev-one'), result)
2083
client.finished_test()
2085
def test_nosuchrevision(self):
2086
# 'nosuchrevision' is returned when the known-revid is not found in the
2087
# remote repo. The client translates that response to NoSuchRevision.
2088
repo, client = self.setup_fake_client_and_repository('quack')
2089
client.add_expected_call(
2090
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2091
'error', ('nosuchrevision', 'rev-foo'))
2093
errors.NoSuchRevision,
2094
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2095
client.finished_test()
1011
2098
class TestRepositoryIsShared(TestRemoteRepository):
1013
2100
def test_is_shared(self):
1156
2328
src_repo.copy_content_into(dest_repo)
1159
class TestRepositoryStreamKnitData(TestRemoteRepository):
1161
def make_pack_file(self, records):
1162
pack_file = StringIO()
1163
pack_writer = pack.ContainerWriter(pack_file.write)
1165
for bytes, names in records:
1166
pack_writer.add_bytes_record(bytes, names)
1171
def make_pack_stream(self, records):
1172
pack_serialiser = pack.ContainerSerialiser()
1173
yield pack_serialiser.begin()
1174
for bytes, names in records:
1175
yield pack_serialiser.bytes_record(bytes, names)
1176
yield pack_serialiser.end()
1178
def test_bad_pack_from_server(self):
1179
"""A response with invalid data (e.g. it has a record with multiple
1180
names) triggers an exception.
1182
Not all possible errors will be caught at this stage, but obviously
1183
malformed data should be.
1185
record = ('bytes', [('name1',), ('name2',)])
1186
pack_stream = self.make_pack_stream([record])
1187
transport_path = 'quack'
1188
repo, client = self.setup_fake_client_and_repository(transport_path)
1189
client.add_success_response_with_body(pack_stream, 'ok')
1190
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
1191
stream = repo.get_data_stream_for_search(search)
1192
self.assertRaises(errors.SmartProtocolError, list, stream)
2331
class _StubRealPackRepository(object):
2333
def __init__(self, calls):
2335
self._pack_collection = _StubPackCollection(calls)
2337
def is_in_write_group(self):
2340
def refresh_data(self):
2341
self.calls.append(('pack collection reload_pack_names',))
2344
class _StubPackCollection(object):
2346
def __init__(self, calls):
2350
self.calls.append(('pack collection autopack',))
2353
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2354
"""Tests for RemoteRepository.autopack implementation."""
2357
"""When the server returns 'ok' and there's no _real_repository, then
2358
nothing else happens: the autopack method is done.
2360
transport_path = 'quack'
2361
repo, client = self.setup_fake_client_and_repository(transport_path)
2362
client.add_expected_call(
2363
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2365
client.finished_test()
2367
def test_ok_with_real_repo(self):
2368
"""When the server returns 'ok' and there is a _real_repository, then
2369
the _real_repository's reload_pack_name's method will be called.
2371
transport_path = 'quack'
2372
repo, client = self.setup_fake_client_and_repository(transport_path)
2373
client.add_expected_call(
2374
'PackRepository.autopack', ('quack/',),
2376
repo._real_repository = _StubRealPackRepository(client._calls)
2379
[('call', 'PackRepository.autopack', ('quack/',)),
2380
('pack collection reload_pack_names',)],
1194
2383
def test_backwards_compatibility(self):
1195
"""If the server doesn't recognise this request, fallback to VFS."""
1196
repo, client = self.setup_fake_client_and_repository('path')
1197
client.add_unknown_method_response(
1198
'Repository.stream_revisions_chunked')
1199
self.mock_called = False
1200
repo._real_repository = MockRealRepository(self)
1201
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
1202
repo.get_data_stream_for_search(search)
1203
self.assertTrue(self.mock_called)
1204
self.failIf(client.expecting_body,
1205
"The protocol has been left in an unclean state that will cause "
1206
"TooManyConcurrentRequests errors.")
1209
class MockRealRepository(object):
1210
"""Helper class for TestRepositoryStreamKnitData.test_unknown_method."""
1212
def __init__(self, test):
1215
def get_data_stream_for_search(self, search):
1216
self.test.assertEqual(set(['revid']), search.get_keys())
1217
self.test.mock_called = True
2384
"""If the server does not recognise the PackRepository.autopack verb,
2385
fallback to the real_repository's implementation.
2387
transport_path = 'quack'
2388
repo, client = self.setup_fake_client_and_repository(transport_path)
2389
client.add_unknown_method_response('PackRepository.autopack')
2390
def stub_ensure_real():
2391
client._calls.append(('_ensure_real',))
2392
repo._real_repository = _StubRealPackRepository(client._calls)
2393
repo._ensure_real = stub_ensure_real
2396
[('call', 'PackRepository.autopack', ('quack/',)),
2398
('pack collection autopack',)],
2402
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2403
"""Base class for unit tests for bzrlib.remote._translate_error."""
2405
def translateTuple(self, error_tuple, **context):
2406
"""Call _translate_error with an ErrorFromSmartServer built from the
2409
:param error_tuple: A tuple of a smart server response, as would be
2410
passed to an ErrorFromSmartServer.
2411
:kwargs context: context items to call _translate_error with.
2413
:returns: The error raised by _translate_error.
2415
# Raise the ErrorFromSmartServer before passing it as an argument,
2416
# because _translate_error may need to re-raise it with a bare 'raise'
2418
server_error = errors.ErrorFromSmartServer(error_tuple)
2419
translated_error = self.translateErrorFromSmartServer(
2420
server_error, **context)
2421
return translated_error
2423
def translateErrorFromSmartServer(self, error_object, **context):
2424
"""Like translateTuple, but takes an already constructed
2425
ErrorFromSmartServer rather than a tuple.
2429
except errors.ErrorFromSmartServer, server_error:
2430
translated_error = self.assertRaises(
2431
errors.BzrError, remote._translate_error, server_error,
2433
return translated_error
2436
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2437
"""Unit tests for bzrlib.remote._translate_error.
2439
Given an ErrorFromSmartServer (which has an error tuple from a smart
2440
server) and some context, _translate_error raises more specific errors from
2443
This test case covers the cases where _translate_error succeeds in
2444
translating an ErrorFromSmartServer to something better. See
2445
TestErrorTranslationRobustness for other cases.
2448
def test_NoSuchRevision(self):
2449
branch = self.make_branch('')
2451
translated_error = self.translateTuple(
2452
('NoSuchRevision', revid), branch=branch)
2453
expected_error = errors.NoSuchRevision(branch, revid)
2454
self.assertEqual(expected_error, translated_error)
2456
def test_nosuchrevision(self):
2457
repository = self.make_repository('')
2459
translated_error = self.translateTuple(
2460
('nosuchrevision', revid), repository=repository)
2461
expected_error = errors.NoSuchRevision(repository, revid)
2462
self.assertEqual(expected_error, translated_error)
2464
def test_nobranch(self):
2465
bzrdir = self.make_bzrdir('')
2466
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2467
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2468
self.assertEqual(expected_error, translated_error)
2470
def test_LockContention(self):
2471
translated_error = self.translateTuple(('LockContention',))
2472
expected_error = errors.LockContention('(remote lock)')
2473
self.assertEqual(expected_error, translated_error)
2475
def test_UnlockableTransport(self):
2476
bzrdir = self.make_bzrdir('')
2477
translated_error = self.translateTuple(
2478
('UnlockableTransport',), bzrdir=bzrdir)
2479
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2480
self.assertEqual(expected_error, translated_error)
2482
def test_LockFailed(self):
2483
lock = 'str() of a server lock'
2484
why = 'str() of why'
2485
translated_error = self.translateTuple(('LockFailed', lock, why))
2486
expected_error = errors.LockFailed(lock, why)
2487
self.assertEqual(expected_error, translated_error)
2489
def test_TokenMismatch(self):
2490
token = 'a lock token'
2491
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2492
expected_error = errors.TokenMismatch(token, '(remote token)')
2493
self.assertEqual(expected_error, translated_error)
2495
def test_Diverged(self):
2496
branch = self.make_branch('a')
2497
other_branch = self.make_branch('b')
2498
translated_error = self.translateTuple(
2499
('Diverged',), branch=branch, other_branch=other_branch)
2500
expected_error = errors.DivergedBranches(branch, other_branch)
2501
self.assertEqual(expected_error, translated_error)
2503
def test_ReadError_no_args(self):
2505
translated_error = self.translateTuple(('ReadError',), path=path)
2506
expected_error = errors.ReadError(path)
2507
self.assertEqual(expected_error, translated_error)
2509
def test_ReadError(self):
2511
translated_error = self.translateTuple(('ReadError', path))
2512
expected_error = errors.ReadError(path)
2513
self.assertEqual(expected_error, translated_error)
2515
def test_PermissionDenied_no_args(self):
2517
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2518
expected_error = errors.PermissionDenied(path)
2519
self.assertEqual(expected_error, translated_error)
2521
def test_PermissionDenied_one_arg(self):
2523
translated_error = self.translateTuple(('PermissionDenied', path))
2524
expected_error = errors.PermissionDenied(path)
2525
self.assertEqual(expected_error, translated_error)
2527
def test_PermissionDenied_one_arg_and_context(self):
2528
"""Given a choice between a path from the local context and a path on
2529
the wire, _translate_error prefers the path from the local context.
2531
local_path = 'local path'
2532
remote_path = 'remote path'
2533
translated_error = self.translateTuple(
2534
('PermissionDenied', remote_path), path=local_path)
2535
expected_error = errors.PermissionDenied(local_path)
2536
self.assertEqual(expected_error, translated_error)
2538
def test_PermissionDenied_two_args(self):
2540
extra = 'a string with extra info'
2541
translated_error = self.translateTuple(
2542
('PermissionDenied', path, extra))
2543
expected_error = errors.PermissionDenied(path, extra)
2544
self.assertEqual(expected_error, translated_error)
2547
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2548
"""Unit tests for bzrlib.remote._translate_error's robustness.
2550
TestErrorTranslationSuccess is for cases where _translate_error can
2551
translate successfully. This class about how _translate_err behaves when
2552
it fails to translate: it re-raises the original error.
2555
def test_unrecognised_server_error(self):
2556
"""If the error code from the server is not recognised, the original
2557
ErrorFromSmartServer is propagated unmodified.
2559
error_tuple = ('An unknown error tuple',)
2560
server_error = errors.ErrorFromSmartServer(error_tuple)
2561
translated_error = self.translateErrorFromSmartServer(server_error)
2562
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2563
self.assertEqual(expected_error, translated_error)
2565
def test_context_missing_a_key(self):
2566
"""In case of a bug in the client, or perhaps an unexpected response
2567
from a server, _translate_error returns the original error tuple from
2568
the server and mutters a warning.
2570
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2571
# in the context dict. So let's give it an empty context dict instead
2572
# to exercise its error recovery.
2574
error_tuple = ('NoSuchRevision', 'revid')
2575
server_error = errors.ErrorFromSmartServer(error_tuple)
2576
translated_error = self.translateErrorFromSmartServer(server_error)
2577
self.assertEqual(server_error, translated_error)
2578
# In addition to re-raising ErrorFromSmartServer, some debug info has
2579
# been muttered to the log file for developer to look at.
2580
self.assertContainsRe(
2581
self._get_log(keep_log_file=True),
2582
"Missing key 'branch' in context")
2584
def test_path_missing(self):
2585
"""Some translations (PermissionDenied, ReadError) can determine the
2586
'path' variable from either the wire or the local context. If neither
2587
has it, then an error is raised.
2589
error_tuple = ('ReadError',)
2590
server_error = errors.ErrorFromSmartServer(error_tuple)
2591
translated_error = self.translateErrorFromSmartServer(server_error)
2592
self.assertEqual(server_error, translated_error)
2593
# In addition to re-raising ErrorFromSmartServer, some debug info has
2594
# been muttered to the log file for developer to look at.
2595
self.assertContainsRe(
2596
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2599
class TestStacking(tests.TestCaseWithTransport):
2600
"""Tests for operations on stacked remote repositories.
2602
The underlying format type must support stacking.
2605
def test_access_stacked_remote(self):
2606
# based on <http://launchpad.net/bugs/261315>
2607
# make a branch stacked on another repository containing an empty
2608
# revision, then open it over hpss - we should be able to see that
2610
base_transport = self.get_transport()
2611
base_builder = self.make_branch_builder('base', format='1.9')
2612
base_builder.start_series()
2613
base_revid = base_builder.build_snapshot('rev-id', None,
2614
[('add', ('', None, 'directory', None))],
2616
base_builder.finish_series()
2617
stacked_branch = self.make_branch('stacked', format='1.9')
2618
stacked_branch.set_stacked_on_url('../base')
2619
# start a server looking at this
2620
smart_server = server.SmartTCPServer_for_testing()
2621
smart_server.setUp()
2622
self.addCleanup(smart_server.tearDown)
2623
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2624
# can get its branch and repository
2625
remote_branch = remote_bzrdir.open_branch()
2626
remote_repo = remote_branch.repository
2627
remote_repo.lock_read()
2629
# it should have an appropriate fallback repository, which should also
2630
# be a RemoteRepository
2631
self.assertLength(1, remote_repo._fallback_repositories)
2632
self.assertIsInstance(remote_repo._fallback_repositories[0],
2634
# and it has the revision committed to the underlying repository;
2635
# these have varying implementations so we try several of them
2636
self.assertTrue(remote_repo.has_revisions([base_revid]))
2637
self.assertTrue(remote_repo.has_revision(base_revid))
2638
self.assertEqual(remote_repo.get_revision(base_revid).message,
2641
remote_repo.unlock()
2643
def prepare_stacked_remote_branch(self):
2644
"""Get stacked_upon and stacked branches with content in each."""
2645
self.setup_smart_server_with_call_log()
2646
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2647
tree1.commit('rev1', rev_id='rev1')
2648
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2649
).open_workingtree()
2650
tree2.commit('local changes make me feel good.')
2651
branch2 = Branch.open(self.get_url('tree2'))
2653
self.addCleanup(branch2.unlock)
2654
return tree1.branch, branch2
2656
def test_stacked_get_parent_map(self):
2657
# the public implementation of get_parent_map obeys stacking
2658
_, branch = self.prepare_stacked_remote_branch()
2659
repo = branch.repository
2660
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2662
def test_unstacked_get_parent_map(self):
2663
# _unstacked_provider.get_parent_map ignores stacking
2664
_, branch = self.prepare_stacked_remote_branch()
2665
provider = branch.repository._unstacked_provider
2666
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2668
def fetch_stream_to_rev_order(self, stream):
2670
for kind, substream in stream:
2671
if not kind == 'revisions':
2674
for content in substream:
2675
result.append(content.key[-1])
2678
def get_ordered_revs(self, format, order):
2679
"""Get a list of the revisions in a stream to format format.
2681
:param format: The format of the target.
2682
:param order: the order that target should have requested.
2683
:result: The revision ids in the stream, in the order seen,
2684
the topological order of revisions in the source.
2686
unordered_format = bzrdir.format_registry.get(format)()
2687
target_repository_format = unordered_format.repository_format
2689
self.assertEqual(order, target_repository_format._fetch_order)
2690
trunk, stacked = self.prepare_stacked_remote_branch()
2691
source = stacked.repository._get_source(target_repository_format)
2692
tip = stacked.last_revision()
2693
revs = stacked.repository.get_ancestry(tip)
2694
search = graph.PendingAncestryResult([tip], stacked.repository)
2695
self.reset_smart_call_log()
2696
stream = source.get_stream(search)
2699
# We trust that if a revision is in the stream the rest of the new
2700
# content for it is too, as per our main fetch tests; here we are
2701
# checking that the revisions are actually included at all, and their
2703
return self.fetch_stream_to_rev_order(stream), revs
2705
def test_stacked_get_stream_unordered(self):
2706
# Repository._get_source.get_stream() from a stacked repository with
2707
# unordered yields the full data from both stacked and stacked upon
2709
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2710
self.assertEqual(set(expected_revs), set(rev_ord))
2711
# Getting unordered results should have made a streaming data request
2712
# from the server, then one from the backing branch.
2713
self.assertLength(2, self.hpss_calls)
2715
def test_stacked_get_stream_topological(self):
2716
# Repository._get_source.get_stream() from a stacked repository with
2717
# topological sorting yields the full data from both stacked and
2718
# stacked upon sources in topological order.
2719
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2720
self.assertEqual(expected_revs, rev_ord)
2721
# Getting topological sort requires VFS calls still
2722
self.assertLength(12, self.hpss_calls)
2724
def test_stacked_get_stream_groupcompress(self):
2725
# Repository._get_source.get_stream() from a stacked repository with
2726
# groupcompress sorting yields the full data from both stacked and
2727
# stacked upon sources in groupcompress order.
2728
raise tests.TestSkipped('No groupcompress ordered format available')
2729
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2730
self.assertEqual(expected_revs, reversed(rev_ord))
2731
# Getting unordered results should have made a streaming data request
2732
# from the backing branch, and one from the stacked on branch.
2733
self.assertLength(2, self.hpss_calls)
2735
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
2736
# When pulling some fixed amount of content that is more than the
2737
# source has (because some is coming from a fallback branch, no error
2738
# should be received. This was reported as bug 360791.
2739
# Need three branches: a trunk, a stacked branch, and a preexisting
2740
# branch pulling content from stacked and trunk.
2741
self.setup_smart_server_with_call_log()
2742
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
2743
r1 = trunk.commit('start')
2744
stacked_branch = trunk.branch.create_clone_on_transport(
2745
self.get_transport('stacked'), stacked_on=trunk.branch.base)
2746
local = self.make_branch('local', format='1.9-rich-root')
2747
local.repository.fetch(stacked_branch.repository,
2748
stacked_branch.last_revision())
2751
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2754
super(TestRemoteBranchEffort, self).setUp()
2755
# Create a smart server that publishes whatever the backing VFS server
2757
self.smart_server = server.SmartTCPServer_for_testing()
2758
self.smart_server.setUp(self.get_server())
2759
self.addCleanup(self.smart_server.tearDown)
2760
# Log all HPSS calls into self.hpss_calls.
2761
_SmartClient.hooks.install_named_hook(
2762
'call', self.capture_hpss_call, None)
2763
self.hpss_calls = []
2765
def capture_hpss_call(self, params):
2766
self.hpss_calls.append(params.method)
2768
def test_copy_content_into_avoids_revision_history(self):
2769
local = self.make_branch('local')
2770
remote_backing_tree = self.make_branch_and_tree('remote')
2771
remote_backing_tree.commit("Commit.")
2772
remote_branch_url = self.smart_server.get_url() + 'remote'
2773
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2774
local.repository.fetch(remote_branch.repository)
2775
self.hpss_calls = []
2776
remote_branch.copy_content_into(local)
2777
self.assertFalse('Branch.revision_history' in self.hpss_calls)