120
124
def cancel_read_body(self):
121
125
self._fake_client.expecting_body = False
127
def read_streamed_body(self):
124
131
class FakeClient(_SmartClient):
125
132
"""Lookalike for _SmartClient allowing testing."""
127
def __init__(self, responses):
128
# We don't call the super init because there is no medium.
134
def __init__(self, responses, fake_medium_base='fake base'):
129
135
"""Create a FakeClient.
131
:param respones: A list of response-tuple, body-data pairs to be sent
137
:param responses: A list of response-tuple, body-data pairs to be sent
134
140
self.responses = responses
136
142
self.expecting_body = False
143
_SmartClient.__init__(self, FakeMedium(fake_medium_base, self._calls))
138
145
def call(self, method, *args):
139
146
self._calls.append(('call', method, args))
145
152
self.expecting_body = True
146
153
return result[0], FakeProtocol(result[1], self)
155
def call_with_body_bytes_expecting_body(self, method, args, body):
156
self._calls.append(('call_with_body_bytes_expecting_body', method,
158
result = self.responses.pop(0)
159
self.expecting_body = True
160
return result[0], FakeProtocol(result[1], self)
163
class FakeMedium(object):
165
def __init__(self, base, client_calls):
167
self.connection = FakeConnection(client_calls)
168
self._client_calls = client_calls
171
class FakeConnection(object):
173
def __init__(self, client_calls):
174
self._remote_is_at_least_1_2 = True
175
self._client_calls = client_calls
177
def disconnect(self):
178
self._client_calls.append(('disconnect medium',))
181
class TestVfsHas(tests.TestCase):
183
def test_unicode_path(self):
184
client = FakeClient([(('yes',), )], '/')
185
transport = RemoteTransport('bzr://localhost/', _client=client)
186
filename = u'/hell\u00d8'.encode('utf8')
187
result = transport.has(filename)
189
[('call', 'has', (filename,))],
191
self.assertTrue(result)
149
194
class TestBzrDirOpenBranch(tests.TestCase):
151
196
def test_branch_present(self):
152
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )])
153
197
transport = MemoryTransport()
154
198
transport.mkdir('quack')
155
199
transport = transport.clone('quack')
200
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )],
156
202
bzrdir = RemoteBzrDir(transport, _client=client)
157
203
result = bzrdir.open_branch()
158
204
self.assertEqual(
159
[('call', 'BzrDir.open_branch', ('///quack/',)),
160
('call', 'BzrDir.find_repository', ('///quack/',))],
205
[('call', 'BzrDir.open_branch', ('quack/',)),
206
('call', 'BzrDir.find_repository', ('quack/',))],
162
208
self.assertIsInstance(result, RemoteBranch)
163
209
self.assertEqual(bzrdir, result.bzrdir)
165
211
def test_branch_missing(self):
166
client = FakeClient([(('nobranch',), )])
167
212
transport = MemoryTransport()
168
213
transport.mkdir('quack')
169
214
transport = transport.clone('quack')
215
client = FakeClient([(('nobranch',), )], transport.base)
170
216
bzrdir = RemoteBzrDir(transport, _client=client)
171
217
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
172
218
self.assertEqual(
173
[('call', 'BzrDir.open_branch', ('///quack/',))],
219
[('call', 'BzrDir.open_branch', ('quack/',))],
222
def test__get_tree_branch(self):
223
# _get_tree_branch is a form of open_branch, but it should only ask for
224
# branch opening, not any other network requests.
227
calls.append("Called")
229
transport = MemoryTransport()
230
# no requests on the network - catches other api calls being made.
231
client = FakeClient([], transport.base)
232
bzrdir = RemoteBzrDir(transport, _client=client)
233
# patch the open_branch call to record that it was called.
234
bzrdir.open_branch = open_branch
235
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
236
self.assertEqual(["Called"], calls)
237
self.assertEqual([], client._calls)
239
def test_url_quoting_of_path(self):
240
# Relpaths on the wire should not be URL-escaped. So "~" should be
241
# transmitted as "~", not "%7E".
242
transport = RemoteTransport('bzr://localhost/~hello/')
243
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )],
245
bzrdir = RemoteBzrDir(transport, _client=client)
246
result = bzrdir.open_branch()
248
[('call', 'BzrDir.open_branch', ('~hello/',)),
249
('call', 'BzrDir.find_repository', ('~hello/',))],
176
252
def check_open_repository(self, rich_root, subtrees):
253
transport = MemoryTransport()
254
transport.mkdir('quack')
255
transport = transport.clone('quack')
178
257
rich_response = 'yes'
182
261
subtree_response = 'yes'
184
263
subtree_response = 'no'
185
client = FakeClient([(('ok', '', rich_response, subtree_response), ),])
186
transport = MemoryTransport()
187
transport.mkdir('quack')
188
transport = transport.clone('quack')
264
client = FakeClient([(('ok', '', rich_response, subtree_response), ),],
189
266
bzrdir = RemoteBzrDir(transport, _client=client)
190
267
result = bzrdir.open_repository()
191
268
self.assertEqual(
192
[('call', 'BzrDir.find_repository', ('///quack/',))],
269
[('call', 'BzrDir.find_repository', ('quack/',))],
194
271
self.assertIsInstance(result, RemoteRepository)
195
272
self.assertEqual(bzrdir, result.bzrdir)
249
326
result = branch.last_revision_info()
251
328
self.assertEqual(
252
[('call', 'Branch.last_revision_info', ('///quack/',))],
329
[('call', 'Branch.last_revision_info', ('quack/',))],
254
331
self.assertEqual((0, NULL_REVISION), result)
256
333
def test_non_empty_branch(self):
257
334
# in a non-empty branch we also decode the response properly
258
335
revid = u'\xc8'.encode('utf8')
259
client = FakeClient([(('ok', '2', revid), )])
260
336
transport = MemoryTransport()
337
client = FakeClient([(('ok', '2', revid), )], transport.base)
261
338
transport.mkdir('kwaak')
262
339
transport = transport.clone('kwaak')
263
340
# we do not want bzrdir to make any remote calls
620
class TestRepositoryGetGraph(TestRemoteRepository):
622
def test_get_graph(self):
623
# get_graph returns a graph with the repository as the
626
transport_path = 'quack'
627
repo, client = self.setup_fake_client_and_repository(
628
responses, transport_path)
629
graph = repo.get_graph()
630
self.assertEqual(graph._parents_provider, repo)
633
class TestRepositoryGetParentMap(TestRemoteRepository):
635
def test_get_parent_map_caching(self):
636
# get_parent_map returns from cache until unlock()
637
# setup a reponse with two revisions
638
r1 = u'\u0e33'.encode('utf8')
639
r2 = u'\u0dab'.encode('utf8')
640
lines = [' '.join([r2, r1]), r1]
641
encoded_body = bz2.compress('\n'.join(lines))
642
responses = [(('ok', ), encoded_body), (('ok', ), encoded_body)]
644
transport_path = 'quack'
645
repo, client = self.setup_fake_client_and_repository(
646
responses, transport_path)
648
graph = repo.get_graph()
649
parents = graph.get_parent_map([r2])
650
self.assertEqual({r2: (r1,)}, parents)
651
# locking and unlocking deeper should not reset
654
parents = graph.get_parent_map([r1])
655
self.assertEqual({r1: (NULL_REVISION,)}, parents)
657
[('call_with_body_bytes_expecting_body',
658
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
661
# now we call again, and it should use the second response.
663
graph = repo.get_graph()
664
parents = graph.get_parent_map([r1])
665
self.assertEqual({r1: (NULL_REVISION,)}, parents)
667
[('call_with_body_bytes_expecting_body',
668
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
669
('call_with_body_bytes_expecting_body',
670
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
675
def test_get_parent_map_reconnects_if_unknown_method(self):
677
"Generic bzr smart protocol error: "
678
"bad request 'Repository.get_parent_map'")
680
(('error', error_msg), ''),
682
transport_path = 'quack'
683
repo, client = self.setup_fake_client_and_repository(
684
responses, transport_path)
685
rev_id = 'revision-id'
686
parents = repo.get_parent_map([rev_id])
688
[('call_with_body_bytes_expecting_body',
689
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
690
('disconnect medium',),
691
('call_expecting_body', 'Repository.get_revision_graph',
541
697
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
543
699
def test_null_revision(self):
783
946
malformed data should be.
785
948
record = ('bytes', [('name1',), ('name2',)])
786
pack_file = self.make_pack_file([record])
787
responses = [(('ok',), pack_file.getvalue()), ]
949
pack_stream = self.make_pack_stream([record])
950
responses = [(('ok',), pack_stream), ]
788
951
transport_path = 'quack'
789
952
repo, client = self.setup_fake_client_and_repository(
790
953
responses, transport_path)
791
stream = repo.get_data_stream(['revid'])
954
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
955
stream = repo.get_data_stream_for_search(search)
792
956
self.assertRaises(errors.SmartProtocolError, list, stream)
794
958
def test_backwards_compatibility(self):
795
959
"""If the server doesn't recognise this request, fallback to VFS."""
797
961
"Generic bzr smart protocol error: "
798
"bad request 'Repository.stream_knit_data_for_revisions'")
962
"bad request 'Repository.stream_revisions_chunked'")
800
964
(('error', error_msg), '')]
801
965
repo, client = self.setup_fake_client_and_repository(
802
966
responses, 'path')
803
967
self.mock_called = False
804
968
repo._real_repository = MockRealRepository(self)
805
repo.get_data_stream(['revid'])
969
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
970
repo.get_data_stream_for_search(search)
806
971
self.assertTrue(self.mock_called)
807
972
self.failIf(client.expecting_body,
808
973
"The protocol has been left in an unclean state that will cause "