197
120
def cancel_read_body(self):
198
121
self._fake_client.expecting_body = False
200
def read_streamed_body(self):
204
124
class FakeClient(_SmartClient):
205
125
"""Lookalike for _SmartClient allowing testing."""
127
def __init__(self, responses):
128
# We don't call the super init because there is no medium.
129
"""Create a FakeClient.
207
def __init__(self, fake_medium_base='fake base'):
208
"""Create a FakeClient."""
131
:param respones: A list of response-tuple, body-data pairs to be sent
134
self.responses = responses
211
136
self.expecting_body = False
212
# if non-None, this is the list of expected calls, with only the
213
# method name and arguments included. the body might be hard to
214
# compute so is not included. If a call is None, that call can
216
self._expected_calls = None
217
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
219
def add_expected_call(self, call_name, call_args, response_type,
220
response_args, response_body=None):
221
if self._expected_calls is None:
222
self._expected_calls = []
223
self._expected_calls.append((call_name, call_args))
224
self.responses.append((response_type, response_args, response_body))
226
def add_success_response(self, *args):
227
self.responses.append(('success', args, None))
229
def add_success_response_with_body(self, body, *args):
230
self.responses.append(('success', args, body))
231
if self._expected_calls is not None:
232
self._expected_calls.append(None)
234
def add_error_response(self, *args):
235
self.responses.append(('error', args))
237
def add_unknown_method_response(self, verb):
238
self.responses.append(('unknown', verb))
240
def finished_test(self):
241
if self._expected_calls:
242
raise AssertionError("%r finished but was still expecting %r"
243
% (self, self._expected_calls[0]))
245
def _get_next_response(self):
247
response_tuple = self.responses.pop(0)
248
except IndexError, e:
249
raise AssertionError("%r didn't expect any more calls"
251
if response_tuple[0] == 'unknown':
252
raise errors.UnknownSmartMethod(response_tuple[1])
253
elif response_tuple[0] == 'error':
254
raise errors.ErrorFromSmartServer(response_tuple[1])
255
return response_tuple
257
def _check_call(self, method, args):
258
if self._expected_calls is None:
259
# the test should be updated to say what it expects
262
next_call = self._expected_calls.pop(0)
264
raise AssertionError("%r didn't expect any more calls "
266
% (self, method, args,))
267
if next_call is None:
269
if method != next_call[0] or args != next_call[1]:
270
raise AssertionError("%r expected %r%r "
272
% (self, next_call[0], next_call[1], method, args,))
274
138
def call(self, method, *args):
275
self._check_call(method, args)
276
139
self._calls.append(('call', method, args))
277
return self._get_next_response()[1]
140
return self.responses.pop(0)[0]
279
142
def call_expecting_body(self, method, *args):
280
self._check_call(method, args)
281
143
self._calls.append(('call_expecting_body', method, args))
282
result = self._get_next_response()
283
self.expecting_body = True
284
return result[1], FakeProtocol(result[2], self)
286
def call_with_body_bytes(self, method, args, body):
287
self._check_call(method, args)
288
self._calls.append(('call_with_body_bytes', method, args, body))
289
result = self._get_next_response()
290
return result[1], FakeProtocol(result[2], self)
292
def call_with_body_bytes_expecting_body(self, method, args, body):
293
self._check_call(method, args)
294
self._calls.append(('call_with_body_bytes_expecting_body', method,
296
result = self._get_next_response()
297
self.expecting_body = True
298
return result[1], FakeProtocol(result[2], self)
300
def call_with_body_stream(self, args, stream):
301
# Explicitly consume the stream before checking for an error, because
302
# that's what happens a real medium.
303
stream = list(stream)
304
self._check_call(args[0], args[1:])
305
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
306
result = self._get_next_response()
307
# The second value returned from call_with_body_stream is supposed to
308
# be a response_handler object, but so far no tests depend on that.
309
response_handler = None
310
return result[1], response_handler
313
class FakeMedium(medium.SmartClientMedium):
315
def __init__(self, client_calls, base):
316
medium.SmartClientMedium.__init__(self, base)
317
self._client_calls = client_calls
319
def disconnect(self):
320
self._client_calls.append(('disconnect medium',))
323
class TestVfsHas(tests.TestCase):
325
def test_unicode_path(self):
326
client = FakeClient('/')
327
client.add_success_response('yes',)
328
transport = RemoteTransport('bzr://localhost/', _client=client)
329
filename = u'/hell\u00d8'.encode('utf8')
330
result = transport.has(filename)
332
[('call', 'has', (filename,))],
334
self.assertTrue(result)
337
class TestRemote(tests.TestCaseWithMemoryTransport):
339
def get_branch_format(self):
340
reference_bzrdir_format = bzrdir.format_registry.get('default')()
341
return reference_bzrdir_format.get_branch_format()
343
def get_repo_format(self):
344
reference_bzrdir_format = bzrdir.format_registry.get('default')()
345
return reference_bzrdir_format.repository_format
347
def assertFinished(self, fake_client):
348
"""Assert that all of a FakeClient's expected calls have occurred."""
349
fake_client.finished_test()
352
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
353
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
355
def assertRemotePath(self, expected, client_base, transport_base):
356
"""Assert that the result of
357
SmartClientMedium.remote_path_from_transport is the expected value for
358
a given client_base and transport_base.
360
client_medium = medium.SmartClientMedium(client_base)
361
transport = get_transport(transport_base)
362
result = client_medium.remote_path_from_transport(transport)
363
self.assertEqual(expected, result)
365
def test_remote_path_from_transport(self):
366
"""SmartClientMedium.remote_path_from_transport calculates a URL for
367
the given transport relative to the root of the client base URL.
369
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
370
self.assertRemotePath(
371
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
373
def assertRemotePathHTTP(self, expected, transport_base, relpath):
374
"""Assert that the result of
375
HttpTransportBase.remote_path_from_transport is the expected value for
376
a given transport_base and relpath of that transport. (Note that
377
HttpTransportBase is a subclass of SmartClientMedium)
379
base_transport = get_transport(transport_base)
380
client_medium = base_transport.get_smart_medium()
381
cloned_transport = base_transport.clone(relpath)
382
result = client_medium.remote_path_from_transport(cloned_transport)
383
self.assertEqual(expected, result)
385
def test_remote_path_from_transport_http(self):
386
"""Remote paths for HTTP transports are calculated differently to other
387
transports. They are just relative to the client base, not the root
388
directory of the host.
390
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
391
self.assertRemotePathHTTP(
392
'../xyz/', scheme + '//host/path', '../xyz/')
393
self.assertRemotePathHTTP(
394
'xyz/', scheme + '//host/path', 'xyz/')
397
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
398
"""Tests for the behaviour of client_medium.remote_is_at_least."""
400
def test_initially_unlimited(self):
401
"""A fresh medium assumes that the remote side supports all
404
client_medium = medium.SmartClientMedium('dummy base')
405
self.assertFalse(client_medium._is_remote_before((99, 99)))
407
def test__remember_remote_is_before(self):
408
"""Calling _remember_remote_is_before ratchets down the known remote
411
client_medium = medium.SmartClientMedium('dummy base')
412
# Mark the remote side as being less than 1.6. The remote side may
414
client_medium._remember_remote_is_before((1, 6))
415
self.assertTrue(client_medium._is_remote_before((1, 6)))
416
self.assertFalse(client_medium._is_remote_before((1, 5)))
417
# Calling _remember_remote_is_before again with a lower value works.
418
client_medium._remember_remote_is_before((1, 5))
419
self.assertTrue(client_medium._is_remote_before((1, 5)))
420
# You cannot call _remember_remote_is_before with a larger value.
422
AssertionError, client_medium._remember_remote_is_before, (1, 9))
425
class TestBzrDirCloningMetaDir(TestRemote):
427
def test_backwards_compat(self):
428
self.setup_smart_server_with_call_log()
429
a_dir = self.make_bzrdir('.')
430
self.reset_smart_call_log()
431
verb = 'BzrDir.cloning_metadir'
432
self.disable_verb(verb)
433
format = a_dir.cloning_metadir()
434
call_count = len([call for call in self.hpss_calls if
435
call.call.method == verb])
436
self.assertEqual(1, call_count)
438
def test_branch_reference(self):
439
transport = self.get_transport('quack')
440
referenced = self.make_branch('referenced')
441
expected = referenced.bzrdir.cloning_metadir()
442
client = FakeClient(transport.base)
443
client.add_expected_call(
444
'BzrDir.cloning_metadir', ('quack/', 'False'),
445
'error', ('BranchReference',)),
446
client.add_expected_call(
447
'BzrDir.open_branchV3', ('quack/',),
448
'success', ('ref', self.get_url('referenced'))),
449
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
451
result = a_bzrdir.cloning_metadir()
452
# We should have got a control dir matching the referenced branch.
453
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
454
self.assertEqual(expected._repository_format, result._repository_format)
455
self.assertEqual(expected._branch_format, result._branch_format)
456
self.assertFinished(client)
458
def test_current_server(self):
459
transport = self.get_transport('.')
460
transport = transport.clone('quack')
461
self.make_bzrdir('quack')
462
client = FakeClient(transport.base)
463
reference_bzrdir_format = bzrdir.format_registry.get('default')()
464
control_name = reference_bzrdir_format.network_name()
465
client.add_expected_call(
466
'BzrDir.cloning_metadir', ('quack/', 'False'),
467
'success', (control_name, '', ('branch', ''))),
468
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
470
result = a_bzrdir.cloning_metadir()
471
# We should have got a reference control dir with default branch and
472
# repository formats.
473
# This pokes a little, just to be sure.
474
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
475
self.assertEqual(None, result._repository_format)
476
self.assertEqual(None, result._branch_format)
477
self.assertFinished(client)
480
class TestBzrDirOpen(TestRemote):
482
def make_fake_client_and_transport(self, path='quack'):
483
transport = MemoryTransport()
484
transport.mkdir(path)
485
transport = transport.clone(path)
486
client = FakeClient(transport.base)
487
return client, transport
489
def test_absent(self):
490
client, transport = self.make_fake_client_and_transport()
491
client.add_expected_call(
492
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
493
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
494
remote.RemoteBzrDirFormat(), _client=client, _force_probe=True)
495
self.assertFinished(client)
497
def test_present_without_workingtree(self):
498
client, transport = self.make_fake_client_and_transport()
499
client.add_expected_call(
500
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
501
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
502
_client=client, _force_probe=True)
503
self.assertIsInstance(bd, RemoteBzrDir)
504
self.assertFalse(bd.has_workingtree())
505
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
506
self.assertFinished(client)
508
def test_present_with_workingtree(self):
509
client, transport = self.make_fake_client_and_transport()
510
client.add_expected_call(
511
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
512
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
513
_client=client, _force_probe=True)
514
self.assertIsInstance(bd, RemoteBzrDir)
515
self.assertTrue(bd.has_workingtree())
516
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
517
self.assertFinished(client)
519
def test_backwards_compat(self):
520
client, transport = self.make_fake_client_and_transport()
521
client.add_expected_call(
522
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
523
client.add_expected_call(
524
'BzrDir.open', ('quack/',), 'success', ('yes',))
525
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
526
_client=client, _force_probe=True)
527
self.assertIsInstance(bd, RemoteBzrDir)
528
self.assertFinished(client)
531
class TestBzrDirOpenBranch(TestRemote):
533
def test_backwards_compat(self):
534
self.setup_smart_server_with_call_log()
535
self.make_branch('.')
536
a_dir = BzrDir.open(self.get_url('.'))
537
self.reset_smart_call_log()
538
verb = 'BzrDir.open_branchV3'
539
self.disable_verb(verb)
540
format = a_dir.open_branch()
541
call_count = len([call for call in self.hpss_calls if
542
call.call.method == verb])
543
self.assertEqual(1, call_count)
144
result = self.responses.pop(0)
145
self.expecting_body = True
146
return result[0], FakeProtocol(result[1], self)
149
class TestBzrDirOpenBranch(tests.TestCase):
545
151
def test_branch_present(self):
546
reference_format = self.get_repo_format()
547
network_name = reference_format.network_name()
548
branch_network_name = self.get_branch_format().network_name()
152
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )])
549
153
transport = MemoryTransport()
550
154
transport.mkdir('quack')
551
155
transport = transport.clone('quack')
552
client = FakeClient(transport.base)
553
client.add_expected_call(
554
'BzrDir.open_branchV3', ('quack/',),
555
'success', ('branch', branch_network_name))
556
client.add_expected_call(
557
'BzrDir.find_repositoryV3', ('quack/',),
558
'success', ('ok', '', 'no', 'no', 'no', network_name))
559
client.add_expected_call(
560
'Branch.get_stacked_on_url', ('quack/',),
561
'error', ('NotStacked',))
562
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
156
bzrdir = RemoteBzrDir(transport, _client=client)
564
157
result = bzrdir.open_branch()
159
[('call', 'BzrDir.open_branch', ('///quack/',)),
160
('call', 'BzrDir.find_repository', ('///quack/',))],
565
162
self.assertIsInstance(result, RemoteBranch)
566
163
self.assertEqual(bzrdir, result.bzrdir)
567
self.assertFinished(client)
569
165
def test_branch_missing(self):
166
client = FakeClient([(('nobranch',), )])
570
167
transport = MemoryTransport()
571
168
transport.mkdir('quack')
572
169
transport = transport.clone('quack')
573
client = FakeClient(transport.base)
574
client.add_error_response('nobranch')
575
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
170
bzrdir = RemoteBzrDir(transport, _client=client)
577
171
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
578
172
self.assertEqual(
579
[('call', 'BzrDir.open_branchV3', ('quack/',))],
173
[('call', 'BzrDir.open_branch', ('///quack/',))],
582
def test__get_tree_branch(self):
583
# _get_tree_branch is a form of open_branch, but it should only ask for
584
# branch opening, not any other network requests.
587
calls.append("Called")
589
transport = MemoryTransport()
590
# no requests on the network - catches other api calls being made.
591
client = FakeClient(transport.base)
592
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
594
# patch the open_branch call to record that it was called.
595
bzrdir.open_branch = open_branch
596
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
597
self.assertEqual(["Called"], calls)
598
self.assertEqual([], client._calls)
600
def test_url_quoting_of_path(self):
601
# Relpaths on the wire should not be URL-escaped. So "~" should be
602
# transmitted as "~", not "%7E".
603
transport = RemoteTCPTransport('bzr://localhost/~hello/')
604
client = FakeClient(transport.base)
605
reference_format = self.get_repo_format()
606
network_name = reference_format.network_name()
607
branch_network_name = self.get_branch_format().network_name()
608
client.add_expected_call(
609
'BzrDir.open_branchV3', ('~hello/',),
610
'success', ('branch', branch_network_name))
611
client.add_expected_call(
612
'BzrDir.find_repositoryV3', ('~hello/',),
613
'success', ('ok', '', 'no', 'no', 'no', network_name))
614
client.add_expected_call(
615
'Branch.get_stacked_on_url', ('~hello/',),
616
'error', ('NotStacked',))
617
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
619
result = bzrdir.open_branch()
620
self.assertFinished(client)
622
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
623
reference_format = self.get_repo_format()
624
network_name = reference_format.network_name()
625
transport = MemoryTransport()
626
transport.mkdir('quack')
627
transport = transport.clone('quack')
176
def check_open_repository(self, rich_root, subtrees):
629
178
rich_response = 'yes'
663
210
RemoteBzrDirFormat.probe_transport, OldServerTransport())
666
class TestBzrDirCreateBranch(TestRemote):
668
def test_backwards_compat(self):
669
self.setup_smart_server_with_call_log()
670
repo = self.make_repository('.')
671
self.reset_smart_call_log()
672
self.disable_verb('BzrDir.create_branch')
673
branch = repo.bzrdir.create_branch()
674
create_branch_call_count = len([call for call in self.hpss_calls if
675
call.call.method == 'BzrDir.create_branch'])
676
self.assertEqual(1, create_branch_call_count)
678
def test_current_server(self):
679
transport = self.get_transport('.')
680
transport = transport.clone('quack')
681
self.make_repository('quack')
682
client = FakeClient(transport.base)
683
reference_bzrdir_format = bzrdir.format_registry.get('default')()
684
reference_format = reference_bzrdir_format.get_branch_format()
685
network_name = reference_format.network_name()
686
reference_repo_fmt = reference_bzrdir_format.repository_format
687
reference_repo_name = reference_repo_fmt.network_name()
688
client.add_expected_call(
689
'BzrDir.create_branch', ('quack/', network_name),
690
'success', ('ok', network_name, '', 'no', 'no', 'yes',
691
reference_repo_name))
692
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
694
branch = a_bzrdir.create_branch()
695
# We should have got a remote branch
696
self.assertIsInstance(branch, remote.RemoteBranch)
697
# its format should have the settings from the response
698
format = branch._format
699
self.assertEqual(network_name, format.network_name())
702
class TestBzrDirCreateRepository(TestRemote):
704
def test_backwards_compat(self):
705
self.setup_smart_server_with_call_log()
706
bzrdir = self.make_bzrdir('.')
707
self.reset_smart_call_log()
708
self.disable_verb('BzrDir.create_repository')
709
repo = bzrdir.create_repository()
710
create_repo_call_count = len([call for call in self.hpss_calls if
711
call.call.method == 'BzrDir.create_repository'])
712
self.assertEqual(1, create_repo_call_count)
714
def test_current_server(self):
715
transport = self.get_transport('.')
716
transport = transport.clone('quack')
717
self.make_bzrdir('quack')
718
client = FakeClient(transport.base)
719
reference_bzrdir_format = bzrdir.format_registry.get('default')()
720
reference_format = reference_bzrdir_format.repository_format
721
network_name = reference_format.network_name()
722
client.add_expected_call(
723
'BzrDir.create_repository', ('quack/',
724
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
726
'success', ('ok', 'yes', 'yes', 'yes', network_name))
727
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
729
repo = a_bzrdir.create_repository()
730
# We should have got a remote repository
731
self.assertIsInstance(repo, remote.RemoteRepository)
732
# its format should have the settings from the response
733
format = repo._format
734
self.assertTrue(format.rich_root_data)
735
self.assertTrue(format.supports_tree_reference)
736
self.assertTrue(format.supports_external_lookups)
737
self.assertEqual(network_name, format.network_name())
740
class TestBzrDirOpenRepository(TestRemote):
742
def test_backwards_compat_1_2_3(self):
743
# fallback all the way to the first version.
744
reference_format = self.get_repo_format()
745
network_name = reference_format.network_name()
746
server_url = 'bzr://example.com/'
747
self.permit_url(server_url)
748
client = FakeClient(server_url)
749
client.add_unknown_method_response('BzrDir.find_repositoryV3')
750
client.add_unknown_method_response('BzrDir.find_repositoryV2')
751
client.add_success_response('ok', '', 'no', 'no')
752
# A real repository instance will be created to determine the network
754
client.add_success_response_with_body(
755
"Bazaar-NG meta directory, format 1\n", 'ok')
756
client.add_success_response_with_body(
757
reference_format.get_format_string(), 'ok')
758
# PackRepository wants to do a stat
759
client.add_success_response('stat', '0', '65535')
760
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
762
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
764
repo = bzrdir.open_repository()
766
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
767
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
768
('call', 'BzrDir.find_repository', ('quack/',)),
769
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
770
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
771
('call', 'stat', ('/quack/.bzr/repository',)),
774
self.assertEqual(network_name, repo._format.network_name())
776
def test_backwards_compat_2(self):
777
# fallback to find_repositoryV2
778
reference_format = self.get_repo_format()
779
network_name = reference_format.network_name()
780
server_url = 'bzr://example.com/'
781
self.permit_url(server_url)
782
client = FakeClient(server_url)
783
client.add_unknown_method_response('BzrDir.find_repositoryV3')
784
client.add_success_response('ok', '', 'no', 'no', 'no')
785
# A real repository instance will be created to determine the network
787
client.add_success_response_with_body(
788
"Bazaar-NG meta directory, format 1\n", 'ok')
789
client.add_success_response_with_body(
790
reference_format.get_format_string(), 'ok')
791
# PackRepository wants to do a stat
792
client.add_success_response('stat', '0', '65535')
793
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
795
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
797
repo = bzrdir.open_repository()
799
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
800
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
801
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
802
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
803
('call', 'stat', ('/quack/.bzr/repository',)),
806
self.assertEqual(network_name, repo._format.network_name())
808
def test_current_server(self):
809
reference_format = self.get_repo_format()
810
network_name = reference_format.network_name()
811
transport = MemoryTransport()
812
transport.mkdir('quack')
813
transport = transport.clone('quack')
814
client = FakeClient(transport.base)
815
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
816
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
818
repo = bzrdir.open_repository()
820
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
822
self.assertEqual(network_name, repo._format.network_name())
825
class TestBzrDirFormatInitializeEx(TestRemote):
827
def test_success(self):
828
"""Simple test for typical successful call."""
829
fmt = bzrdir.RemoteBzrDirFormat()
830
default_format_name = BzrDirFormat.get_default_format().network_name()
831
transport = self.get_transport()
832
client = FakeClient(transport.base)
833
client.add_expected_call(
834
'BzrDirFormat.initialize_ex_1.16',
835
(default_format_name, 'path', 'False', 'False', 'False', '',
836
'', '', '', 'False'),
838
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
839
'bzrdir fmt', 'False', '', '', 'repo lock token'))
840
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
841
# it's currently hard to test that without supplying a real remote
842
# transport connected to a real server.
843
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
844
transport, False, False, False, None, None, None, None, False)
845
self.assertFinished(client)
847
def test_error(self):
848
"""Error responses are translated, e.g. 'PermissionDenied' raises the
849
corresponding error from the client.
851
fmt = bzrdir.RemoteBzrDirFormat()
852
default_format_name = BzrDirFormat.get_default_format().network_name()
853
transport = self.get_transport()
854
client = FakeClient(transport.base)
855
client.add_expected_call(
856
'BzrDirFormat.initialize_ex_1.16',
857
(default_format_name, 'path', 'False', 'False', 'False', '',
858
'', '', '', 'False'),
860
('PermissionDenied', 'path', 'extra info'))
861
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
862
# it's currently hard to test that without supplying a real remote
863
# transport connected to a real server.
864
err = self.assertRaises(errors.PermissionDenied,
865
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
866
False, False, False, None, None, None, None, False)
867
self.assertEqual('path', err.path)
868
self.assertEqual(': extra info', err.extra)
869
self.assertFinished(client)
871
def test_error_from_real_server(self):
872
"""Integration test for error translation."""
873
transport = self.make_smart_server('foo')
874
transport = transport.clone('no-such-path')
875
fmt = bzrdir.RemoteBzrDirFormat()
876
err = self.assertRaises(errors.NoSuchFile,
877
fmt.initialize_on_transport_ex, transport, create_prefix=False)
880
213
class OldSmartClient(object):
881
214
"""A fake smart client for test_old_version that just returns a version one
882
215
response to the 'hello' (query version) command.
905
235
return OldSmartClient()
908
class RemoteBzrDirTestCase(TestRemote):
910
def make_remote_bzrdir(self, transport, client):
911
"""Make a RemotebzrDir using 'client' as the _client."""
912
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
916
class RemoteBranchTestCase(RemoteBzrDirTestCase):
918
def lock_remote_branch(self, branch):
919
"""Trick a RemoteBranch into thinking it is locked."""
920
branch._lock_mode = 'w'
921
branch._lock_count = 2
922
branch._lock_token = 'branch token'
923
branch._repo_lock_token = 'repo token'
924
branch.repository._lock_mode = 'w'
925
branch.repository._lock_count = 2
926
branch.repository._lock_token = 'repo token'
928
def make_remote_branch(self, transport, client):
929
"""Make a RemoteBranch using 'client' as its _SmartClient.
931
A RemoteBzrDir and RemoteRepository will also be created to fill out
932
the RemoteBranch, albeit with stub values for some of their attributes.
934
# we do not want bzrdir to make any remote calls, so use False as its
935
# _client. If it tries to make a remote call, this will fail
937
bzrdir = self.make_remote_bzrdir(transport, False)
938
repo = RemoteRepository(bzrdir, None, _client=client)
939
branch_format = self.get_branch_format()
940
format = RemoteBranchFormat(network_name=branch_format.network_name())
941
return RemoteBranch(bzrdir, repo, _client=client, format=format)
944
class TestBranchGetParent(RemoteBranchTestCase):
946
def test_no_parent(self):
947
# in an empty branch we decode the response properly
948
transport = MemoryTransport()
949
client = FakeClient(transport.base)
950
client.add_expected_call(
951
'Branch.get_stacked_on_url', ('quack/',),
952
'error', ('NotStacked',))
953
client.add_expected_call(
954
'Branch.get_parent', ('quack/',),
956
transport.mkdir('quack')
957
transport = transport.clone('quack')
958
branch = self.make_remote_branch(transport, client)
959
result = branch.get_parent()
960
self.assertFinished(client)
961
self.assertEqual(None, result)
963
def test_parent_relative(self):
964
transport = MemoryTransport()
965
client = FakeClient(transport.base)
966
client.add_expected_call(
967
'Branch.get_stacked_on_url', ('kwaak/',),
968
'error', ('NotStacked',))
969
client.add_expected_call(
970
'Branch.get_parent', ('kwaak/',),
971
'success', ('../foo/',))
972
transport.mkdir('kwaak')
973
transport = transport.clone('kwaak')
974
branch = self.make_remote_branch(transport, client)
975
result = branch.get_parent()
976
self.assertEqual(transport.clone('../foo').base, result)
978
def test_parent_absolute(self):
979
transport = MemoryTransport()
980
client = FakeClient(transport.base)
981
client.add_expected_call(
982
'Branch.get_stacked_on_url', ('kwaak/',),
983
'error', ('NotStacked',))
984
client.add_expected_call(
985
'Branch.get_parent', ('kwaak/',),
986
'success', ('http://foo/',))
987
transport.mkdir('kwaak')
988
transport = transport.clone('kwaak')
989
branch = self.make_remote_branch(transport, client)
990
result = branch.get_parent()
991
self.assertEqual('http://foo/', result)
992
self.assertFinished(client)
995
class TestBranchSetParentLocation(RemoteBranchTestCase):
997
def test_no_parent(self):
998
# We call the verb when setting parent to None
999
transport = MemoryTransport()
1000
client = FakeClient(transport.base)
1001
client.add_expected_call(
1002
'Branch.get_stacked_on_url', ('quack/',),
1003
'error', ('NotStacked',))
1004
client.add_expected_call(
1005
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1007
transport.mkdir('quack')
1008
transport = transport.clone('quack')
1009
branch = self.make_remote_branch(transport, client)
1010
branch._lock_token = 'b'
1011
branch._repo_lock_token = 'r'
1012
branch._set_parent_location(None)
1013
self.assertFinished(client)
1015
def test_parent(self):
1016
transport = MemoryTransport()
1017
client = FakeClient(transport.base)
1018
client.add_expected_call(
1019
'Branch.get_stacked_on_url', ('kwaak/',),
1020
'error', ('NotStacked',))
1021
client.add_expected_call(
1022
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1024
transport.mkdir('kwaak')
1025
transport = transport.clone('kwaak')
1026
branch = self.make_remote_branch(transport, client)
1027
branch._lock_token = 'b'
1028
branch._repo_lock_token = 'r'
1029
branch._set_parent_location('foo')
1030
self.assertFinished(client)
1032
def test_backwards_compat(self):
1033
self.setup_smart_server_with_call_log()
1034
branch = self.make_branch('.')
1035
self.reset_smart_call_log()
1036
verb = 'Branch.set_parent_location'
1037
self.disable_verb(verb)
1038
branch.set_parent('http://foo/')
1039
self.assertLength(12, self.hpss_calls)
1042
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1044
def test_backwards_compat(self):
1045
self.setup_smart_server_with_call_log()
1046
branch = self.make_branch('.')
1047
self.reset_smart_call_log()
1048
verb = 'Branch.get_tags_bytes'
1049
self.disable_verb(verb)
1050
branch.tags.get_tag_dict()
1051
call_count = len([call for call in self.hpss_calls if
1052
call.call.method == verb])
1053
self.assertEqual(1, call_count)
1055
def test_trivial(self):
1056
transport = MemoryTransport()
1057
client = FakeClient(transport.base)
1058
client.add_expected_call(
1059
'Branch.get_stacked_on_url', ('quack/',),
1060
'error', ('NotStacked',))
1061
client.add_expected_call(
1062
'Branch.get_tags_bytes', ('quack/',),
1064
transport.mkdir('quack')
1065
transport = transport.clone('quack')
1066
branch = self.make_remote_branch(transport, client)
1067
result = branch.tags.get_tag_dict()
1068
self.assertFinished(client)
1069
self.assertEqual({}, result)
1072
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1074
def test_trivial(self):
1075
transport = MemoryTransport()
1076
client = FakeClient(transport.base)
1077
client.add_expected_call(
1078
'Branch.get_stacked_on_url', ('quack/',),
1079
'error', ('NotStacked',))
1080
client.add_expected_call(
1081
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1083
transport.mkdir('quack')
1084
transport = transport.clone('quack')
1085
branch = self.make_remote_branch(transport, client)
1086
self.lock_remote_branch(branch)
1087
branch._set_tags_bytes('tags bytes')
1088
self.assertFinished(client)
1089
self.assertEqual('tags bytes', client._calls[-1][-1])
1091
def test_backwards_compatible(self):
1092
transport = MemoryTransport()
1093
client = FakeClient(transport.base)
1094
client.add_expected_call(
1095
'Branch.get_stacked_on_url', ('quack/',),
1096
'error', ('NotStacked',))
1097
client.add_expected_call(
1098
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1099
'unknown', ('Branch.set_tags_bytes',))
1100
transport.mkdir('quack')
1101
transport = transport.clone('quack')
1102
branch = self.make_remote_branch(transport, client)
1103
self.lock_remote_branch(branch)
1104
class StubRealBranch(object):
1107
def _set_tags_bytes(self, bytes):
1108
self.calls.append(('set_tags_bytes', bytes))
1109
real_branch = StubRealBranch()
1110
branch._real_branch = real_branch
1111
branch._set_tags_bytes('tags bytes')
1112
# Call a second time, to exercise the 'remote version already inferred'
1114
branch._set_tags_bytes('tags bytes')
1115
self.assertFinished(client)
1117
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1120
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
238
class TestBranchLastRevisionInfo(tests.TestCase):
1122
240
def test_empty_branch(self):
1123
241
# in an empty branch we decode the response properly
242
client = FakeClient([(('ok', '0', 'null:'), )])
1124
243
transport = MemoryTransport()
1125
client = FakeClient(transport.base)
1126
client.add_expected_call(
1127
'Branch.get_stacked_on_url', ('quack/',),
1128
'error', ('NotStacked',))
1129
client.add_expected_call(
1130
'Branch.last_revision_info', ('quack/',),
1131
'success', ('ok', '0', 'null:'))
1132
244
transport.mkdir('quack')
1133
245
transport = transport.clone('quack')
1134
branch = self.make_remote_branch(transport, client)
246
# we do not want bzrdir to make any remote calls
247
bzrdir = RemoteBzrDir(transport, _client=False)
248
branch = RemoteBranch(bzrdir, None, _client=client)
1135
249
result = branch.last_revision_info()
1136
self.assertFinished(client)
252
[('call', 'Branch.last_revision_info', ('///quack/',))],
1137
254
self.assertEqual((0, NULL_REVISION), result)
1139
256
def test_non_empty_branch(self):
1140
257
# in a non-empty branch we also decode the response properly
1141
258
revid = u'\xc8'.encode('utf8')
259
client = FakeClient([(('ok', '2', revid), )])
1142
260
transport = MemoryTransport()
1143
client = FakeClient(transport.base)
1144
client.add_expected_call(
1145
'Branch.get_stacked_on_url', ('kwaak/',),
1146
'error', ('NotStacked',))
1147
client.add_expected_call(
1148
'Branch.last_revision_info', ('kwaak/',),
1149
'success', ('ok', '2', revid))
1150
261
transport.mkdir('kwaak')
1151
262
transport = transport.clone('kwaak')
1152
branch = self.make_remote_branch(transport, client)
263
# we do not want bzrdir to make any remote calls
264
bzrdir = RemoteBzrDir(transport, _client=False)
265
branch = RemoteBranch(bzrdir, None, _client=client)
1153
266
result = branch.last_revision_info()
269
[('call', 'Branch.last_revision_info', ('///kwaak/',))],
1154
271
self.assertEqual((2, revid), result)
1157
class TestBranch_get_stacked_on_url(TestRemote):
1158
"""Test Branch._get_stacked_on_url rpc"""
1160
def test_get_stacked_on_invalid_url(self):
1161
# test that asking for a stacked on url the server can't access works.
1162
# This isn't perfect, but then as we're in the same process there
1163
# really isn't anything we can do to be 100% sure that the server
1164
# doesn't just open in - this test probably needs to be rewritten using
1165
# a spawn()ed server.
1166
stacked_branch = self.make_branch('stacked', format='1.9')
1167
memory_branch = self.make_branch('base', format='1.9')
1168
vfs_url = self.get_vfs_only_url('base')
1169
stacked_branch.set_stacked_on_url(vfs_url)
1170
transport = stacked_branch.bzrdir.root_transport
1171
client = FakeClient(transport.base)
1172
client.add_expected_call(
1173
'Branch.get_stacked_on_url', ('stacked/',),
1174
'success', ('ok', vfs_url))
1175
# XXX: Multiple calls are bad, this second call documents what is
1177
client.add_expected_call(
1178
'Branch.get_stacked_on_url', ('stacked/',),
1179
'success', ('ok', vfs_url))
1180
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1182
repo_fmt = remote.RemoteRepositoryFormat()
1183
repo_fmt._custom_format = stacked_branch.repository._format
1184
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1186
result = branch.get_stacked_on_url()
1187
self.assertEqual(vfs_url, result)
1189
def test_backwards_compatible(self):
1190
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1191
base_branch = self.make_branch('base', format='1.6')
1192
stacked_branch = self.make_branch('stacked', format='1.6')
1193
stacked_branch.set_stacked_on_url('../base')
1194
client = FakeClient(self.get_url())
1195
branch_network_name = self.get_branch_format().network_name()
1196
client.add_expected_call(
1197
'BzrDir.open_branchV3', ('stacked/',),
1198
'success', ('branch', branch_network_name))
1199
client.add_expected_call(
1200
'BzrDir.find_repositoryV3', ('stacked/',),
1201
'success', ('ok', '', 'no', 'no', 'yes',
1202
stacked_branch.repository._format.network_name()))
1203
# called twice, once from constructor and then again by us
1204
client.add_expected_call(
1205
'Branch.get_stacked_on_url', ('stacked/',),
1206
'unknown', ('Branch.get_stacked_on_url',))
1207
client.add_expected_call(
1208
'Branch.get_stacked_on_url', ('stacked/',),
1209
'unknown', ('Branch.get_stacked_on_url',))
1210
# this will also do vfs access, but that goes direct to the transport
1211
# and isn't seen by the FakeClient.
1212
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1213
remote.RemoteBzrDirFormat(), _client=client)
1214
branch = bzrdir.open_branch()
1215
result = branch.get_stacked_on_url()
1216
self.assertEqual('../base', result)
1217
self.assertFinished(client)
1218
# it's in the fallback list both for the RemoteRepository and its vfs
1220
self.assertEqual(1, len(branch.repository._fallback_repositories))
1222
len(branch.repository._real_repository._fallback_repositories))
1224
def test_get_stacked_on_real_branch(self):
1225
base_branch = self.make_branch('base', format='1.6')
1226
stacked_branch = self.make_branch('stacked', format='1.6')
1227
stacked_branch.set_stacked_on_url('../base')
1228
reference_format = self.get_repo_format()
1229
network_name = reference_format.network_name()
1230
client = FakeClient(self.get_url())
1231
branch_network_name = self.get_branch_format().network_name()
1232
client.add_expected_call(
1233
'BzrDir.open_branchV3', ('stacked/',),
1234
'success', ('branch', branch_network_name))
1235
client.add_expected_call(
1236
'BzrDir.find_repositoryV3', ('stacked/',),
1237
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1238
# called twice, once from constructor and then again by us
1239
client.add_expected_call(
1240
'Branch.get_stacked_on_url', ('stacked/',),
1241
'success', ('ok', '../base'))
1242
client.add_expected_call(
1243
'Branch.get_stacked_on_url', ('stacked/',),
1244
'success', ('ok', '../base'))
1245
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1246
remote.RemoteBzrDirFormat(), _client=client)
1247
branch = bzrdir.open_branch()
1248
result = branch.get_stacked_on_url()
1249
self.assertEqual('../base', result)
1250
self.assertFinished(client)
1251
# it's in the fallback list both for the RemoteRepository.
1252
self.assertEqual(1, len(branch.repository._fallback_repositories))
1253
# And we haven't had to construct a real repository.
1254
self.assertEqual(None, branch.repository._real_repository)
1257
class TestBranchSetLastRevision(RemoteBranchTestCase):
274
class TestBranchSetLastRevision(tests.TestCase):
1259
276
def test_set_empty(self):
1260
277
# set_revision_history([]) is translated to calling
1261
278
# Branch.set_last_revision(path, '') on the wire.
279
client = FakeClient([
281
(('ok', 'branch token', 'repo token'), ),
1262
286
transport = MemoryTransport()
1263
287
transport.mkdir('branch')
1264
288
transport = transport.clone('branch')
1266
client = FakeClient(transport.base)
1267
client.add_expected_call(
1268
'Branch.get_stacked_on_url', ('branch/',),
1269
'error', ('NotStacked',))
1270
client.add_expected_call(
1271
'Branch.lock_write', ('branch/', '', ''),
1272
'success', ('ok', 'branch token', 'repo token'))
1273
client.add_expected_call(
1274
'Branch.last_revision_info',
1276
'success', ('ok', '0', 'null:'))
1277
client.add_expected_call(
1278
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1280
client.add_expected_call(
1281
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1283
branch = self.make_remote_branch(transport, client)
290
bzrdir = RemoteBzrDir(transport, _client=False)
291
branch = RemoteBranch(bzrdir, None, _client=client)
1284
292
# This is a hack to work around the problem that RemoteBranch currently
1285
293
# unnecessarily invokes _ensure_real upon a call to lock_write.
1286
294
branch._ensure_real = lambda: None
1287
295
branch.lock_write()
1288
297
result = branch.set_revision_history([])
299
[('call', 'Branch.set_last_revision',
300
('///branch/', 'branch token', 'repo token', 'null:'))],
1290
303
self.assertEqual(None, result)
1291
self.assertFinished(client)
1293
305
def test_set_nonempty(self):
1294
306
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1295
307
# Branch.set_last_revision(path, rev-idN) on the wire.
308
client = FakeClient([
310
(('ok', 'branch token', 'repo token'), ),
1296
315
transport = MemoryTransport()
1297
316
transport.mkdir('branch')
1298
317
transport = transport.clone('branch')
1300
client = FakeClient(transport.base)
1301
client.add_expected_call(
1302
'Branch.get_stacked_on_url', ('branch/',),
1303
'error', ('NotStacked',))
1304
client.add_expected_call(
1305
'Branch.lock_write', ('branch/', '', ''),
1306
'success', ('ok', 'branch token', 'repo token'))
1307
client.add_expected_call(
1308
'Branch.last_revision_info',
1310
'success', ('ok', '0', 'null:'))
1312
encoded_body = bz2.compress('\n'.join(lines))
1313
client.add_success_response_with_body(encoded_body, 'ok')
1314
client.add_expected_call(
1315
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1317
client.add_expected_call(
1318
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1320
branch = self.make_remote_branch(transport, client)
319
bzrdir = RemoteBzrDir(transport, _client=False)
320
branch = RemoteBranch(bzrdir, None, _client=client)
1321
321
# This is a hack to work around the problem that RemoteBranch currently
1322
322
# unnecessarily invokes _ensure_real upon a call to lock_write.
1323
323
branch._ensure_real = lambda: None
1324
324
# Lock the branch, reset the record of remote calls.
1325
325
branch.lock_write()
1326
328
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
330
[('call', 'Branch.set_last_revision',
331
('///branch/', 'branch token', 'repo token', 'rev-id2'))],
1328
334
self.assertEqual(None, result)
1329
self.assertFinished(client)
1331
336
def test_no_such_revision(self):
337
# A response of 'NoSuchRevision' is translated into an exception.
338
client = FakeClient([
340
(('ok', 'branch token', 'repo token'), ),
342
(('NoSuchRevision', 'rev-id'), ),
1332
345
transport = MemoryTransport()
1333
346
transport.mkdir('branch')
1334
347
transport = transport.clone('branch')
1335
# A response of 'NoSuchRevision' is translated into an exception.
1336
client = FakeClient(transport.base)
1337
client.add_expected_call(
1338
'Branch.get_stacked_on_url', ('branch/',),
1339
'error', ('NotStacked',))
1340
client.add_expected_call(
1341
'Branch.lock_write', ('branch/', '', ''),
1342
'success', ('ok', 'branch token', 'repo token'))
1343
client.add_expected_call(
1344
'Branch.last_revision_info',
1346
'success', ('ok', '0', 'null:'))
1347
# get_graph calls to construct the revision history, for the set_rh
1350
encoded_body = bz2.compress('\n'.join(lines))
1351
client.add_success_response_with_body(encoded_body, 'ok')
1352
client.add_expected_call(
1353
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1354
'error', ('NoSuchRevision', 'rev-id'))
1355
client.add_expected_call(
1356
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1359
branch = self.make_remote_branch(transport, client)
349
bzrdir = RemoteBzrDir(transport, _client=False)
350
branch = RemoteBranch(bzrdir, None, _client=client)
351
branch._ensure_real = lambda: None
1360
352
branch.lock_write()
1361
355
self.assertRaises(
1362
356
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1364
self.assertFinished(client)
1366
def test_tip_change_rejected(self):
1367
"""TipChangeRejected responses cause a TipChangeRejected exception to
1370
transport = MemoryTransport()
1371
transport.mkdir('branch')
1372
transport = transport.clone('branch')
1373
client = FakeClient(transport.base)
1374
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1375
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1376
client.add_expected_call(
1377
'Branch.get_stacked_on_url', ('branch/',),
1378
'error', ('NotStacked',))
1379
client.add_expected_call(
1380
'Branch.lock_write', ('branch/', '', ''),
1381
'success', ('ok', 'branch token', 'repo token'))
1382
client.add_expected_call(
1383
'Branch.last_revision_info',
1385
'success', ('ok', '0', 'null:'))
1387
encoded_body = bz2.compress('\n'.join(lines))
1388
client.add_success_response_with_body(encoded_body, 'ok')
1389
client.add_expected_call(
1390
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1391
'error', ('TipChangeRejected', rejection_msg_utf8))
1392
client.add_expected_call(
1393
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1395
branch = self.make_remote_branch(transport, client)
1396
branch._ensure_real = lambda: None
1398
# The 'TipChangeRejected' error response triggered by calling
1399
# set_revision_history causes a TipChangeRejected exception.
1400
err = self.assertRaises(
1401
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1402
# The UTF-8 message from the response has been decoded into a unicode
1404
self.assertIsInstance(err.msg, unicode)
1405
self.assertEqual(rejection_msg_unicode, err.msg)
1407
self.assertFinished(client)
1410
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1412
def test_set_last_revision_info(self):
1413
# set_last_revision_info(num, 'rev-id') is translated to calling
1414
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1415
transport = MemoryTransport()
1416
transport.mkdir('branch')
1417
transport = transport.clone('branch')
1418
client = FakeClient(transport.base)
1419
# get_stacked_on_url
1420
client.add_error_response('NotStacked')
1422
client.add_success_response('ok', 'branch token', 'repo token')
1423
# query the current revision
1424
client.add_success_response('ok', '0', 'null:')
1426
client.add_success_response('ok')
1428
client.add_success_response('ok')
1430
branch = self.make_remote_branch(transport, client)
1431
# Lock the branch, reset the record of remote calls.
1434
result = branch.set_last_revision_info(1234, 'a-revision-id')
1436
[('call', 'Branch.last_revision_info', ('branch/',)),
1437
('call', 'Branch.set_last_revision_info',
1438
('branch/', 'branch token', 'repo token',
1439
'1234', 'a-revision-id'))],
1441
self.assertEqual(None, result)
1443
def test_no_such_revision(self):
1444
# A response of 'NoSuchRevision' is translated into an exception.
1445
transport = MemoryTransport()
1446
transport.mkdir('branch')
1447
transport = transport.clone('branch')
1448
client = FakeClient(transport.base)
1449
# get_stacked_on_url
1450
client.add_error_response('NotStacked')
1452
client.add_success_response('ok', 'branch token', 'repo token')
1454
client.add_error_response('NoSuchRevision', 'revid')
1456
client.add_success_response('ok')
1458
branch = self.make_remote_branch(transport, client)
1459
# Lock the branch, reset the record of remote calls.
1464
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1467
def test_backwards_compatibility(self):
1468
"""If the server does not support the Branch.set_last_revision_info
1469
verb (which is new in 1.4), then the client falls back to VFS methods.
1471
# This test is a little messy. Unlike most tests in this file, it
1472
# doesn't purely test what a Remote* object sends over the wire, and
1473
# how it reacts to responses from the wire. It instead relies partly
1474
# on asserting that the RemoteBranch will call
1475
# self._real_branch.set_last_revision_info(...).
1477
# First, set up our RemoteBranch with a FakeClient that raises
1478
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1479
transport = MemoryTransport()
1480
transport.mkdir('branch')
1481
transport = transport.clone('branch')
1482
client = FakeClient(transport.base)
1483
client.add_expected_call(
1484
'Branch.get_stacked_on_url', ('branch/',),
1485
'error', ('NotStacked',))
1486
client.add_expected_call(
1487
'Branch.last_revision_info',
1489
'success', ('ok', '0', 'null:'))
1490
client.add_expected_call(
1491
'Branch.set_last_revision_info',
1492
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1493
'unknown', 'Branch.set_last_revision_info')
1495
branch = self.make_remote_branch(transport, client)
1496
class StubRealBranch(object):
1499
def set_last_revision_info(self, revno, revision_id):
1501
('set_last_revision_info', revno, revision_id))
1502
def _clear_cached_state(self):
1504
real_branch = StubRealBranch()
1505
branch._real_branch = real_branch
1506
self.lock_remote_branch(branch)
1508
# Call set_last_revision_info, and verify it behaved as expected.
1509
result = branch.set_last_revision_info(1234, 'a-revision-id')
1511
[('set_last_revision_info', 1234, 'a-revision-id')],
1513
self.assertFinished(client)
1515
def test_unexpected_error(self):
1516
# If the server sends an error the client doesn't understand, it gets
1517
# turned into an UnknownErrorFromSmartServer, which is presented as a
1518
# non-internal error to the user.
1519
transport = MemoryTransport()
1520
transport.mkdir('branch')
1521
transport = transport.clone('branch')
1522
client = FakeClient(transport.base)
1523
# get_stacked_on_url
1524
client.add_error_response('NotStacked')
1526
client.add_success_response('ok', 'branch token', 'repo token')
1528
client.add_error_response('UnexpectedError')
1530
client.add_success_response('ok')
1532
branch = self.make_remote_branch(transport, client)
1533
# Lock the branch, reset the record of remote calls.
1537
err = self.assertRaises(
1538
errors.UnknownErrorFromSmartServer,
1539
branch.set_last_revision_info, 123, 'revid')
1540
self.assertEqual(('UnexpectedError',), err.error_tuple)
1543
def test_tip_change_rejected(self):
1544
"""TipChangeRejected responses cause a TipChangeRejected exception to
1547
transport = MemoryTransport()
1548
transport.mkdir('branch')
1549
transport = transport.clone('branch')
1550
client = FakeClient(transport.base)
1551
# get_stacked_on_url
1552
client.add_error_response('NotStacked')
1554
client.add_success_response('ok', 'branch token', 'repo token')
1556
client.add_error_response('TipChangeRejected', 'rejection message')
1558
client.add_success_response('ok')
1560
branch = self.make_remote_branch(transport, client)
1561
# Lock the branch, reset the record of remote calls.
1563
self.addCleanup(branch.unlock)
1566
# The 'TipChangeRejected' error response triggered by calling
1567
# set_last_revision_info causes a TipChangeRejected exception.
1568
err = self.assertRaises(
1569
errors.TipChangeRejected,
1570
branch.set_last_revision_info, 123, 'revid')
1571
self.assertEqual('rejection message', err.msg)
1574
class TestBranchGetSetConfig(RemoteBranchTestCase):
360
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'.
1576
369
def test_get_branch_conf(self):
1577
370
# in an empty branch we decode the response properly
1578
client = FakeClient()
1579
client.add_expected_call(
1580
'Branch.get_stacked_on_url', ('memory:///',),
1581
'error', ('NotStacked',),)
1582
client.add_success_response_with_body('# config file body', 'ok')
1583
transport = MemoryTransport()
1584
branch = self.make_remote_branch(transport, client)
1585
config = branch.get_config()
1586
config.has_explicit_nickname()
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')
1587
380
self.assertEqual(
1588
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1589
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
381
[('call_expecting_body', 'Branch.get_config_file', ('///quack/',))],
1592
def test_get_multi_line_branch_conf(self):
1593
# Make sure that multiple-line branch.conf files are supported
1595
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1596
client = FakeClient()
1597
client.add_expected_call(
1598
'Branch.get_stacked_on_url', ('memory:///',),
1599
'error', ('NotStacked',),)
1600
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1601
transport = MemoryTransport()
1602
branch = self.make_remote_branch(transport, client)
1603
config = branch.get_config()
1604
self.assertEqual(u'2', config.get_user_option('b'))
1606
def test_set_option(self):
1607
client = FakeClient()
1608
client.add_expected_call(
1609
'Branch.get_stacked_on_url', ('memory:///',),
1610
'error', ('NotStacked',),)
1611
client.add_expected_call(
1612
'Branch.lock_write', ('memory:///', '', ''),
1613
'success', ('ok', 'branch token', 'repo token'))
1614
client.add_expected_call(
1615
'Branch.set_config_option', ('memory:///', 'branch token',
1616
'repo token', 'foo', 'bar', ''),
1618
client.add_expected_call(
1619
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1621
transport = MemoryTransport()
1622
branch = self.make_remote_branch(transport, client)
1624
config = branch._get_config()
1625
config.set_option('foo', 'bar')
1627
self.assertFinished(client)
1629
def test_backwards_compat_set_option(self):
1630
self.setup_smart_server_with_call_log()
1631
branch = self.make_branch('.')
1632
verb = 'Branch.set_config_option'
1633
self.disable_verb(verb)
1635
self.addCleanup(branch.unlock)
1636
self.reset_smart_call_log()
1637
branch._get_config().set_option('value', 'name')
1638
self.assertLength(10, self.hpss_calls)
1639
self.assertEqual('value', branch._get_config().get_option('name'))
1642
class TestBranchLockWrite(RemoteBranchTestCase):
383
self.assertEqual('config file body', result.read())
386
class TestBranchLockWrite(tests.TestCase):
1644
388
def test_lock_write_unlockable(self):
389
client = FakeClient([(('UnlockableTransport', ), '')])
1645
390
transport = MemoryTransport()
1646
client = FakeClient(transport.base)
1647
client.add_expected_call(
1648
'Branch.get_stacked_on_url', ('quack/',),
1649
'error', ('NotStacked',),)
1650
client.add_expected_call(
1651
'Branch.lock_write', ('quack/', '', ''),
1652
'error', ('UnlockableTransport',))
1653
391
transport.mkdir('quack')
1654
392
transport = transport.clone('quack')
1655
branch = self.make_remote_branch(transport, client)
393
# we do not want bzrdir to make any remote calls
394
bzrdir = RemoteBzrDir(transport, _client=False)
395
branch = RemoteBranch(bzrdir, None, _client=client)
1656
396
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1657
self.assertFinished(client)
1660
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1662
def test__get_config(self):
1663
client = FakeClient()
1664
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1665
transport = MemoryTransport()
1666
bzrdir = self.make_remote_bzrdir(transport, client)
1667
config = bzrdir.get_config()
1668
self.assertEqual('/', config.get_default_stack_on())
1669
397
self.assertEqual(
1670
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
398
[('call', 'Branch.lock_write', ('///quack/', '', ''))],
1673
def test_set_option_uses_vfs(self):
1674
self.setup_smart_server_with_call_log()
1675
bzrdir = self.make_bzrdir('.')
1676
self.reset_smart_call_log()
1677
config = bzrdir.get_config()
1678
config.set_default_stack_on('/')
1679
self.assertLength(3, self.hpss_calls)
1681
def test_backwards_compat_get_option(self):
1682
self.setup_smart_server_with_call_log()
1683
bzrdir = self.make_bzrdir('.')
1684
verb = 'BzrDir.get_config_file'
1685
self.disable_verb(verb)
1686
self.reset_smart_call_log()
1687
self.assertEqual(None,
1688
bzrdir._get_config().get_option('default_stack_on'))
1689
self.assertLength(3, self.hpss_calls)
1692
402
class TestTransportIsReadonly(tests.TestCase):
1694
404
def test_true(self):
1695
client = FakeClient()
1696
client.add_success_response('yes')
405
client = FakeClient([(('yes',), '')])
1697
406
transport = RemoteTransport('bzr://example.com/', medium=False,
1699
408
self.assertEqual(True, transport.is_readonly())
1880
class TestRepositoryGetGraph(TestRemoteRepository):
1882
def test_get_graph(self):
1883
# get_graph returns a graph with a custom parents provider.
1884
transport_path = 'quack'
1885
repo, client = self.setup_fake_client_and_repository(transport_path)
1886
graph = repo.get_graph()
1887
self.assertNotEqual(graph._parents_provider, repo)
1890
class TestRepositoryGetParentMap(TestRemoteRepository):
1892
def test_get_parent_map_caching(self):
1893
# get_parent_map returns from cache until unlock()
1894
# setup a reponse with two revisions
1895
r1 = u'\u0e33'.encode('utf8')
1896
r2 = u'\u0dab'.encode('utf8')
1897
lines = [' '.join([r2, r1]), r1]
1898
encoded_body = bz2.compress('\n'.join(lines))
1900
transport_path = 'quack'
1901
repo, client = self.setup_fake_client_and_repository(transport_path)
1902
client.add_success_response_with_body(encoded_body, 'ok')
1903
client.add_success_response_with_body(encoded_body, 'ok')
1905
graph = repo.get_graph()
1906
parents = graph.get_parent_map([r2])
1907
self.assertEqual({r2: (r1,)}, parents)
1908
# locking and unlocking deeper should not reset
1911
parents = graph.get_parent_map([r1])
1912
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1914
[('call_with_body_bytes_expecting_body',
1915
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1919
# now we call again, and it should use the second response.
1921
graph = repo.get_graph()
1922
parents = graph.get_parent_map([r1])
1923
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1925
[('call_with_body_bytes_expecting_body',
1926
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1928
('call_with_body_bytes_expecting_body',
1929
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1935
def test_get_parent_map_reconnects_if_unknown_method(self):
1936
transport_path = 'quack'
1937
rev_id = 'revision-id'
1938
repo, client = self.setup_fake_client_and_repository(transport_path)
1939
client.add_unknown_method_response('Repository.get_parent_map')
1940
client.add_success_response_with_body(rev_id, 'ok')
1941
self.assertFalse(client._medium._is_remote_before((1, 2)))
1942
parents = repo.get_parent_map([rev_id])
1944
[('call_with_body_bytes_expecting_body',
1945
'Repository.get_parent_map', ('quack/', 'include-missing:',
1947
('disconnect medium',),
1948
('call_expecting_body', 'Repository.get_revision_graph',
1951
# The medium is now marked as being connected to an older server
1952
self.assertTrue(client._medium._is_remote_before((1, 2)))
1953
self.assertEqual({rev_id: ('null:',)}, parents)
1955
def test_get_parent_map_fallback_parentless_node(self):
1956
"""get_parent_map falls back to get_revision_graph on old servers. The
1957
results from get_revision_graph are tweaked to match the get_parent_map
1960
Specifically, a {key: ()} result from get_revision_graph means "no
1961
parents" for that key, which in get_parent_map results should be
1962
represented as {key: ('null:',)}.
1964
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1966
rev_id = 'revision-id'
1967
transport_path = 'quack'
1968
repo, client = self.setup_fake_client_and_repository(transport_path)
1969
client.add_success_response_with_body(rev_id, 'ok')
1970
client._medium._remember_remote_is_before((1, 2))
1971
parents = repo.get_parent_map([rev_id])
1973
[('call_expecting_body', 'Repository.get_revision_graph',
1976
self.assertEqual({rev_id: ('null:',)}, parents)
1978
def test_get_parent_map_unexpected_response(self):
1979
repo, client = self.setup_fake_client_and_repository('path')
1980
client.add_success_response('something unexpected!')
1982
errors.UnexpectedSmartServerResponse,
1983
repo.get_parent_map, ['a-revision-id'])
1985
def test_get_parent_map_negative_caches_missing_keys(self):
1986
self.setup_smart_server_with_call_log()
1987
repo = self.make_repository('foo')
1988
self.assertIsInstance(repo, RemoteRepository)
1990
self.addCleanup(repo.unlock)
1991
self.reset_smart_call_log()
1992
graph = repo.get_graph()
1993
self.assertEqual({},
1994
graph.get_parent_map(['some-missing', 'other-missing']))
1995
self.assertLength(1, self.hpss_calls)
1996
# No call if we repeat this
1997
self.reset_smart_call_log()
1998
graph = repo.get_graph()
1999
self.assertEqual({},
2000
graph.get_parent_map(['some-missing', 'other-missing']))
2001
self.assertLength(0, self.hpss_calls)
2002
# Asking for more unknown keys makes a request.
2003
self.reset_smart_call_log()
2004
graph = repo.get_graph()
2005
self.assertEqual({},
2006
graph.get_parent_map(['some-missing', 'other-missing',
2008
self.assertLength(1, self.hpss_calls)
2010
def disableExtraResults(self):
2011
self.overrideAttr(SmartServerRepositoryGetParentMap,
2012
'no_extra_results', True)
2014
def test_null_cached_missing_and_stop_key(self):
2015
self.setup_smart_server_with_call_log()
2016
# Make a branch with a single revision.
2017
builder = self.make_branch_builder('foo')
2018
builder.start_series()
2019
builder.build_snapshot('first', None, [
2020
('add', ('', 'root-id', 'directory', ''))])
2021
builder.finish_series()
2022
branch = builder.get_branch()
2023
repo = branch.repository
2024
self.assertIsInstance(repo, RemoteRepository)
2025
# Stop the server from sending extra results.
2026
self.disableExtraResults()
2028
self.addCleanup(repo.unlock)
2029
self.reset_smart_call_log()
2030
graph = repo.get_graph()
2031
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2032
# 'first' it will be a candidate for the stop_keys of subsequent
2033
# requests, and because 'null:' was queried but not returned it will be
2034
# cached as missing.
2035
self.assertEqual({'first': ('null:',)},
2036
graph.get_parent_map(['first', 'null:']))
2037
# Now query for another key. This request will pass along a recipe of
2038
# start and stop keys describing the already cached results, and this
2039
# recipe's revision count must be correct (or else it will trigger an
2040
# error from the server).
2041
self.assertEqual({}, graph.get_parent_map(['another-key']))
2042
# This assertion guards against disableExtraResults silently failing to
2043
# work, thus invalidating the test.
2044
self.assertLength(2, self.hpss_calls)
2046
def test_get_parent_map_gets_ghosts_from_result(self):
2047
# asking for a revision should negatively cache close ghosts in its
2049
self.setup_smart_server_with_call_log()
2050
tree = self.make_branch_and_memory_tree('foo')
2053
builder = treebuilder.TreeBuilder()
2054
builder.start_tree(tree)
2056
builder.finish_tree()
2057
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2058
rev_id = tree.commit('')
2062
self.addCleanup(tree.unlock)
2063
repo = tree.branch.repository
2064
self.assertIsInstance(repo, RemoteRepository)
2066
repo.get_parent_map([rev_id])
2067
self.reset_smart_call_log()
2068
# Now asking for rev_id's ghost parent should not make calls
2069
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2070
self.assertLength(0, self.hpss_calls)
2073
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2075
def test_allows_new_revisions(self):
2076
"""get_parent_map's results can be updated by commit."""
2077
smart_server = server.SmartTCPServer_for_testing()
2078
self.start_server(smart_server)
2079
self.make_branch('branch')
2080
branch = Branch.open(smart_server.get_url() + '/branch')
2081
tree = branch.create_checkout('tree', lightweight=True)
2083
self.addCleanup(tree.unlock)
2084
graph = tree.branch.repository.get_graph()
2085
# This provides an opportunity for the missing rev-id to be cached.
2086
self.assertEqual({}, graph.get_parent_map(['rev1']))
2087
tree.commit('message', rev_id='rev1')
2088
graph = tree.branch.repository.get_graph()
2089
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2092
541
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2094
543
def test_null_revision(self):
2095
544
# a null revision has the predictable result {}, we should have no wire
2096
545
# traffic when calling it with this argument
546
responses = [(('notused', ), '')]
2097
547
transport_path = 'empty'
2098
repo, client = self.setup_fake_client_and_repository(transport_path)
2099
client.add_success_response('notused')
2100
# actual RemoteRepository.get_revision_graph is gone, but there's an
2101
# equivalent private method for testing
2102
result = repo._get_revision_graph(NULL_REVISION)
548
repo, client = self.setup_fake_client_and_repository(
549
responses, transport_path)
550
result = repo.get_revision_graph(NULL_REVISION)
2103
551
self.assertEqual([], client._calls)
2104
552
self.assertEqual({}, result)
2354
696
def test_none(self):
2355
697
# repo.has_revision(None) should not cause any traffic.
2356
698
transport_path = 'quack'
2357
repo, client = self.setup_fake_client_and_repository(transport_path)
700
repo, client = self.setup_fake_client_and_repository(
701
responses, transport_path)
2359
703
# The null revision is always there, so has_revision(None) == True.
2360
self.assertEqual(True, repo.has_revision(NULL_REVISION))
704
self.assertEqual(True, repo.has_revision(None))
2362
706
# The remote repo shouldn't be accessed.
2363
707
self.assertEqual([], client._calls)
2366
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2367
"""Base class for Repository.insert_stream and .insert_stream_1.19
2371
def checkInsertEmptyStream(self, repo, client):
2372
"""Insert an empty stream, checking the result.
2374
This checks that there are no resume_tokens or missing_keys, and that
2375
the client is finished.
2377
sink = repo._get_sink()
2378
fmt = repository.RepositoryFormat.get_default_format()
2379
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2380
self.assertEqual([], resume_tokens)
2381
self.assertEqual(set(), missing_keys)
2382
self.assertFinished(client)
2385
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2386
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2389
This test case is very similar to TestRepositoryInsertStream_1_19.
2393
TestRemoteRepository.setUp(self)
2394
self.disable_verb('Repository.insert_stream_1.19')
2396
def test_unlocked_repo(self):
2397
transport_path = 'quack'
2398
repo, client = self.setup_fake_client_and_repository(transport_path)
2399
client.add_expected_call(
2400
'Repository.insert_stream_1.19', ('quack/', ''),
2401
'unknown', ('Repository.insert_stream_1.19',))
2402
client.add_expected_call(
2403
'Repository.insert_stream', ('quack/', ''),
2405
client.add_expected_call(
2406
'Repository.insert_stream', ('quack/', ''),
2408
self.checkInsertEmptyStream(repo, client)
2410
def test_locked_repo_with_no_lock_token(self):
2411
transport_path = 'quack'
2412
repo, client = self.setup_fake_client_and_repository(transport_path)
2413
client.add_expected_call(
2414
'Repository.lock_write', ('quack/', ''),
2415
'success', ('ok', ''))
2416
client.add_expected_call(
2417
'Repository.insert_stream_1.19', ('quack/', ''),
2418
'unknown', ('Repository.insert_stream_1.19',))
2419
client.add_expected_call(
2420
'Repository.insert_stream', ('quack/', ''),
2422
client.add_expected_call(
2423
'Repository.insert_stream', ('quack/', ''),
2426
self.checkInsertEmptyStream(repo, client)
2428
def test_locked_repo_with_lock_token(self):
2429
transport_path = 'quack'
2430
repo, client = self.setup_fake_client_and_repository(transport_path)
2431
client.add_expected_call(
2432
'Repository.lock_write', ('quack/', ''),
2433
'success', ('ok', 'a token'))
2434
client.add_expected_call(
2435
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2436
'unknown', ('Repository.insert_stream_1.19',))
2437
client.add_expected_call(
2438
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2440
client.add_expected_call(
2441
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2444
self.checkInsertEmptyStream(repo, client)
2446
def test_stream_with_inventory_deltas(self):
2447
"""'inventory-deltas' substreams cannot be sent to the
2448
Repository.insert_stream verb, because not all servers that implement
2449
that verb will accept them. So when one is encountered the RemoteSink
2450
immediately stops using that verb and falls back to VFS insert_stream.
2452
transport_path = 'quack'
2453
repo, client = self.setup_fake_client_and_repository(transport_path)
2454
client.add_expected_call(
2455
'Repository.insert_stream_1.19', ('quack/', ''),
2456
'unknown', ('Repository.insert_stream_1.19',))
2457
client.add_expected_call(
2458
'Repository.insert_stream', ('quack/', ''),
2460
client.add_expected_call(
2461
'Repository.insert_stream', ('quack/', ''),
2463
# Create a fake real repository for insert_stream to fall back on, so
2464
# that we can directly see the records the RemoteSink passes to the
2469
def insert_stream(self, stream, src_format, resume_tokens):
2470
for substream_kind, substream in stream:
2471
self.records.append(
2472
(substream_kind, [record.key for record in substream]))
2473
return ['fake tokens'], ['fake missing keys']
2474
fake_real_sink = FakeRealSink()
2475
class FakeRealRepository:
2476
def _get_sink(self):
2477
return fake_real_sink
2478
def is_in_write_group(self):
2480
def refresh_data(self):
2482
repo._real_repository = FakeRealRepository()
2483
sink = repo._get_sink()
2484
fmt = repository.RepositoryFormat.get_default_format()
2485
stream = self.make_stream_with_inv_deltas(fmt)
2486
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2487
# Every record from the first inventory delta should have been sent to
2489
expected_records = [
2490
('inventory-deltas', [('rev2',), ('rev3',)]),
2491
('texts', [('some-rev', 'some-file')])]
2492
self.assertEqual(expected_records, fake_real_sink.records)
2493
# The return values from the real sink's insert_stream are propagated
2494
# back to the original caller.
2495
self.assertEqual(['fake tokens'], resume_tokens)
2496
self.assertEqual(['fake missing keys'], missing_keys)
2497
self.assertFinished(client)
2499
def make_stream_with_inv_deltas(self, fmt):
2500
"""Make a simple stream with an inventory delta followed by more
2501
records and more substreams to test that all records and substreams
2502
from that point on are used.
2504
This sends, in order:
2505
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2507
* texts substream: (some-rev, some-file)
2509
# Define a stream using generators so that it isn't rewindable.
2510
inv = inventory.Inventory(revision_id='rev1')
2511
inv.root.revision = 'rev1'
2512
def stream_with_inv_delta():
2513
yield ('inventories', inventories_substream())
2514
yield ('inventory-deltas', inventory_delta_substream())
2516
versionedfile.FulltextContentFactory(
2517
('some-rev', 'some-file'), (), None, 'content')])
2518
def inventories_substream():
2519
# An empty inventory fulltext. This will be streamed normally.
2520
text = fmt._serializer.write_inventory_to_string(inv)
2521
yield versionedfile.FulltextContentFactory(
2522
('rev1',), (), None, text)
2523
def inventory_delta_substream():
2524
# An inventory delta. This can't be streamed via this verb, so it
2525
# will trigger a fallback to VFS insert_stream.
2526
entry = inv.make_entry(
2527
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2528
entry.revision = 'ghost'
2529
delta = [(None, 'newdir', 'newdir-id', entry)]
2530
serializer = inventory_delta.InventoryDeltaSerializer(
2531
versioned_root=True, tree_references=False)
2532
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2533
yield versionedfile.ChunkedContentFactory(
2534
('rev2',), (('rev1',)), None, lines)
2536
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2537
yield versionedfile.ChunkedContentFactory(
2538
('rev3',), (('rev1',)), None, lines)
2539
return stream_with_inv_delta()
2542
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2544
def test_unlocked_repo(self):
2545
transport_path = 'quack'
2546
repo, client = self.setup_fake_client_and_repository(transport_path)
2547
client.add_expected_call(
2548
'Repository.insert_stream_1.19', ('quack/', ''),
2550
client.add_expected_call(
2551
'Repository.insert_stream_1.19', ('quack/', ''),
2553
self.checkInsertEmptyStream(repo, client)
2555
def test_locked_repo_with_no_lock_token(self):
2556
transport_path = 'quack'
2557
repo, client = self.setup_fake_client_and_repository(transport_path)
2558
client.add_expected_call(
2559
'Repository.lock_write', ('quack/', ''),
2560
'success', ('ok', ''))
2561
client.add_expected_call(
2562
'Repository.insert_stream_1.19', ('quack/', ''),
2564
client.add_expected_call(
2565
'Repository.insert_stream_1.19', ('quack/', ''),
2568
self.checkInsertEmptyStream(repo, client)
2570
def test_locked_repo_with_lock_token(self):
2571
transport_path = 'quack'
2572
repo, client = self.setup_fake_client_and_repository(transport_path)
2573
client.add_expected_call(
2574
'Repository.lock_write', ('quack/', ''),
2575
'success', ('ok', 'a token'))
2576
client.add_expected_call(
2577
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2579
client.add_expected_call(
2580
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2583
self.checkInsertEmptyStream(repo, client)
2586
710
class TestRepositoryTarball(TestRemoteRepository):
2588
712
# This is a canned tarball reponse we can validate against
2637
763
src_repo.copy_content_into(dest_repo)
2640
class _StubRealPackRepository(object):
2642
def __init__(self, calls):
2644
self._pack_collection = _StubPackCollection(calls)
2646
def is_in_write_group(self):
2649
def refresh_data(self):
2650
self.calls.append(('pack collection reload_pack_names',))
2653
class _StubPackCollection(object):
2655
def __init__(self, calls):
2659
self.calls.append(('pack collection autopack',))
2662
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2663
"""Tests for RemoteRepository.autopack implementation."""
2666
"""When the server returns 'ok' and there's no _real_repository, then
2667
nothing else happens: the autopack method is done.
2669
transport_path = 'quack'
2670
repo, client = self.setup_fake_client_and_repository(transport_path)
2671
client.add_expected_call(
2672
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2674
self.assertFinished(client)
2676
def test_ok_with_real_repo(self):
2677
"""When the server returns 'ok' and there is a _real_repository, then
2678
the _real_repository's reload_pack_name's method will be called.
2680
transport_path = 'quack'
2681
repo, client = self.setup_fake_client_and_repository(transport_path)
2682
client.add_expected_call(
2683
'PackRepository.autopack', ('quack/',),
2685
repo._real_repository = _StubRealPackRepository(client._calls)
2688
[('call', 'PackRepository.autopack', ('quack/',)),
2689
('pack collection reload_pack_names',)],
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.
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)
2692
794
def test_backwards_compatibility(self):
2693
"""If the server does not recognise the PackRepository.autopack verb,
2694
fallback to the real_repository's implementation.
2696
transport_path = 'quack'
2697
repo, client = self.setup_fake_client_and_repository(transport_path)
2698
client.add_unknown_method_response('PackRepository.autopack')
2699
def stub_ensure_real():
2700
client._calls.append(('_ensure_real',))
2701
repo._real_repository = _StubRealPackRepository(client._calls)
2702
repo._ensure_real = stub_ensure_real
2705
[('call', 'PackRepository.autopack', ('quack/',)),
2707
('pack collection autopack',)],
2711
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2712
"""Base class for unit tests for bzrlib.remote._translate_error."""
2714
def translateTuple(self, error_tuple, **context):
2715
"""Call _translate_error with an ErrorFromSmartServer built from the
2718
:param error_tuple: A tuple of a smart server response, as would be
2719
passed to an ErrorFromSmartServer.
2720
:kwargs context: context items to call _translate_error with.
2722
:returns: The error raised by _translate_error.
2724
# Raise the ErrorFromSmartServer before passing it as an argument,
2725
# because _translate_error may need to re-raise it with a bare 'raise'
2727
server_error = errors.ErrorFromSmartServer(error_tuple)
2728
translated_error = self.translateErrorFromSmartServer(
2729
server_error, **context)
2730
return translated_error
2732
def translateErrorFromSmartServer(self, error_object, **context):
2733
"""Like translateTuple, but takes an already constructed
2734
ErrorFromSmartServer rather than a tuple.
2738
except errors.ErrorFromSmartServer, server_error:
2739
translated_error = self.assertRaises(
2740
errors.BzrError, remote._translate_error, server_error,
2742
return translated_error
2745
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2746
"""Unit tests for bzrlib.remote._translate_error.
2748
Given an ErrorFromSmartServer (which has an error tuple from a smart
2749
server) and some context, _translate_error raises more specific errors from
2752
This test case covers the cases where _translate_error succeeds in
2753
translating an ErrorFromSmartServer to something better. See
2754
TestErrorTranslationRobustness for other cases.
2757
def test_NoSuchRevision(self):
2758
branch = self.make_branch('')
2760
translated_error = self.translateTuple(
2761
('NoSuchRevision', revid), branch=branch)
2762
expected_error = errors.NoSuchRevision(branch, revid)
2763
self.assertEqual(expected_error, translated_error)
2765
def test_nosuchrevision(self):
2766
repository = self.make_repository('')
2768
translated_error = self.translateTuple(
2769
('nosuchrevision', revid), repository=repository)
2770
expected_error = errors.NoSuchRevision(repository, revid)
2771
self.assertEqual(expected_error, translated_error)
2773
def test_nobranch(self):
2774
bzrdir = self.make_bzrdir('')
2775
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2776
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2777
self.assertEqual(expected_error, translated_error)
2779
def test_nobranch_one_arg(self):
2780
bzrdir = self.make_bzrdir('')
2781
translated_error = self.translateTuple(
2782
('nobranch', 'extra detail'), bzrdir=bzrdir)
2783
expected_error = errors.NotBranchError(
2784
path=bzrdir.root_transport.base,
2785
detail='extra detail')
2786
self.assertEqual(expected_error, translated_error)
2788
def test_LockContention(self):
2789
translated_error = self.translateTuple(('LockContention',))
2790
expected_error = errors.LockContention('(remote lock)')
2791
self.assertEqual(expected_error, translated_error)
2793
def test_UnlockableTransport(self):
2794
bzrdir = self.make_bzrdir('')
2795
translated_error = self.translateTuple(
2796
('UnlockableTransport',), bzrdir=bzrdir)
2797
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2798
self.assertEqual(expected_error, translated_error)
2800
def test_LockFailed(self):
2801
lock = 'str() of a server lock'
2802
why = 'str() of why'
2803
translated_error = self.translateTuple(('LockFailed', lock, why))
2804
expected_error = errors.LockFailed(lock, why)
2805
self.assertEqual(expected_error, translated_error)
2807
def test_TokenMismatch(self):
2808
token = 'a lock token'
2809
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2810
expected_error = errors.TokenMismatch(token, '(remote token)')
2811
self.assertEqual(expected_error, translated_error)
2813
def test_Diverged(self):
2814
branch = self.make_branch('a')
2815
other_branch = self.make_branch('b')
2816
translated_error = self.translateTuple(
2817
('Diverged',), branch=branch, other_branch=other_branch)
2818
expected_error = errors.DivergedBranches(branch, other_branch)
2819
self.assertEqual(expected_error, translated_error)
2821
def test_ReadError_no_args(self):
2823
translated_error = self.translateTuple(('ReadError',), path=path)
2824
expected_error = errors.ReadError(path)
2825
self.assertEqual(expected_error, translated_error)
2827
def test_ReadError(self):
2829
translated_error = self.translateTuple(('ReadError', path))
2830
expected_error = errors.ReadError(path)
2831
self.assertEqual(expected_error, translated_error)
2833
def test_IncompatibleRepositories(self):
2834
translated_error = self.translateTuple(('IncompatibleRepositories',
2835
"repo1", "repo2", "details here"))
2836
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
2838
self.assertEqual(expected_error, translated_error)
2840
def test_PermissionDenied_no_args(self):
2842
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2843
expected_error = errors.PermissionDenied(path)
2844
self.assertEqual(expected_error, translated_error)
2846
def test_PermissionDenied_one_arg(self):
2848
translated_error = self.translateTuple(('PermissionDenied', path))
2849
expected_error = errors.PermissionDenied(path)
2850
self.assertEqual(expected_error, translated_error)
2852
def test_PermissionDenied_one_arg_and_context(self):
2853
"""Given a choice between a path from the local context and a path on
2854
the wire, _translate_error prefers the path from the local context.
2856
local_path = 'local path'
2857
remote_path = 'remote path'
2858
translated_error = self.translateTuple(
2859
('PermissionDenied', remote_path), path=local_path)
2860
expected_error = errors.PermissionDenied(local_path)
2861
self.assertEqual(expected_error, translated_error)
2863
def test_PermissionDenied_two_args(self):
2865
extra = 'a string with extra info'
2866
translated_error = self.translateTuple(
2867
('PermissionDenied', path, extra))
2868
expected_error = errors.PermissionDenied(path, extra)
2869
self.assertEqual(expected_error, translated_error)
2872
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2873
"""Unit tests for bzrlib.remote._translate_error's robustness.
2875
TestErrorTranslationSuccess is for cases where _translate_error can
2876
translate successfully. This class about how _translate_err behaves when
2877
it fails to translate: it re-raises the original error.
2880
def test_unrecognised_server_error(self):
2881
"""If the error code from the server is not recognised, the original
2882
ErrorFromSmartServer is propagated unmodified.
2884
error_tuple = ('An unknown error tuple',)
2885
server_error = errors.ErrorFromSmartServer(error_tuple)
2886
translated_error = self.translateErrorFromSmartServer(server_error)
2887
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2888
self.assertEqual(expected_error, translated_error)
2890
def test_context_missing_a_key(self):
2891
"""In case of a bug in the client, or perhaps an unexpected response
2892
from a server, _translate_error returns the original error tuple from
2893
the server and mutters a warning.
2895
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2896
# in the context dict. So let's give it an empty context dict instead
2897
# to exercise its error recovery.
2899
error_tuple = ('NoSuchRevision', 'revid')
2900
server_error = errors.ErrorFromSmartServer(error_tuple)
2901
translated_error = self.translateErrorFromSmartServer(server_error)
2902
self.assertEqual(server_error, translated_error)
2903
# In addition to re-raising ErrorFromSmartServer, some debug info has
2904
# been muttered to the log file for developer to look at.
2905
self.assertContainsRe(
2907
"Missing key 'branch' in context")
2909
def test_path_missing(self):
2910
"""Some translations (PermissionDenied, ReadError) can determine the
2911
'path' variable from either the wire or the local context. If neither
2912
has it, then an error is raised.
2914
error_tuple = ('ReadError',)
2915
server_error = errors.ErrorFromSmartServer(error_tuple)
2916
translated_error = self.translateErrorFromSmartServer(server_error)
2917
self.assertEqual(server_error, translated_error)
2918
# In addition to re-raising ErrorFromSmartServer, some debug info has
2919
# been muttered to the log file for developer to look at.
2920
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
2923
class TestStacking(tests.TestCaseWithTransport):
2924
"""Tests for operations on stacked remote repositories.
2926
The underlying format type must support stacking.
2929
def test_access_stacked_remote(self):
2930
# based on <http://launchpad.net/bugs/261315>
2931
# make a branch stacked on another repository containing an empty
2932
# revision, then open it over hpss - we should be able to see that
2934
base_transport = self.get_transport()
2935
base_builder = self.make_branch_builder('base', format='1.9')
2936
base_builder.start_series()
2937
base_revid = base_builder.build_snapshot('rev-id', None,
2938
[('add', ('', None, 'directory', None))],
2940
base_builder.finish_series()
2941
stacked_branch = self.make_branch('stacked', format='1.9')
2942
stacked_branch.set_stacked_on_url('../base')
2943
# start a server looking at this
2944
smart_server = server.SmartTCPServer_for_testing()
2945
self.start_server(smart_server)
2946
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2947
# can get its branch and repository
2948
remote_branch = remote_bzrdir.open_branch()
2949
remote_repo = remote_branch.repository
2950
remote_repo.lock_read()
2952
# it should have an appropriate fallback repository, which should also
2953
# be a RemoteRepository
2954
self.assertLength(1, remote_repo._fallback_repositories)
2955
self.assertIsInstance(remote_repo._fallback_repositories[0],
2957
# and it has the revision committed to the underlying repository;
2958
# these have varying implementations so we try several of them
2959
self.assertTrue(remote_repo.has_revisions([base_revid]))
2960
self.assertTrue(remote_repo.has_revision(base_revid))
2961
self.assertEqual(remote_repo.get_revision(base_revid).message,
2964
remote_repo.unlock()
2966
def prepare_stacked_remote_branch(self):
2967
"""Get stacked_upon and stacked branches with content in each."""
2968
self.setup_smart_server_with_call_log()
2969
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2970
tree1.commit('rev1', rev_id='rev1')
2971
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2972
).open_workingtree()
2973
local_tree = tree2.branch.create_checkout('local')
2974
local_tree.commit('local changes make me feel good.')
2975
branch2 = Branch.open(self.get_url('tree2'))
2977
self.addCleanup(branch2.unlock)
2978
return tree1.branch, branch2
2980
def test_stacked_get_parent_map(self):
2981
# the public implementation of get_parent_map obeys stacking
2982
_, branch = self.prepare_stacked_remote_branch()
2983
repo = branch.repository
2984
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2986
def test_unstacked_get_parent_map(self):
2987
# _unstacked_provider.get_parent_map ignores stacking
2988
_, branch = self.prepare_stacked_remote_branch()
2989
provider = branch.repository._unstacked_provider
2990
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2992
def fetch_stream_to_rev_order(self, stream):
2994
for kind, substream in stream:
2995
if not kind == 'revisions':
2998
for content in substream:
2999
result.append(content.key[-1])
3002
def get_ordered_revs(self, format, order, branch_factory=None):
3003
"""Get a list of the revisions in a stream to format format.
3005
:param format: The format of the target.
3006
:param order: the order that target should have requested.
3007
:param branch_factory: A callable to create a trunk and stacked branch
3008
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3009
:result: The revision ids in the stream, in the order seen,
3010
the topological order of revisions in the source.
3012
unordered_format = bzrdir.format_registry.get(format)()
3013
target_repository_format = unordered_format.repository_format
3015
self.assertEqual(order, target_repository_format._fetch_order)
3016
if branch_factory is None:
3017
branch_factory = self.prepare_stacked_remote_branch
3018
_, stacked = branch_factory()
3019
source = stacked.repository._get_source(target_repository_format)
3020
tip = stacked.last_revision()
3021
revs = stacked.repository.get_ancestry(tip)
3022
search = graph.PendingAncestryResult([tip], stacked.repository)
3023
self.reset_smart_call_log()
3024
stream = source.get_stream(search)
3027
# We trust that if a revision is in the stream the rest of the new
3028
# content for it is too, as per our main fetch tests; here we are
3029
# checking that the revisions are actually included at all, and their
3031
return self.fetch_stream_to_rev_order(stream), revs
3033
def test_stacked_get_stream_unordered(self):
3034
# Repository._get_source.get_stream() from a stacked repository with
3035
# unordered yields the full data from both stacked and stacked upon
3037
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3038
self.assertEqual(set(expected_revs), set(rev_ord))
3039
# Getting unordered results should have made a streaming data request
3040
# from the server, then one from the backing branch.
3041
self.assertLength(2, self.hpss_calls)
3043
def test_stacked_on_stacked_get_stream_unordered(self):
3044
# Repository._get_source.get_stream() from a stacked repository which
3045
# is itself stacked yields the full data from all three sources.
3046
def make_stacked_stacked():
3047
_, stacked = self.prepare_stacked_remote_branch()
3048
tree = stacked.bzrdir.sprout('tree3', stacked=True
3049
).open_workingtree()
3050
local_tree = tree.branch.create_checkout('local-tree3')
3051
local_tree.commit('more local changes are better')
3052
branch = Branch.open(self.get_url('tree3'))
3054
self.addCleanup(branch.unlock)
3056
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3057
branch_factory=make_stacked_stacked)
3058
self.assertEqual(set(expected_revs), set(rev_ord))
3059
# Getting unordered results should have made a streaming data request
3060
# from the server, and one from each backing repo
3061
self.assertLength(3, self.hpss_calls)
3063
def test_stacked_get_stream_topological(self):
3064
# Repository._get_source.get_stream() from a stacked repository with
3065
# topological sorting yields the full data from both stacked and
3066
# stacked upon sources in topological order.
3067
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3068
self.assertEqual(expected_revs, rev_ord)
3069
# Getting topological sort requires VFS calls still - one of which is
3070
# pushing up from the bound branch.
3071
self.assertLength(13, self.hpss_calls)
3073
def test_stacked_get_stream_groupcompress(self):
3074
# Repository._get_source.get_stream() from a stacked repository with
3075
# groupcompress sorting yields the full data from both stacked and
3076
# stacked upon sources in groupcompress order.
3077
raise tests.TestSkipped('No groupcompress ordered format available')
3078
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3079
self.assertEqual(expected_revs, reversed(rev_ord))
3080
# Getting unordered results should have made a streaming data request
3081
# from the backing branch, and one from the stacked on branch.
3082
self.assertLength(2, self.hpss_calls)
3084
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3085
# When pulling some fixed amount of content that is more than the
3086
# source has (because some is coming from a fallback branch, no error
3087
# should be received. This was reported as bug 360791.
3088
# Need three branches: a trunk, a stacked branch, and a preexisting
3089
# branch pulling content from stacked and trunk.
3090
self.setup_smart_server_with_call_log()
3091
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3092
r1 = trunk.commit('start')
3093
stacked_branch = trunk.branch.create_clone_on_transport(
3094
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3095
local = self.make_branch('local', format='1.9-rich-root')
3096
local.repository.fetch(stacked_branch.repository,
3097
stacked_branch.last_revision())
3100
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3103
super(TestRemoteBranchEffort, self).setUp()
3104
# Create a smart server that publishes whatever the backing VFS server
3106
self.smart_server = server.SmartTCPServer_for_testing()
3107
self.start_server(self.smart_server, self.get_server())
3108
# Log all HPSS calls into self.hpss_calls.
3109
_SmartClient.hooks.install_named_hook(
3110
'call', self.capture_hpss_call, None)
3111
self.hpss_calls = []
3113
def capture_hpss_call(self, params):
3114
self.hpss_calls.append(params.method)
3116
def test_copy_content_into_avoids_revision_history(self):
3117
local = self.make_branch('local')
3118
remote_backing_tree = self.make_branch_and_tree('remote')
3119
remote_backing_tree.commit("Commit.")
3120
remote_branch_url = self.smart_server.get_url() + 'remote'
3121
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3122
local.repository.fetch(remote_branch.repository)
3123
self.hpss_calls = []
3124
remote_branch.copy_content_into(local)
3125
self.assertFalse('Branch.revision_history' in self.hpss_calls)
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