120
125
def cancel_read_body(self):
121
126
self._fake_client.expecting_body = False
128
def read_streamed_body(self):
124
132
class FakeClient(_SmartClient):
125
133
"""Lookalike for _SmartClient allowing testing."""
127
def __init__(self, responses):
128
# We don't call the super init because there is no medium.
135
def __init__(self, fake_medium_base='fake base'):
129
136
"""Create a FakeClient.
131
:param respones: A list of response-tuple, body-data pairs to be sent
138
:param responses: A list of response-tuple, body-data pairs to be sent
139
back to callers. A special case is if the response-tuple is
140
'unknown verb', then a UnknownSmartMethod will be raised for that
141
call, using the second element of the tuple as the verb in the
134
self.responses = responses
136
146
self.expecting_body = False
147
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
149
def add_success_response(self, *args):
150
self.responses.append(('success', args, None))
152
def add_success_response_with_body(self, body, *args):
153
self.responses.append(('success', args, body))
155
def add_error_response(self, *args):
156
self.responses.append(('error', args))
158
def add_unknown_method_response(self, verb):
159
self.responses.append(('unknown', verb))
161
def _get_next_response(self):
162
response_tuple = self.responses.pop(0)
163
if response_tuple[0] == 'unknown':
164
raise errors.UnknownSmartMethod(response_tuple[1])
165
elif response_tuple[0] == 'error':
166
raise errors.ErrorFromSmartServer(response_tuple[1])
167
return response_tuple
138
169
def call(self, method, *args):
139
170
self._calls.append(('call', method, args))
140
return self.responses.pop(0)[0]
171
return self._get_next_response()[1]
142
173
def call_expecting_body(self, method, *args):
143
174
self._calls.append(('call_expecting_body', method, args))
144
result = self.responses.pop(0)
145
self.expecting_body = True
146
return result[0], FakeProtocol(result[1], self)
175
result = self._get_next_response()
176
self.expecting_body = True
177
return result[1], FakeProtocol(result[2], self)
179
def call_with_body_bytes_expecting_body(self, method, args, body):
180
self._calls.append(('call_with_body_bytes_expecting_body', method,
182
result = self._get_next_response()
183
self.expecting_body = True
184
return result[1], FakeProtocol(result[2], self)
187
class FakeMedium(medium.SmartClientMedium):
189
def __init__(self, client_calls, base):
190
medium.SmartClientMedium.__init__(self, base)
191
self._client_calls = client_calls
193
def disconnect(self):
194
self._client_calls.append(('disconnect medium',))
197
class TestVfsHas(tests.TestCase):
199
def test_unicode_path(self):
200
client = FakeClient('/')
201
client.add_success_response('yes',)
202
transport = RemoteTransport('bzr://localhost/', _client=client)
203
filename = u'/hell\u00d8'.encode('utf8')
204
result = transport.has(filename)
206
[('call', 'has', (filename,))],
208
self.assertTrue(result)
211
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
212
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
214
def assertRemotePath(self, expected, client_base, transport_base):
215
"""Assert that the result of
216
SmartClientMedium.remote_path_from_transport is the expected value for
217
a given client_base and transport_base.
219
client_medium = medium.SmartClientMedium(client_base)
220
transport = get_transport(transport_base)
221
result = client_medium.remote_path_from_transport(transport)
222
self.assertEqual(expected, result)
224
def test_remote_path_from_transport(self):
225
"""SmartClientMedium.remote_path_from_transport calculates a URL for
226
the given transport relative to the root of the client base URL.
228
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
229
self.assertRemotePath(
230
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
232
def assertRemotePathHTTP(self, expected, transport_base, relpath):
233
"""Assert that the result of
234
HttpTransportBase.remote_path_from_transport is the expected value for
235
a given transport_base and relpath of that transport. (Note that
236
HttpTransportBase is a subclass of SmartClientMedium)
238
base_transport = get_transport(transport_base)
239
client_medium = base_transport.get_smart_medium()
240
cloned_transport = base_transport.clone(relpath)
241
result = client_medium.remote_path_from_transport(cloned_transport)
242
self.assertEqual(expected, result)
244
def test_remote_path_from_transport_http(self):
245
"""Remote paths for HTTP transports are calculated differently to other
246
transports. They are just relative to the client base, not the root
247
directory of the host.
249
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
250
self.assertRemotePathHTTP(
251
'../xyz/', scheme + '//host/path', '../xyz/')
252
self.assertRemotePathHTTP(
253
'xyz/', scheme + '//host/path', 'xyz/')
256
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
257
"""Tests for the behaviour of client_medium.remote_is_at_least."""
259
def test_initially_unlimited(self):
260
"""A fresh medium assumes that the remote side supports all
263
client_medium = medium.SmartClientMedium('dummy base')
264
self.assertFalse(client_medium._is_remote_before((99, 99)))
266
def test__remember_remote_is_before(self):
267
"""Calling _remember_remote_is_before ratchets down the known remote
270
client_medium = medium.SmartClientMedium('dummy base')
271
# Mark the remote side as being less than 1.6. The remote side may
273
client_medium._remember_remote_is_before((1, 6))
274
self.assertTrue(client_medium._is_remote_before((1, 6)))
275
self.assertFalse(client_medium._is_remote_before((1, 5)))
276
# Calling _remember_remote_is_before again with a lower value works.
277
client_medium._remember_remote_is_before((1, 5))
278
self.assertTrue(client_medium._is_remote_before((1, 5)))
279
# You cannot call _remember_remote_is_before with a larger value.
281
AssertionError, client_medium._remember_remote_is_before, (1, 9))
149
284
class TestBzrDirOpenBranch(tests.TestCase):
151
286
def test_branch_present(self):
152
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )])
153
287
transport = MemoryTransport()
154
288
transport.mkdir('quack')
155
289
transport = transport.clone('quack')
290
client = FakeClient(transport.base)
291
client.add_success_response('ok', '')
292
client.add_success_response('ok', '', 'no', 'no', 'no')
156
293
bzrdir = RemoteBzrDir(transport, _client=client)
157
294
result = bzrdir.open_branch()
158
295
self.assertEqual(
159
[('call', 'BzrDir.open_branch', ('///quack/',)),
160
('call', 'BzrDir.find_repository', ('///quack/',))],
296
[('call', 'BzrDir.open_branch', ('quack/',)),
297
('call', 'BzrDir.find_repositoryV2', ('quack/',))],
162
299
self.assertIsInstance(result, RemoteBranch)
163
300
self.assertEqual(bzrdir, result.bzrdir)
165
302
def test_branch_missing(self):
166
client = FakeClient([(('nobranch',), )])
167
303
transport = MemoryTransport()
168
304
transport.mkdir('quack')
169
305
transport = transport.clone('quack')
306
client = FakeClient(transport.base)
307
client.add_error_response('nobranch')
170
308
bzrdir = RemoteBzrDir(transport, _client=client)
171
309
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
172
310
self.assertEqual(
173
[('call', 'BzrDir.open_branch', ('///quack/',))],
176
def check_open_repository(self, rich_root, subtrees):
311
[('call', 'BzrDir.open_branch', ('quack/',))],
314
def test__get_tree_branch(self):
315
# _get_tree_branch is a form of open_branch, but it should only ask for
316
# branch opening, not any other network requests.
319
calls.append("Called")
321
transport = MemoryTransport()
322
# no requests on the network - catches other api calls being made.
323
client = FakeClient(transport.base)
324
bzrdir = RemoteBzrDir(transport, _client=client)
325
# patch the open_branch call to record that it was called.
326
bzrdir.open_branch = open_branch
327
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
328
self.assertEqual(["Called"], calls)
329
self.assertEqual([], client._calls)
331
def test_url_quoting_of_path(self):
332
# Relpaths on the wire should not be URL-escaped. So "~" should be
333
# transmitted as "~", not "%7E".
334
transport = RemoteTCPTransport('bzr://localhost/~hello/')
335
client = FakeClient(transport.base)
336
client.add_success_response('ok', '')
337
client.add_success_response('ok', '', 'no', 'no', 'no')
338
bzrdir = RemoteBzrDir(transport, _client=client)
339
result = bzrdir.open_branch()
341
[('call', 'BzrDir.open_branch', ('~hello/',)),
342
('call', 'BzrDir.find_repositoryV2', ('~hello/',))],
345
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
346
transport = MemoryTransport()
347
transport.mkdir('quack')
348
transport = transport.clone('quack')
178
350
rich_response = 'yes'
356
551
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
554
def test_tip_change_rejected(self):
555
"""TipChangeRejected responses cause a TipChangeRejected exception to
558
transport = MemoryTransport()
559
transport.mkdir('branch')
560
transport = transport.clone('branch')
561
client = FakeClient(transport.base)
563
client.add_success_response('ok', 'branch token', 'repo token')
565
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
566
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)
574
branch._ensure_real = lambda: None
576
self.addCleanup(branch.unlock)
579
# The 'TipChangeRejected' error response triggered by calling
580
# set_revision_history causes a TipChangeRejected exception.
581
err = self.assertRaises(
582
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
583
# The UTF-8 message from the response has been decoded into a unicode
585
self.assertIsInstance(err.msg, unicode)
586
self.assertEqual(rejection_msg_unicode, err.msg)
589
class TestBranchSetLastRevisionInfo(tests.TestCase):
591
def test_set_last_revision_info(self):
592
# set_last_revision_info(num, 'rev-id') is translated to calling
593
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
594
transport = MemoryTransport()
595
transport.mkdir('branch')
596
transport = transport.clone('branch')
597
client = FakeClient(transport.base)
599
client.add_success_response('ok', 'branch token', 'repo token')
601
client.add_success_response('ok')
603
client.add_success_response('ok')
605
bzrdir = RemoteBzrDir(transport, _client=False)
606
branch = RemoteBranch(bzrdir, None, _client=client)
607
# This is a hack to work around the problem that RemoteBranch currently
608
# unnecessarily invokes _ensure_real upon a call to lock_write.
609
branch._ensure_real = lambda: None
610
# Lock the branch, reset the record of remote calls.
613
result = branch.set_last_revision_info(1234, 'a-revision-id')
615
[('call', 'Branch.set_last_revision_info',
616
('branch/', 'branch token', 'repo token',
617
'1234', 'a-revision-id'))],
619
self.assertEqual(None, result)
621
def test_no_such_revision(self):
622
# A response of 'NoSuchRevision' is translated into an exception.
623
transport = MemoryTransport()
624
transport.mkdir('branch')
625
transport = transport.clone('branch')
626
client = FakeClient(transport.base)
628
client.add_success_response('ok', 'branch token', 'repo token')
630
client.add_error_response('NoSuchRevision', 'revid')
632
client.add_success_response('ok')
634
bzrdir = RemoteBzrDir(transport, _client=False)
635
repo = RemoteRepository(bzrdir, None, _client=client)
636
branch = RemoteBranch(bzrdir, repo, _client=client)
637
# This is a hack to work around the problem that RemoteBranch currently
638
# unnecessarily invokes _ensure_real upon a call to lock_write.
639
branch._ensure_real = lambda: None
640
# Lock the branch, reset the record of remote calls.
645
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
648
def lock_remote_branch(self, branch):
649
"""Trick a RemoteBranch into thinking it is locked."""
650
branch._lock_mode = 'w'
651
branch._lock_count = 2
652
branch._lock_token = 'branch token'
653
branch._repo_lock_token = 'repo token'
655
def test_backwards_compatibility(self):
656
"""If the server does not support the Branch.set_last_revision_info
657
verb (which is new in 1.4), then the client falls back to VFS methods.
659
# This test is a little messy. Unlike most tests in this file, it
660
# doesn't purely test what a Remote* object sends over the wire, and
661
# how it reacts to responses from the wire. It instead relies partly
662
# on asserting that the RemoteBranch will call
663
# self._real_branch.set_last_revision_info(...).
665
# First, set up our RemoteBranch with a FakeClient that raises
666
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
667
transport = MemoryTransport()
668
transport.mkdir('branch')
669
transport = transport.clone('branch')
670
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)
674
class StubRealBranch(object):
677
def set_last_revision_info(self, revno, revision_id):
679
('set_last_revision_info', revno, revision_id))
680
def _clear_cached_state(self):
682
real_branch = StubRealBranch()
683
branch._real_branch = real_branch
684
self.lock_remote_branch(branch)
686
# Call set_last_revision_info, and verify it behaved as expected.
687
result = branch.set_last_revision_info(1234, 'a-revision-id')
689
[('call', 'Branch.set_last_revision_info',
690
('branch/', 'branch token', 'repo token',
691
'1234', 'a-revision-id')),],
694
[('set_last_revision_info', 1234, 'a-revision-id')],
697
def test_unexpected_error(self):
698
# A response of 'NoSuchRevision' is translated into an exception.
699
transport = MemoryTransport()
700
transport.mkdir('branch')
701
transport = transport.clone('branch')
702
client = FakeClient(transport.base)
704
client.add_success_response('ok', 'branch token', 'repo token')
706
client.add_error_response('UnexpectedError')
708
client.add_success_response('ok')
710
bzrdir = RemoteBzrDir(transport, _client=False)
711
repo = RemoteRepository(bzrdir, None, _client=client)
712
branch = RemoteBranch(bzrdir, repo, _client=client)
713
# This is a hack to work around the problem that RemoteBranch currently
714
# unnecessarily invokes _ensure_real upon a call to lock_write.
715
branch._ensure_real = lambda: None
716
# Lock the branch, reset the record of remote calls.
720
err = self.assertRaises(
721
errors.ErrorFromSmartServer,
722
branch.set_last_revision_info, 123, 'revid')
723
self.assertEqual(('UnexpectedError',), err.error_tuple)
726
def test_tip_change_rejected(self):
727
"""TipChangeRejected responses cause a TipChangeRejected exception to
730
transport = MemoryTransport()
731
transport.mkdir('branch')
732
transport = transport.clone('branch')
733
client = FakeClient(transport.base)
735
client.add_success_response('ok', 'branch token', 'repo token')
737
client.add_error_response('TipChangeRejected', 'rejection message')
739
client.add_success_response('ok')
741
bzrdir = RemoteBzrDir(transport, _client=False)
742
repo = RemoteRepository(bzrdir, None, _client=client)
743
branch = RemoteBranch(bzrdir, repo, _client=client)
744
# This is a hack to work around the problem that RemoteBranch currently
745
# unnecessarily invokes _ensure_real upon a call to lock_write.
746
branch._ensure_real = lambda: None
747
# Lock the branch, reset the record of remote calls.
749
self.addCleanup(branch.unlock)
752
# The 'TipChangeRejected' error response triggered by calling
753
# set_last_revision_info causes a TipChangeRejected exception.
754
err = self.assertRaises(
755
errors.TipChangeRejected,
756
branch.set_last_revision_info, 123, 'revid')
757
self.assertEqual('rejection message', err.msg)
360
760
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
361
"""Test branch.control_files api munging...
363
We special case RemoteBranch.control_files.get('branch.conf') to
364
call a specific API so that RemoteBranch's can intercept configuration
365
file reading, allowing them to signal to the client about things like
366
'email is configured for commits'.
761
"""Getting the branch configuration should use an abstract method not vfs.
369
764
def test_get_branch_conf(self):
370
# in an empty branch we decode the response properly
371
client = FakeClient([(('ok', ), 'config file body')])
372
# we need to make a real branch because the remote_branch.control_files
373
# will trigger _ensure_real.
374
branch = self.make_branch('quack')
375
transport = branch.bzrdir.root_transport
376
# we do not want bzrdir to make any remote calls
377
bzrdir = RemoteBzrDir(transport, _client=False)
378
branch = RemoteBranch(bzrdir, None, _client=client)
379
result = branch.control_files.get('branch.conf')
381
[('call_expecting_body', 'Branch.get_config_file', ('///quack/',))],
383
self.assertEqual('config file body', result.read())
765
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
766
## # We should see that branch.get_config() does a single rpc to get the
767
## # remote configuration file, abstracting away where that is stored on
768
## # the server. However at the moment it always falls back to using the
769
## # vfs, and this would need some changes in config.py.
771
## # in an empty branch we decode the response properly
772
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
773
## # we need to make a real branch because the remote_branch.control_files
774
## # will trigger _ensure_real.
775
## branch = self.make_branch('quack')
776
## transport = branch.bzrdir.root_transport
777
## # we do not want bzrdir to make any remote calls
778
## bzrdir = RemoteBzrDir(transport, _client=False)
779
## branch = RemoteBranch(bzrdir, None, _client=client)
780
## config = branch.get_config()
782
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
386
786
class TestBranchLockWrite(tests.TestCase):
388
788
def test_lock_write_unlockable(self):
389
client = FakeClient([(('UnlockableTransport', ), '')])
390
789
transport = MemoryTransport()
790
client = FakeClient(transport.base)
791
client.add_error_response('UnlockableTransport')
391
792
transport.mkdir('quack')
392
793
transport = transport.clone('quack')
393
794
# we do not want bzrdir to make any remote calls
394
795
bzrdir = RemoteBzrDir(transport, _client=False)
395
branch = RemoteBranch(bzrdir, None, _client=client)
796
repo = RemoteRepository(bzrdir, None, _client=client)
797
branch = RemoteBranch(bzrdir, repo, _client=client)
396
798
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
397
799
self.assertEqual(
398
[('call', 'Branch.lock_write', ('///quack/', '', ''))],
800
[('call', 'Branch.lock_write', ('quack/', '', ''))],
402
804
class TestTransportIsReadonly(tests.TestCase):
404
806
def test_true(self):
405
client = FakeClient([(('yes',), '')])
807
client = FakeClient()
808
client.add_success_response('yes')
406
809
transport = RemoteTransport('bzr://example.com/', medium=False,
408
811
self.assertEqual(True, transport.is_readonly())
928
class TestRepositoryGetGraph(TestRemoteRepository):
930
def test_get_graph(self):
931
# get_graph returns a graph with the repository as the
933
transport_path = 'quack'
934
repo, client = self.setup_fake_client_and_repository(transport_path)
935
graph = repo.get_graph()
936
self.assertEqual(graph._parents_provider, repo)
939
class TestRepositoryGetParentMap(TestRemoteRepository):
941
def test_get_parent_map_caching(self):
942
# get_parent_map returns from cache until unlock()
943
# setup a reponse with two revisions
944
r1 = u'\u0e33'.encode('utf8')
945
r2 = u'\u0dab'.encode('utf8')
946
lines = [' '.join([r2, r1]), r1]
947
encoded_body = bz2.compress('\n'.join(lines))
949
transport_path = 'quack'
950
repo, client = self.setup_fake_client_and_repository(transport_path)
951
client.add_success_response_with_body(encoded_body, 'ok')
952
client.add_success_response_with_body(encoded_body, 'ok')
954
graph = repo.get_graph()
955
parents = graph.get_parent_map([r2])
956
self.assertEqual({r2: (r1,)}, parents)
957
# locking and unlocking deeper should not reset
960
parents = graph.get_parent_map([r1])
961
self.assertEqual({r1: (NULL_REVISION,)}, parents)
963
[('call_with_body_bytes_expecting_body',
964
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
967
# now we call again, and it should use the second response.
969
graph = repo.get_graph()
970
parents = graph.get_parent_map([r1])
971
self.assertEqual({r1: (NULL_REVISION,)}, parents)
973
[('call_with_body_bytes_expecting_body',
974
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
975
('call_with_body_bytes_expecting_body',
976
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
981
def test_get_parent_map_reconnects_if_unknown_method(self):
982
transport_path = 'quack'
983
repo, client = self.setup_fake_client_and_repository(transport_path)
984
client.add_unknown_method_response('Repository,get_parent_map')
985
client.add_success_response_with_body('', 'ok')
986
self.assertFalse(client._medium._is_remote_before((1, 2)))
987
rev_id = 'revision-id'
988
expected_deprecations = [
989
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
991
parents = self.callDeprecated(
992
expected_deprecations, repo.get_parent_map, [rev_id])
994
[('call_with_body_bytes_expecting_body',
995
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
996
('disconnect medium',),
997
('call_expecting_body', 'Repository.get_revision_graph',
1000
# The medium is now marked as being connected to an older server
1001
self.assertTrue(client._medium._is_remote_before((1, 2)))
1003
def test_get_parent_map_fallback_parentless_node(self):
1004
"""get_parent_map falls back to get_revision_graph on old servers. The
1005
results from get_revision_graph are tweaked to match the get_parent_map
1008
Specifically, a {key: ()} result from get_revision_graph means "no
1009
parents" for that key, which in get_parent_map results should be
1010
represented as {key: ('null:',)}.
1012
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1014
rev_id = 'revision-id'
1015
transport_path = 'quack'
1016
repo, client = self.setup_fake_client_and_repository(transport_path)
1017
client.add_success_response_with_body(rev_id, 'ok')
1018
client._medium._remember_remote_is_before((1, 2))
1019
expected_deprecations = [
1020
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1022
parents = self.callDeprecated(
1023
expected_deprecations, repo.get_parent_map, [rev_id])
1025
[('call_expecting_body', 'Repository.get_revision_graph',
1028
self.assertEqual({rev_id: ('null:',)}, parents)
1030
def test_get_parent_map_unexpected_response(self):
1031
repo, client = self.setup_fake_client_and_repository('path')
1032
client.add_success_response('something unexpected!')
1034
errors.UnexpectedSmartServerResponse,
1035
repo.get_parent_map, ['a-revision-id'])
541
1038
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
543
1040
def test_null_revision(self):
544
1041
# a null revision has the predictable result {}, we should have no wire
545
1042
# traffic when calling it with this argument
546
responses = [(('notused', ), '')]
547
1043
transport_path = 'empty'
548
repo, client = self.setup_fake_client_and_repository(
549
responses, transport_path)
550
result = repo.get_revision_graph(NULL_REVISION)
1044
repo, client = self.setup_fake_client_and_repository(transport_path)
1045
client.add_success_response('notused')
1046
result = self.applyDeprecated(one_four, repo.get_revision_graph,
551
1048
self.assertEqual([], client._calls)
552
1049
self.assertEqual({}, result)
578
1074
lines = [' '.join([r2, r11, r12]), r11, r12]
579
1075
encoded_body = '\n'.join(lines)
581
responses = [(('ok', ), encoded_body)]
582
1077
transport_path = 'sinhala'
583
repo, client = self.setup_fake_client_and_repository(
584
responses, transport_path)
585
result = repo.get_revision_graph(r2)
1078
repo, client = self.setup_fake_client_and_repository(transport_path)
1079
client.add_success_response_with_body(encoded_body, 'ok')
1080
result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
586
1081
self.assertEqual(
587
1082
[('call_expecting_body', 'Repository.get_revision_graph',
588
('///sinhala/', r2))],
590
1085
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
592
1087
def test_no_such_revision(self):
594
responses = [(('nosuchrevision', revid), '')]
595
1089
transport_path = 'sinhala'
596
repo, client = self.setup_fake_client_and_repository(
597
responses, transport_path)
1090
repo, client = self.setup_fake_client_and_repository(transport_path)
1091
client.add_error_response('nosuchrevision', revid)
598
1092
# also check that the right revision is reported in the error
599
1093
self.assertRaises(errors.NoSuchRevision,
600
repo.get_revision_graph, revid)
1094
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
601
1095
self.assertEqual(
602
1096
[('call_expecting_body', 'Repository.get_revision_graph',
603
('///sinhala/', revid))],
1097
('sinhala/', revid))],
1100
def test_unexpected_error(self):
1102
transport_path = 'sinhala'
1103
repo, client = self.setup_fake_client_and_repository(transport_path)
1104
client.add_error_response('AnUnexpectedError')
1105
e = self.assertRaises(errors.ErrorFromSmartServer,
1106
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1107
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
607
1110
class TestRepositoryIsShared(TestRemoteRepository):
609
1112
def test_is_shared(self):
610
1113
# ('yes', ) for Repository.is_shared -> 'True'.
611
responses = [(('yes', ), )]
612
1114
transport_path = 'quack'
613
repo, client = self.setup_fake_client_and_repository(
614
responses, transport_path)
1115
repo, client = self.setup_fake_client_and_repository(transport_path)
1116
client.add_success_response('yes')
615
1117
result = repo.is_shared()
616
1118
self.assertEqual(
617
[('call', 'Repository.is_shared', ('///quack/',))],
1119
[('call', 'Repository.is_shared', ('quack/',))],
619
1121
self.assertEqual(True, result)
621
1123
def test_is_not_shared(self):
622
1124
# ('no', ) for Repository.is_shared -> 'False'.
623
responses = [(('no', ), )]
624
1125
transport_path = 'qwack'
625
repo, client = self.setup_fake_client_and_repository(
626
responses, transport_path)
1126
repo, client = self.setup_fake_client_and_repository(transport_path)
1127
client.add_success_response('no')
627
1128
result = repo.is_shared()
628
1129
self.assertEqual(
629
[('call', 'Repository.is_shared', ('///qwack/',))],
1130
[('call', 'Repository.is_shared', ('qwack/',))],
631
1132
self.assertEqual(False, result)
634
1135
class TestRepositoryLockWrite(TestRemoteRepository):
636
1137
def test_lock_write(self):
637
responses = [(('ok', 'a token'), '')]
638
1138
transport_path = 'quack'
639
repo, client = self.setup_fake_client_and_repository(
640
responses, transport_path)
1139
repo, client = self.setup_fake_client_and_repository(transport_path)
1140
client.add_success_response('ok', 'a token')
641
1141
result = repo.lock_write()
642
1142
self.assertEqual(
643
[('call', 'Repository.lock_write', ('///quack/', ''))],
1143
[('call', 'Repository.lock_write', ('quack/', ''))],
645
1145
self.assertEqual('a token', result)
647
1147
def test_lock_write_already_locked(self):
648
responses = [(('LockContention', ), '')]
649
1148
transport_path = 'quack'
650
repo, client = self.setup_fake_client_and_repository(
651
responses, transport_path)
1149
repo, client = self.setup_fake_client_and_repository(transport_path)
1150
client.add_error_response('LockContention')
652
1151
self.assertRaises(errors.LockContention, repo.lock_write)
653
1152
self.assertEqual(
654
[('call', 'Repository.lock_write', ('///quack/', ''))],
1153
[('call', 'Repository.lock_write', ('quack/', ''))],
657
1156
def test_lock_write_unlockable(self):
658
responses = [(('UnlockableTransport', ), '')]
659
1157
transport_path = 'quack'
660
repo, client = self.setup_fake_client_and_repository(
661
responses, transport_path)
1158
repo, client = self.setup_fake_client_and_repository(transport_path)
1159
client.add_error_response('UnlockableTransport')
662
1160
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
663
1161
self.assertEqual(
664
[('call', 'Repository.lock_write', ('///quack/', ''))],
1162
[('call', 'Repository.lock_write', ('quack/', ''))],
668
1166
class TestRepositoryUnlock(TestRemoteRepository):
670
1168
def test_unlock(self):
671
responses = [(('ok', 'a token'), ''),
673
1169
transport_path = 'quack'
674
repo, client = self.setup_fake_client_and_repository(
675
responses, transport_path)
1170
repo, client = self.setup_fake_client_and_repository(transport_path)
1171
client.add_success_response('ok', 'a token')
1172
client.add_success_response('ok')
676
1173
repo.lock_write()
678
1175
self.assertEqual(
679
[('call', 'Repository.lock_write', ('///quack/', '')),
680
('call', 'Repository.unlock', ('///quack/', 'a token'))],
1176
[('call', 'Repository.lock_write', ('quack/', '')),
1177
('call', 'Repository.unlock', ('quack/', 'a token'))],
683
1180
def test_unlock_wrong_token(self):
684
1181
# If somehow the token is wrong, unlock will raise TokenMismatch.
685
responses = [(('ok', 'a token'), ''),
686
(('TokenMismatch',), '')]
687
1182
transport_path = 'quack'
688
repo, client = self.setup_fake_client_and_repository(
689
responses, transport_path)
1183
repo, client = self.setup_fake_client_and_repository(transport_path)
1184
client.add_success_response('ok', 'a token')
1185
client.add_error_response('TokenMismatch')
690
1186
repo.lock_write()
691
1187
self.assertRaises(errors.TokenMismatch, repo.unlock)
763
1255
src_repo.copy_content_into(dest_repo)
766
class TestRepositoryStreamKnitData(TestRemoteRepository):
768
def make_pack_file(self, records):
769
pack_file = StringIO()
770
pack_writer = pack.ContainerWriter(pack_file.write)
772
for bytes, names in records:
773
pack_writer.add_bytes_record(bytes, names)
778
def test_bad_pack_from_server(self):
779
"""A response with invalid data (e.g. it has a record with multiple
780
names) triggers an exception.
1258
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
1259
"""Base class for unit tests for bzrlib.remote._translate_error."""
1261
def translateTuple(self, error_tuple, **context):
1262
"""Call _translate_error with an ErrorFromSmartServer built from the
1265
:param error_tuple: A tuple of a smart server response, as would be
1266
passed to an ErrorFromSmartServer.
1267
:kwargs context: context items to call _translate_error with.
1269
:returns: The error raised by _translate_error.
1271
# Raise the ErrorFromSmartServer before passing it as an argument,
1272
# because _translate_error may need to re-raise it with a bare 'raise'
1274
server_error = errors.ErrorFromSmartServer(error_tuple)
1275
translated_error = self.translateErrorFromSmartServer(
1276
server_error, **context)
1277
return translated_error
1279
def translateErrorFromSmartServer(self, error_object, **context):
1280
"""Like translateTuple, but takes an already constructed
1281
ErrorFromSmartServer rather than a tuple.
1285
except errors.ErrorFromSmartServer, server_error:
1286
translated_error = self.assertRaises(
1287
errors.BzrError, remote._translate_error, server_error,
1289
return translated_error
1292
class TestErrorTranslationSuccess(TestErrorTranslationBase):
1293
"""Unit tests for bzrlib.remote._translate_error.
1295
Given an ErrorFromSmartServer (which has an error tuple from a smart
1296
server) and some context, _translate_error raises more specific errors from
1299
This test case covers the cases where _translate_error succeeds in
1300
translating an ErrorFromSmartServer to something better. See
1301
TestErrorTranslationRobustness for other cases.
1304
def test_NoSuchRevision(self):
1305
branch = self.make_branch('')
1307
translated_error = self.translateTuple(
1308
('NoSuchRevision', revid), branch=branch)
1309
expected_error = errors.NoSuchRevision(branch, revid)
1310
self.assertEqual(expected_error, translated_error)
1312
def test_nosuchrevision(self):
1313
repository = self.make_repository('')
1315
translated_error = self.translateTuple(
1316
('nosuchrevision', revid), repository=repository)
1317
expected_error = errors.NoSuchRevision(repository, revid)
1318
self.assertEqual(expected_error, translated_error)
1320
def test_nobranch(self):
1321
bzrdir = self.make_bzrdir('')
1322
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
1323
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
1324
self.assertEqual(expected_error, translated_error)
1326
def test_LockContention(self):
1327
translated_error = self.translateTuple(('LockContention',))
1328
expected_error = errors.LockContention('(remote lock)')
1329
self.assertEqual(expected_error, translated_error)
1331
def test_UnlockableTransport(self):
1332
bzrdir = self.make_bzrdir('')
1333
translated_error = self.translateTuple(
1334
('UnlockableTransport',), bzrdir=bzrdir)
1335
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
1336
self.assertEqual(expected_error, translated_error)
1338
def test_LockFailed(self):
1339
lock = 'str() of a server lock'
1340
why = 'str() of why'
1341
translated_error = self.translateTuple(('LockFailed', lock, why))
1342
expected_error = errors.LockFailed(lock, why)
1343
self.assertEqual(expected_error, translated_error)
1345
def test_TokenMismatch(self):
1346
token = 'a lock token'
1347
translated_error = self.translateTuple(('TokenMismatch',), token=token)
1348
expected_error = errors.TokenMismatch(token, '(remote token)')
1349
self.assertEqual(expected_error, translated_error)
1351
def test_Diverged(self):
1352
branch = self.make_branch('a')
1353
other_branch = self.make_branch('b')
1354
translated_error = self.translateTuple(
1355
('Diverged',), branch=branch, other_branch=other_branch)
1356
expected_error = errors.DivergedBranches(branch, other_branch)
1357
self.assertEqual(expected_error, translated_error)
1360
class TestErrorTranslationRobustness(TestErrorTranslationBase):
1361
"""Unit tests for bzrlib.remote._translate_error's robustness.
1363
TestErrorTranslationSuccess is for cases where _translate_error can
1364
translate successfully. This class about how _translate_err behaves when
1365
it fails to translate: it re-raises the original error.
1368
def test_unrecognised_server_error(self):
1369
"""If the error code from the server is not recognised, the original
1370
ErrorFromSmartServer is propagated unmodified.
1372
error_tuple = ('An unknown error tuple',)
1373
server_error = errors.ErrorFromSmartServer(error_tuple)
1374
translated_error = self.translateErrorFromSmartServer(server_error)
1375
self.assertEqual(server_error, translated_error)
1377
def test_context_missing_a_key(self):
1378
"""In case of a bug in the client, or perhaps an unexpected response
1379
from a server, _translate_error returns the original error tuple from
1380
the server and mutters a warning.
1382
# To translate a NoSuchRevision error _translate_error needs a 'branch'
1383
# in the context dict. So let's give it an empty context dict instead
1384
# to exercise its error recovery.
1386
error_tuple = ('NoSuchRevision', 'revid')
1387
server_error = errors.ErrorFromSmartServer(error_tuple)
1388
translated_error = self.translateErrorFromSmartServer(server_error)
1389
self.assertEqual(server_error, translated_error)
1390
# In addition to re-raising ErrorFromSmartServer, some debug info has
1391
# been muttered to the log file for developer to look at.
1392
self.assertContainsRe(
1393
self._get_log(keep_log_file=True),
1394
"Missing key 'branch' in context")
782
Not all possible errors will be caught at this stage, but obviously
783
malformed data should be.
785
record = ('bytes', [('name1',), ('name2',)])
786
pack_file = self.make_pack_file([record])
787
responses = [(('ok',), pack_file.getvalue()), ]
788
transport_path = 'quack'
789
repo, client = self.setup_fake_client_and_repository(
790
responses, transport_path)
791
stream = repo.get_data_stream(['revid'])
792
self.assertRaises(errors.SmartProtocolError, list, stream)
794
def test_backwards_compatibility(self):
795
"""If the server doesn't recognise this request, fallback to VFS."""
797
"Generic bzr smart protocol error: "
798
"bad request 'Repository.stream_knit_data_for_revisions'")
800
(('error', error_msg), '')]
801
repo, client = self.setup_fake_client_and_repository(
803
self.mock_called = False
804
repo._real_repository = MockRealRepository(self)
805
repo.get_data_stream(['revid'])
806
self.assertTrue(self.mock_called)
807
self.failIf(client.expecting_body,
808
"The protocol has been left in an unclean state that will cause "
809
"TooManyConcurrentRequests errors.")
812
class MockRealRepository(object):
813
"""Helper class for TestRepositoryStreamKnitData.test_unknown_method."""
815
def __init__(self, test):
818
def get_data_stream(self, revision_ids):
819
self.test.assertEqual(['revid'], revision_ids)
820
self.test.mock_called = True