108
109
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
110
111
def __init__(self, body, fake_client):
111
self._body_buffer = StringIO(body)
113
self._body_buffer = None
112
114
self._fake_client = fake_client
114
116
def read_body_bytes(self, count=-1):
117
if self._body_buffer is None:
118
self._body_buffer = StringIO(self.body)
115
119
bytes = self._body_buffer.read(count)
116
120
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
117
121
self._fake_client.expecting_body = False
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))
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):
149
169
class TestBzrDirOpenBranch(tests.TestCase):
151
171
def test_branch_present(self):
152
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )])
153
172
transport = MemoryTransport()
154
173
transport.mkdir('quack')
155
174
transport = transport.clone('quack')
175
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )],
156
177
bzrdir = RemoteBzrDir(transport, _client=client)
157
178
result = bzrdir.open_branch()
158
179
self.assertEqual(
159
[('call', 'BzrDir.open_branch', ('///quack/',)),
160
('call', 'BzrDir.find_repository', ('///quack/',))],
180
[('call', 'BzrDir.open_branch', ('quack/',)),
181
('call', 'BzrDir.find_repository', ('quack/',))],
162
183
self.assertIsInstance(result, RemoteBranch)
163
184
self.assertEqual(bzrdir, result.bzrdir)
165
186
def test_branch_missing(self):
166
client = FakeClient([(('nobranch',), )])
167
187
transport = MemoryTransport()
168
188
transport.mkdir('quack')
169
189
transport = transport.clone('quack')
190
client = FakeClient([(('nobranch',), )], transport.base)
170
191
bzrdir = RemoteBzrDir(transport, _client=client)
171
192
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
172
193
self.assertEqual(
173
[('call', 'BzrDir.open_branch', ('///quack/',))],
194
[('call', 'BzrDir.open_branch', ('quack/',))],
176
197
def check_open_repository(self, rich_root, subtrees):
198
transport = MemoryTransport()
199
transport.mkdir('quack')
200
transport = transport.clone('quack')
178
202
rich_response = 'yes'
182
206
subtree_response = 'yes'
184
208
subtree_response = 'no'
185
client = FakeClient([(('ok', '', rich_response, subtree_response), ),])
186
transport = MemoryTransport()
187
transport.mkdir('quack')
188
transport = transport.clone('quack')
209
client = FakeClient([(('ok', '', rich_response, subtree_response), ),],
189
211
bzrdir = RemoteBzrDir(transport, _client=client)
190
212
result = bzrdir.open_repository()
191
213
self.assertEqual(
192
[('call', 'BzrDir.find_repository', ('///quack/',))],
214
[('call', 'BzrDir.find_repository', ('quack/',))],
194
216
self.assertIsInstance(result, RemoteRepository)
195
217
self.assertEqual(bzrdir, result.bzrdir)
240
262
def test_empty_branch(self):
241
263
# in an empty branch we decode the response properly
242
client = FakeClient([(('ok', '0', 'null:'), )])
243
264
transport = MemoryTransport()
265
client = FakeClient([(('ok', '0', 'null:'), )], transport.base)
244
266
transport.mkdir('quack')
245
267
transport = transport.clone('quack')
246
268
# we do not want bzrdir to make any remote calls
249
271
result = branch.last_revision_info()
251
273
self.assertEqual(
252
[('call', 'Branch.last_revision_info', ('///quack/',))],
274
[('call', 'Branch.last_revision_info', ('quack/',))],
254
276
self.assertEqual((0, NULL_REVISION), result)
256
278
def test_non_empty_branch(self):
257
279
# in a non-empty branch we also decode the response properly
258
280
revid = u'\xc8'.encode('utf8')
259
client = FakeClient([(('ok', '2', revid), )])
260
281
transport = MemoryTransport()
282
client = FakeClient([(('ok', '2', revid), )], transport.base)
261
283
transport.mkdir('kwaak')
262
284
transport = transport.clone('kwaak')
263
285
# we do not want bzrdir to make any remote calls
276
298
def test_set_empty(self):
277
299
# set_revision_history([]) is translated to calling
278
300
# Branch.set_last_revision(path, '') on the wire.
301
transport = MemoryTransport()
302
transport.mkdir('branch')
303
transport = transport.clone('branch')
279
305
client = FakeClient([
281
307
(('ok', 'branch token', 'repo token'), ),
282
308
# set_last_revision
286
transport = MemoryTransport()
287
transport.mkdir('branch')
288
transport = transport.clone('branch')
290
313
bzrdir = RemoteBzrDir(transport, _client=False)
291
314
branch = RemoteBranch(bzrdir, None, _client=client)
292
315
# This is a hack to work around the problem that RemoteBranch currently
297
320
result = branch.set_revision_history([])
298
321
self.assertEqual(
299
322
[('call', 'Branch.set_last_revision',
300
('///branch/', 'branch token', 'repo token', 'null:'))],
323
('branch/', 'branch token', 'repo token', 'null:'))],
303
326
self.assertEqual(None, result)
305
328
def test_set_nonempty(self):
306
329
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
307
330
# Branch.set_last_revision(path, rev-idN) on the wire.
331
transport = MemoryTransport()
332
transport.mkdir('branch')
333
transport = transport.clone('branch')
308
335
client = FakeClient([
310
337
(('ok', 'branch token', 'repo token'), ),
311
338
# set_last_revision
315
transport = MemoryTransport()
316
transport.mkdir('branch')
317
transport = transport.clone('branch')
319
343
bzrdir = RemoteBzrDir(transport, _client=False)
320
344
branch = RemoteBranch(bzrdir, None, _client=client)
321
345
# This is a hack to work around the problem that RemoteBranch currently
328
352
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
329
353
self.assertEqual(
330
354
[('call', 'Branch.set_last_revision',
331
('///branch/', 'branch token', 'repo token', 'rev-id2'))],
355
('branch/', 'branch token', 'repo token', 'rev-id2'))],
334
358
self.assertEqual(None, result)
369
393
def test_get_branch_conf(self):
370
394
# in an empty branch we decode the response properly
371
client = FakeClient([(('ok', ), 'config file body')])
395
client = FakeClient([(('ok', ), 'config file body')], self.get_url())
372
396
# we need to make a real branch because the remote_branch.control_files
373
397
# will trigger _ensure_real.
374
398
branch = self.make_branch('quack')
378
402
branch = RemoteBranch(bzrdir, None, _client=client)
379
403
result = branch.control_files.get('branch.conf')
380
404
self.assertEqual(
381
[('call_expecting_body', 'Branch.get_config_file', ('///quack/',))],
405
[('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
383
407
self.assertEqual('config file body', result.read())
386
410
class TestBranchLockWrite(tests.TestCase):
388
412
def test_lock_write_unlockable(self):
389
client = FakeClient([(('UnlockableTransport', ), '')])
390
413
transport = MemoryTransport()
414
client = FakeClient([(('UnlockableTransport', ), '')], transport.base)
391
415
transport.mkdir('quack')
392
416
transport = transport.clone('quack')
393
417
# we do not want bzrdir to make any remote calls
395
419
branch = RemoteBranch(bzrdir, None, _client=client)
396
420
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
397
421
self.assertEqual(
398
[('call', 'Branch.lock_write', ('///quack/', '', ''))],
422
[('call', 'Branch.lock_write', ('quack/', '', ''))],
468
492
:param transport_path: Path below the root of the MemoryTransport
469
493
where the repository will be created.
471
client = FakeClient(responses)
472
495
transport = MemoryTransport()
473
496
transport.mkdir(transport_path)
497
client = FakeClient(responses, transport.base)
474
498
transport = transport.clone(transport_path)
475
499
# we do not want bzrdir to make any remote calls
476
500
bzrdir = RemoteBzrDir(transport, _client=False)
507
531
result = repo.gather_stats(revid)
508
532
self.assertEqual(
509
533
[('call_expecting_body', 'Repository.gather_stats',
510
('///quick/', revid, 'no'))],
534
('quick/', revid, 'no'))],
512
536
self.assertEqual({'revisions': 2, 'size': 18,
513
537
'firstrev': (123456.300, 3600),
529
553
result = repo.gather_stats(revid, True)
530
554
self.assertEqual(
531
555
[('call_expecting_body', 'Repository.gather_stats',
532
('///buick/', revid, 'yes'))],
556
('buick/', revid, 'yes'))],
534
558
self.assertEqual({'revisions': 2, 'size': 18,
535
559
'committers': 128,
565
class TestRepositoryGetGraph(TestRemoteRepository):
567
def test_get_graph(self):
568
# get_graph returns a graph with the repository as the
571
transport_path = 'quack'
572
repo, client = self.setup_fake_client_and_repository(
573
responses, transport_path)
574
graph = repo.get_graph()
575
self.assertEqual(graph._parents_provider, repo)
578
class TestRepositoryGetParentMap(TestRemoteRepository):
580
def test_get_parent_map_caching(self):
581
# get_parent_map returns from cache until unlock()
582
# setup a reponse with two revisions
583
r1 = u'\u0e33'.encode('utf8')
584
r2 = u'\u0dab'.encode('utf8')
585
lines = [' '.join([r2, r1]), r1]
586
encoded_body = '\n'.join(lines)
587
responses = [(('ok', ), encoded_body), (('ok', ), encoded_body)]
589
transport_path = 'quack'
590
repo, client = self.setup_fake_client_and_repository(
591
responses, transport_path)
593
graph = repo.get_graph()
594
parents = graph.get_parent_map([r2])
595
self.assertEqual({r2: (r1,)}, parents)
596
# locking and unlocking deeper should not reset
599
parents = graph.get_parent_map([r1])
600
self.assertEqual({r1: (NULL_REVISION,)}, parents)
602
[('call_expecting_body', 'Repository.get_parent_map',
606
# now we call again, and it should use the second response.
608
graph = repo.get_graph()
609
parents = graph.get_parent_map([r1])
610
self.assertEqual({r1: (NULL_REVISION,)}, parents)
612
[('call_expecting_body', 'Repository.get_parent_map',
614
('call_expecting_body', 'Repository.get_parent_map',
541
621
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
543
623
def test_null_revision(self):
614
694
responses, transport_path)
615
695
result = repo.is_shared()
616
696
self.assertEqual(
617
[('call', 'Repository.is_shared', ('///quack/',))],
697
[('call', 'Repository.is_shared', ('quack/',))],
619
699
self.assertEqual(True, result)
626
706
responses, transport_path)
627
707
result = repo.is_shared()
628
708
self.assertEqual(
629
[('call', 'Repository.is_shared', ('///qwack/',))],
709
[('call', 'Repository.is_shared', ('qwack/',))],
631
711
self.assertEqual(False, result)
640
720
responses, transport_path)
641
721
result = repo.lock_write()
642
722
self.assertEqual(
643
[('call', 'Repository.lock_write', ('///quack/', ''))],
723
[('call', 'Repository.lock_write', ('quack/', ''))],
645
725
self.assertEqual('a token', result)
651
731
responses, transport_path)
652
732
self.assertRaises(errors.LockContention, repo.lock_write)
653
733
self.assertEqual(
654
[('call', 'Repository.lock_write', ('///quack/', ''))],
734
[('call', 'Repository.lock_write', ('quack/', ''))],
657
737
def test_lock_write_unlockable(self):
676
756
repo.lock_write()
678
758
self.assertEqual(
679
[('call', 'Repository.lock_write', ('///quack/', '')),
680
('call', 'Repository.unlock', ('///quack/', 'a token'))],
759
[('call', 'Repository.lock_write', ('quack/', '')),
760
('call', 'Repository.unlock', ('quack/', 'a token'))],
683
763
def test_unlock_wrong_token(self):
775
855
pack_file.seek(0)
858
def make_pack_stream(self, records):
859
pack_serialiser = pack.ContainerSerialiser()
860
yield pack_serialiser.begin()
861
for bytes, names in records:
862
yield pack_serialiser.bytes_record(bytes, names)
863
yield pack_serialiser.end()
778
865
def test_bad_pack_from_server(self):
779
866
"""A response with invalid data (e.g. it has a record with multiple
780
867
names) triggers an exception.
783
870
malformed data should be.
785
872
record = ('bytes', [('name1',), ('name2',)])
786
pack_file = self.make_pack_file([record])
787
responses = [(('ok',), pack_file.getvalue()), ]
873
pack_stream = self.make_pack_stream([record])
874
responses = [(('ok',), pack_stream), ]
788
875
transport_path = 'quack'
789
876
repo, client = self.setup_fake_client_and_repository(
790
877
responses, transport_path)
791
stream = repo.get_data_stream(['revid'])
878
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
879
stream = repo.get_data_stream_for_search(search)
792
880
self.assertRaises(errors.SmartProtocolError, list, stream)
794
882
def test_backwards_compatibility(self):
795
883
"""If the server doesn't recognise this request, fallback to VFS."""
797
885
"Generic bzr smart protocol error: "
798
"bad request 'Repository.stream_knit_data_for_revisions'")
886
"bad request 'Repository.stream_revisions_chunked'")
800
888
(('error', error_msg), '')]
801
889
repo, client = self.setup_fake_client_and_repository(
802
890
responses, 'path')
803
891
self.mock_called = False
804
892
repo._real_repository = MockRealRepository(self)
805
repo.get_data_stream(['revid'])
893
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
894
repo.get_data_stream_for_search(search)
806
895
self.assertTrue(self.mock_called)
807
896
self.failIf(client.expecting_body,
808
897
"The protocol has been left in an unclean state that will cause "