146
102
b = BzrDir.open_from_transport(self.transport).open_branch()
147
103
self.assertStartsWith(str(b), 'RemoteBranch(')
149
def test_remote_bzrdir_repr(self):
150
b = BzrDir.open_from_transport(self.transport)
151
self.assertStartsWith(str(b), 'RemoteBzrDir(')
153
def test_remote_branch_format_supports_stacking(self):
155
self.make_branch('unstackable', format='pack-0.92')
156
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
157
self.assertFalse(b._format.supports_stacking())
158
self.make_branch('stackable', format='1.9')
159
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
160
self.assertTrue(b._format.supports_stacking())
162
def test_remote_repo_format_supports_external_references(self):
164
bd = self.make_bzrdir('unstackable', format='pack-0.92')
165
r = bd.create_repository()
166
self.assertFalse(r._format.supports_external_lookups)
167
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
168
self.assertFalse(r._format.supports_external_lookups)
169
bd = self.make_bzrdir('stackable', format='1.9')
170
r = bd.create_repository()
171
self.assertTrue(r._format.supports_external_lookups)
172
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
173
self.assertTrue(r._format.supports_external_lookups)
175
def test_remote_branch_set_append_revisions_only(self):
176
# Make a format 1.9 branch, which supports append_revisions_only
177
branch = self.make_branch('branch', format='1.9')
178
branch.set_append_revisions_only(True)
179
config = branch.get_config_stack()
181
True, config.get('append_revisions_only'))
182
branch.set_append_revisions_only(False)
183
config = branch.get_config_stack()
185
False, config.get('append_revisions_only'))
187
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
188
branch = self.make_branch('branch', format='knit')
190
errors.UpgradeRequired, branch.set_append_revisions_only, True)
193
106
class FakeProtocol(object):
194
107
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
196
def __init__(self, body, fake_client):
198
self._body_buffer = None
199
self._fake_client = fake_client
109
def __init__(self, body):
110
self._body_buffer = StringIO(body)
201
112
def read_body_bytes(self, count=-1):
202
if self._body_buffer is None:
203
self._body_buffer = StringIO(self.body)
204
bytes = self._body_buffer.read(count)
205
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
206
self._fake_client.expecting_body = False
209
def cancel_read_body(self):
210
self._fake_client.expecting_body = False
212
def read_streamed_body(self):
113
return self._body_buffer.read(count)
216
116
class FakeClient(_SmartClient):
217
117
"""Lookalike for _SmartClient allowing testing."""
119
def __init__(self, responses):
120
# We don't call the super init because there is no medium.
121
"""create a FakeClient.
219
def __init__(self, fake_medium_base='fake base'):
220
"""Create a FakeClient."""
123
:param respones: A list of response-tuple, body-data pairs to be sent
126
self.responses = responses
223
self.expecting_body = False
224
# if non-None, this is the list of expected calls, with only the
225
# method name and arguments included. the body might be hard to
226
# compute so is not included. If a call is None, that call can
228
self._expected_calls = None
229
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
231
def add_expected_call(self, call_name, call_args, response_type,
232
response_args, response_body=None):
233
if self._expected_calls is None:
234
self._expected_calls = []
235
self._expected_calls.append((call_name, call_args))
236
self.responses.append((response_type, response_args, response_body))
238
def add_success_response(self, *args):
239
self.responses.append(('success', args, None))
241
def add_success_response_with_body(self, body, *args):
242
self.responses.append(('success', args, body))
243
if self._expected_calls is not None:
244
self._expected_calls.append(None)
246
def add_error_response(self, *args):
247
self.responses.append(('error', args))
249
def add_unknown_method_response(self, verb):
250
self.responses.append(('unknown', verb))
252
def finished_test(self):
253
if self._expected_calls:
254
raise AssertionError("%r finished but was still expecting %r"
255
% (self, self._expected_calls[0]))
257
def _get_next_response(self):
259
response_tuple = self.responses.pop(0)
260
except IndexError, e:
261
raise AssertionError("%r didn't expect any more calls"
263
if response_tuple[0] == 'unknown':
264
raise errors.UnknownSmartMethod(response_tuple[1])
265
elif response_tuple[0] == 'error':
266
raise errors.ErrorFromSmartServer(response_tuple[1])
267
return response_tuple
269
def _check_call(self, method, args):
270
if self._expected_calls is None:
271
# the test should be updated to say what it expects
274
next_call = self._expected_calls.pop(0)
276
raise AssertionError("%r didn't expect any more calls "
278
% (self, method, args,))
279
if next_call is None:
281
if method != next_call[0] or args != next_call[1]:
282
raise AssertionError("%r expected %r%r "
284
% (self, next_call[0], next_call[1], method, args,))
286
129
def call(self, method, *args):
287
self._check_call(method, args)
288
130
self._calls.append(('call', method, args))
289
return self._get_next_response()[1]
131
return self.responses.pop(0)[0]
291
133
def call_expecting_body(self, method, *args):
292
self._check_call(method, args)
293
134
self._calls.append(('call_expecting_body', method, args))
294
result = self._get_next_response()
295
self.expecting_body = True
296
return result[1], FakeProtocol(result[2], self)
298
def call_with_body_bytes(self, method, args, body):
299
self._check_call(method, args)
300
self._calls.append(('call_with_body_bytes', method, args, body))
301
result = self._get_next_response()
302
return result[1], FakeProtocol(result[2], self)
304
def call_with_body_bytes_expecting_body(self, method, args, body):
305
self._check_call(method, args)
306
self._calls.append(('call_with_body_bytes_expecting_body', method,
308
result = self._get_next_response()
309
self.expecting_body = True
310
return result[1], FakeProtocol(result[2], self)
312
def call_with_body_stream(self, args, stream):
313
# Explicitly consume the stream before checking for an error, because
314
# that's what happens a real medium.
315
stream = list(stream)
316
self._check_call(args[0], args[1:])
317
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
318
result = self._get_next_response()
319
# The second value returned from call_with_body_stream is supposed to
320
# be a response_handler object, but so far no tests depend on that.
321
response_handler = None
322
return result[1], response_handler
325
class FakeMedium(medium.SmartClientMedium):
327
def __init__(self, client_calls, base):
328
medium.SmartClientMedium.__init__(self, base)
329
self._client_calls = client_calls
331
def disconnect(self):
332
self._client_calls.append(('disconnect medium',))
335
class TestVfsHas(tests.TestCase):
337
def test_unicode_path(self):
338
client = FakeClient('/')
339
client.add_success_response('yes',)
340
transport = RemoteTransport('bzr://localhost/', _client=client)
341
filename = u'/hell\u00d8'.encode('utf8')
342
result = transport.has(filename)
344
[('call', 'has', (filename,))],
346
self.assertTrue(result)
349
class TestRemote(tests.TestCaseWithMemoryTransport):
351
def get_branch_format(self):
352
reference_bzrdir_format = bzrdir.format_registry.get('default')()
353
return reference_bzrdir_format.get_branch_format()
355
def get_repo_format(self):
356
reference_bzrdir_format = bzrdir.format_registry.get('default')()
357
return reference_bzrdir_format.repository_format
359
def assertFinished(self, fake_client):
360
"""Assert that all of a FakeClient's expected calls have occurred."""
361
fake_client.finished_test()
364
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
365
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
367
def assertRemotePath(self, expected, client_base, transport_base):
368
"""Assert that the result of
369
SmartClientMedium.remote_path_from_transport is the expected value for
370
a given client_base and transport_base.
372
client_medium = medium.SmartClientMedium(client_base)
373
t = transport.get_transport(transport_base)
374
result = client_medium.remote_path_from_transport(t)
375
self.assertEqual(expected, result)
377
def test_remote_path_from_transport(self):
378
"""SmartClientMedium.remote_path_from_transport calculates a URL for
379
the given transport relative to the root of the client base URL.
381
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
382
self.assertRemotePath(
383
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
385
def assertRemotePathHTTP(self, expected, transport_base, relpath):
386
"""Assert that the result of
387
HttpTransportBase.remote_path_from_transport is the expected value for
388
a given transport_base and relpath of that transport. (Note that
389
HttpTransportBase is a subclass of SmartClientMedium)
391
base_transport = transport.get_transport(transport_base)
392
client_medium = base_transport.get_smart_medium()
393
cloned_transport = base_transport.clone(relpath)
394
result = client_medium.remote_path_from_transport(cloned_transport)
395
self.assertEqual(expected, result)
397
def test_remote_path_from_transport_http(self):
398
"""Remote paths for HTTP transports are calculated differently to other
399
transports. They are just relative to the client base, not the root
400
directory of the host.
402
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
403
self.assertRemotePathHTTP(
404
'../xyz/', scheme + '//host/path', '../xyz/')
405
self.assertRemotePathHTTP(
406
'xyz/', scheme + '//host/path', 'xyz/')
409
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
410
"""Tests for the behaviour of client_medium.remote_is_at_least."""
412
def test_initially_unlimited(self):
413
"""A fresh medium assumes that the remote side supports all
416
client_medium = medium.SmartClientMedium('dummy base')
417
self.assertFalse(client_medium._is_remote_before((99, 99)))
419
def test__remember_remote_is_before(self):
420
"""Calling _remember_remote_is_before ratchets down the known remote
423
client_medium = medium.SmartClientMedium('dummy base')
424
# Mark the remote side as being less than 1.6. The remote side may
426
client_medium._remember_remote_is_before((1, 6))
427
self.assertTrue(client_medium._is_remote_before((1, 6)))
428
self.assertFalse(client_medium._is_remote_before((1, 5)))
429
# Calling _remember_remote_is_before again with a lower value works.
430
client_medium._remember_remote_is_before((1, 5))
431
self.assertTrue(client_medium._is_remote_before((1, 5)))
432
# If you call _remember_remote_is_before with a higher value it logs a
433
# warning, and continues to remember the lower value.
434
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
435
client_medium._remember_remote_is_before((1, 9))
436
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
437
self.assertTrue(client_medium._is_remote_before((1, 5)))
440
class TestBzrDirCloningMetaDir(TestRemote):
442
def test_backwards_compat(self):
443
self.setup_smart_server_with_call_log()
444
a_dir = self.make_bzrdir('.')
445
self.reset_smart_call_log()
446
verb = 'BzrDir.cloning_metadir'
447
self.disable_verb(verb)
448
format = a_dir.cloning_metadir()
449
call_count = len([call for call in self.hpss_calls if
450
call.call.method == verb])
451
self.assertEqual(1, call_count)
453
def test_branch_reference(self):
454
transport = self.get_transport('quack')
455
referenced = self.make_branch('referenced')
456
expected = referenced.bzrdir.cloning_metadir()
457
client = FakeClient(transport.base)
458
client.add_expected_call(
459
'BzrDir.cloning_metadir', ('quack/', 'False'),
460
'error', ('BranchReference',)),
461
client.add_expected_call(
462
'BzrDir.open_branchV3', ('quack/',),
463
'success', ('ref', self.get_url('referenced'))),
464
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
466
result = a_bzrdir.cloning_metadir()
467
# We should have got a control dir matching the referenced branch.
468
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
469
self.assertEqual(expected._repository_format, result._repository_format)
470
self.assertEqual(expected._branch_format, result._branch_format)
471
self.assertFinished(client)
473
def test_current_server(self):
474
transport = self.get_transport('.')
475
transport = transport.clone('quack')
476
self.make_bzrdir('quack')
477
client = FakeClient(transport.base)
478
reference_bzrdir_format = bzrdir.format_registry.get('default')()
479
control_name = reference_bzrdir_format.network_name()
480
client.add_expected_call(
481
'BzrDir.cloning_metadir', ('quack/', 'False'),
482
'success', (control_name, '', ('branch', ''))),
483
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
485
result = a_bzrdir.cloning_metadir()
486
# We should have got a reference control dir with default branch and
487
# repository formats.
488
# This pokes a little, just to be sure.
489
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
490
self.assertEqual(None, result._repository_format)
491
self.assertEqual(None, result._branch_format)
492
self.assertFinished(client)
494
def test_unknown(self):
495
transport = self.get_transport('quack')
496
referenced = self.make_branch('referenced')
497
expected = referenced.bzrdir.cloning_metadir()
498
client = FakeClient(transport.base)
499
client.add_expected_call(
500
'BzrDir.cloning_metadir', ('quack/', 'False'),
501
'success', ('unknown', 'unknown', ('branch', ''))),
502
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
504
self.assertRaises(errors.UnknownFormatError, a_bzrdir.cloning_metadir)
507
class TestBzrDirCheckoutMetaDir(TestRemote):
509
def test__get_checkout_format(self):
510
transport = MemoryTransport()
511
client = FakeClient(transport.base)
512
reference_bzrdir_format = bzrdir.format_registry.get('default')()
513
control_name = reference_bzrdir_format.network_name()
514
client.add_expected_call(
515
'BzrDir.checkout_metadir', ('quack/', ),
516
'success', (control_name, '', ''))
517
transport.mkdir('quack')
518
transport = transport.clone('quack')
519
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
521
result = a_bzrdir.checkout_metadir()
522
# We should have got a reference control dir with default branch and
523
# repository formats.
524
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
525
self.assertEqual(None, result._repository_format)
526
self.assertEqual(None, result._branch_format)
527
self.assertFinished(client)
529
def test_unknown_format(self):
530
transport = MemoryTransport()
531
client = FakeClient(transport.base)
532
client.add_expected_call(
533
'BzrDir.checkout_metadir', ('quack/',),
534
'success', ('dontknow', '', ''))
535
transport.mkdir('quack')
536
transport = transport.clone('quack')
537
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
539
self.assertRaises(errors.UnknownFormatError,
540
a_bzrdir.checkout_metadir)
541
self.assertFinished(client)
544
class TestBzrDirDestroyBranch(TestRemote):
546
def test_destroy_default(self):
547
transport = self.get_transport('quack')
548
referenced = self.make_branch('referenced')
549
client = FakeClient(transport.base)
550
client.add_expected_call(
551
'BzrDir.destroy_branch', ('quack/', ),
553
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
555
a_bzrdir.destroy_branch()
556
self.assertFinished(client)
558
def test_destroy_named(self):
559
transport = self.get_transport('quack')
560
referenced = self.make_branch('referenced')
561
client = FakeClient(transport.base)
562
client.add_expected_call(
563
'BzrDir.destroy_branch', ('quack/', "foo"),
565
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
567
a_bzrdir.destroy_branch("foo")
568
self.assertFinished(client)
571
class TestBzrDirHasWorkingTree(TestRemote):
573
def test_has_workingtree(self):
574
transport = self.get_transport('quack')
575
client = FakeClient(transport.base)
576
client.add_expected_call(
577
'BzrDir.has_workingtree', ('quack/',),
578
'success', ('yes',)),
579
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
581
self.assertTrue(a_bzrdir.has_workingtree())
582
self.assertFinished(client)
584
def test_no_workingtree(self):
585
transport = self.get_transport('quack')
586
client = FakeClient(transport.base)
587
client.add_expected_call(
588
'BzrDir.has_workingtree', ('quack/',),
590
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
592
self.assertFalse(a_bzrdir.has_workingtree())
593
self.assertFinished(client)
596
class TestBzrDirDestroyRepository(TestRemote):
598
def test_destroy_repository(self):
599
transport = self.get_transport('quack')
600
client = FakeClient(transport.base)
601
client.add_expected_call(
602
'BzrDir.destroy_repository', ('quack/',),
604
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
606
a_bzrdir.destroy_repository()
607
self.assertFinished(client)
610
class TestBzrDirOpen(TestRemote):
612
def make_fake_client_and_transport(self, path='quack'):
613
transport = MemoryTransport()
614
transport.mkdir(path)
615
transport = transport.clone(path)
616
client = FakeClient(transport.base)
617
return client, transport
619
def test_absent(self):
620
client, transport = self.make_fake_client_and_transport()
621
client.add_expected_call(
622
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
623
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
624
RemoteBzrDirFormat(), _client=client, _force_probe=True)
625
self.assertFinished(client)
627
def test_present_without_workingtree(self):
628
client, transport = self.make_fake_client_and_transport()
629
client.add_expected_call(
630
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
631
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
632
_client=client, _force_probe=True)
633
self.assertIsInstance(bd, RemoteBzrDir)
634
self.assertFalse(bd.has_workingtree())
635
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
636
self.assertFinished(client)
638
def test_present_with_workingtree(self):
639
client, transport = self.make_fake_client_and_transport()
640
client.add_expected_call(
641
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
642
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
643
_client=client, _force_probe=True)
644
self.assertIsInstance(bd, RemoteBzrDir)
645
self.assertTrue(bd.has_workingtree())
646
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
647
self.assertFinished(client)
649
def test_backwards_compat(self):
650
client, transport = self.make_fake_client_and_transport()
651
client.add_expected_call(
652
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
653
client.add_expected_call(
654
'BzrDir.open', ('quack/',), 'success', ('yes',))
655
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
656
_client=client, _force_probe=True)
657
self.assertIsInstance(bd, RemoteBzrDir)
658
self.assertFinished(client)
660
def test_backwards_compat_hpss_v2(self):
661
client, transport = self.make_fake_client_and_transport()
662
# Monkey-patch fake client to simulate real-world behaviour with v2
663
# server: upon first RPC call detect the protocol version, and because
664
# the version is 2 also do _remember_remote_is_before((1, 6)) before
665
# continuing with the RPC.
666
orig_check_call = client._check_call
667
def check_call(method, args):
668
client._medium._protocol_version = 2
669
client._medium._remember_remote_is_before((1, 6))
670
client._check_call = orig_check_call
671
client._check_call(method, args)
672
client._check_call = check_call
673
client.add_expected_call(
674
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
675
client.add_expected_call(
676
'BzrDir.open', ('quack/',), 'success', ('yes',))
677
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
678
_client=client, _force_probe=True)
679
self.assertIsInstance(bd, RemoteBzrDir)
680
self.assertFinished(client)
683
class TestBzrDirOpenBranch(TestRemote):
685
def test_backwards_compat(self):
686
self.setup_smart_server_with_call_log()
687
self.make_branch('.')
688
a_dir = BzrDir.open(self.get_url('.'))
689
self.reset_smart_call_log()
690
verb = 'BzrDir.open_branchV3'
691
self.disable_verb(verb)
692
format = a_dir.open_branch()
693
call_count = len([call for call in self.hpss_calls if
694
call.call.method == verb])
695
self.assertEqual(1, call_count)
135
result = self.responses.pop(0)
136
return result[0], FakeProtocol(result[1])
139
class TestBzrDirOpenBranch(tests.TestCase):
697
141
def test_branch_present(self):
698
reference_format = self.get_repo_format()
699
network_name = reference_format.network_name()
700
branch_network_name = self.get_branch_format().network_name()
142
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )])
701
143
transport = MemoryTransport()
702
144
transport.mkdir('quack')
703
145
transport = transport.clone('quack')
704
client = FakeClient(transport.base)
705
client.add_expected_call(
706
'BzrDir.open_branchV3', ('quack/',),
707
'success', ('branch', branch_network_name))
708
client.add_expected_call(
709
'BzrDir.find_repositoryV3', ('quack/',),
710
'success', ('ok', '', 'no', 'no', 'no', network_name))
711
client.add_expected_call(
712
'Branch.get_stacked_on_url', ('quack/',),
713
'error', ('NotStacked',))
714
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
146
bzrdir = RemoteBzrDir(transport, _client=client)
716
147
result = bzrdir.open_branch()
149
[('call', 'BzrDir.open_branch', ('///quack/',)),
150
('call', 'BzrDir.find_repository', ('///quack/',))],
717
152
self.assertIsInstance(result, RemoteBranch)
718
153
self.assertEqual(bzrdir, result.bzrdir)
719
self.assertFinished(client)
721
155
def test_branch_missing(self):
156
client = FakeClient([(('nobranch',), )])
722
157
transport = MemoryTransport()
723
158
transport.mkdir('quack')
724
159
transport = transport.clone('quack')
725
client = FakeClient(transport.base)
726
client.add_error_response('nobranch')
727
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
160
bzrdir = RemoteBzrDir(transport, _client=client)
729
161
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
730
162
self.assertEqual(
731
[('call', 'BzrDir.open_branchV3', ('quack/',))],
163
[('call', 'BzrDir.open_branch', ('///quack/',))],
734
def test__get_tree_branch(self):
735
# _get_tree_branch is a form of open_branch, but it should only ask for
736
# branch opening, not any other network requests.
738
def open_branch(name=None, possible_transports=None):
739
calls.append("Called")
741
transport = MemoryTransport()
742
# no requests on the network - catches other api calls being made.
743
client = FakeClient(transport.base)
744
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
746
# patch the open_branch call to record that it was called.
747
bzrdir.open_branch = open_branch
748
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
749
self.assertEqual(["Called"], calls)
750
self.assertEqual([], client._calls)
752
def test_url_quoting_of_path(self):
753
# Relpaths on the wire should not be URL-escaped. So "~" should be
754
# transmitted as "~", not "%7E".
755
transport = RemoteTCPTransport('bzr://localhost/~hello/')
756
client = FakeClient(transport.base)
757
reference_format = self.get_repo_format()
758
network_name = reference_format.network_name()
759
branch_network_name = self.get_branch_format().network_name()
760
client.add_expected_call(
761
'BzrDir.open_branchV3', ('~hello/',),
762
'success', ('branch', branch_network_name))
763
client.add_expected_call(
764
'BzrDir.find_repositoryV3', ('~hello/',),
765
'success', ('ok', '', 'no', 'no', 'no', network_name))
766
client.add_expected_call(
767
'Branch.get_stacked_on_url', ('~hello/',),
768
'error', ('NotStacked',))
769
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
771
result = bzrdir.open_branch()
772
self.assertFinished(client)
774
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
775
reference_format = self.get_repo_format()
776
network_name = reference_format.network_name()
777
transport = MemoryTransport()
778
transport.mkdir('quack')
779
transport = transport.clone('quack')
166
def check_open_repository(self, rich_root, subtrees):
781
168
rich_response = 'yes'
805
191
self.check_open_repository(False, True)
806
192
self.check_open_repository(True, False)
807
193
self.check_open_repository(False, False)
808
self.check_open_repository(False, False, 'yes')
810
195
def test_old_server(self):
811
196
"""RemoteBzrDirFormat should fail to probe if the server version is too
814
199
self.assertRaises(errors.NotBranchError,
815
RemoteBzrProber.probe_transport, OldServerTransport())
818
class TestBzrDirCreateBranch(TestRemote):
820
def test_backwards_compat(self):
821
self.setup_smart_server_with_call_log()
822
repo = self.make_repository('.')
823
self.reset_smart_call_log()
824
self.disable_verb('BzrDir.create_branch')
825
branch = repo.bzrdir.create_branch()
826
create_branch_call_count = len([call for call in self.hpss_calls if
827
call.call.method == 'BzrDir.create_branch'])
828
self.assertEqual(1, create_branch_call_count)
830
def test_current_server(self):
831
transport = self.get_transport('.')
832
transport = transport.clone('quack')
833
self.make_repository('quack')
834
client = FakeClient(transport.base)
835
reference_bzrdir_format = bzrdir.format_registry.get('default')()
836
reference_format = reference_bzrdir_format.get_branch_format()
837
network_name = reference_format.network_name()
838
reference_repo_fmt = reference_bzrdir_format.repository_format
839
reference_repo_name = reference_repo_fmt.network_name()
840
client.add_expected_call(
841
'BzrDir.create_branch', ('quack/', network_name),
842
'success', ('ok', network_name, '', 'no', 'no', 'yes',
843
reference_repo_name))
844
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
846
branch = a_bzrdir.create_branch()
847
# We should have got a remote branch
848
self.assertIsInstance(branch, remote.RemoteBranch)
849
# its format should have the settings from the response
850
format = branch._format
851
self.assertEqual(network_name, format.network_name())
853
def test_already_open_repo_and_reused_medium(self):
854
"""Bug 726584: create_branch(..., repository=repo) should work
855
regardless of what the smart medium's base URL is.
857
self.transport_server = test_server.SmartTCPServer_for_testing
858
transport = self.get_transport('.')
859
repo = self.make_repository('quack')
860
# Client's medium rooted a transport root (not at the bzrdir)
861
client = FakeClient(transport.base)
862
transport = transport.clone('quack')
863
reference_bzrdir_format = bzrdir.format_registry.get('default')()
864
reference_format = reference_bzrdir_format.get_branch_format()
865
network_name = reference_format.network_name()
866
reference_repo_fmt = reference_bzrdir_format.repository_format
867
reference_repo_name = reference_repo_fmt.network_name()
868
client.add_expected_call(
869
'BzrDir.create_branch', ('extra/quack/', network_name),
870
'success', ('ok', network_name, '', 'no', 'no', 'yes',
871
reference_repo_name))
872
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
874
branch = a_bzrdir.create_branch(repository=repo)
875
# We should have got a remote branch
876
self.assertIsInstance(branch, remote.RemoteBranch)
877
# its format should have the settings from the response
878
format = branch._format
879
self.assertEqual(network_name, format.network_name())
882
class TestBzrDirCreateRepository(TestRemote):
884
def test_backwards_compat(self):
885
self.setup_smart_server_with_call_log()
886
bzrdir = self.make_bzrdir('.')
887
self.reset_smart_call_log()
888
self.disable_verb('BzrDir.create_repository')
889
repo = bzrdir.create_repository()
890
create_repo_call_count = len([call for call in self.hpss_calls if
891
call.call.method == 'BzrDir.create_repository'])
892
self.assertEqual(1, create_repo_call_count)
894
def test_current_server(self):
895
transport = self.get_transport('.')
896
transport = transport.clone('quack')
897
self.make_bzrdir('quack')
898
client = FakeClient(transport.base)
899
reference_bzrdir_format = bzrdir.format_registry.get('default')()
900
reference_format = reference_bzrdir_format.repository_format
901
network_name = reference_format.network_name()
902
client.add_expected_call(
903
'BzrDir.create_repository', ('quack/',
904
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
906
'success', ('ok', 'yes', 'yes', 'yes', network_name))
907
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
909
repo = a_bzrdir.create_repository()
910
# We should have got a remote repository
911
self.assertIsInstance(repo, remote.RemoteRepository)
912
# its format should have the settings from the response
913
format = repo._format
914
self.assertTrue(format.rich_root_data)
915
self.assertTrue(format.supports_tree_reference)
916
self.assertTrue(format.supports_external_lookups)
917
self.assertEqual(network_name, format.network_name())
920
class TestBzrDirOpenRepository(TestRemote):
922
def test_backwards_compat_1_2_3(self):
923
# fallback all the way to the first version.
924
reference_format = self.get_repo_format()
925
network_name = reference_format.network_name()
926
server_url = 'bzr://example.com/'
927
self.permit_url(server_url)
928
client = FakeClient(server_url)
929
client.add_unknown_method_response('BzrDir.find_repositoryV3')
930
client.add_unknown_method_response('BzrDir.find_repositoryV2')
931
client.add_success_response('ok', '', 'no', 'no')
932
# A real repository instance will be created to determine the network
934
client.add_success_response_with_body(
935
"Bazaar-NG meta directory, format 1\n", 'ok')
936
client.add_success_response_with_body(
937
reference_format.get_format_string(), 'ok')
938
# PackRepository wants to do a stat
939
client.add_success_response('stat', '0', '65535')
940
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
942
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
944
repo = bzrdir.open_repository()
946
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
947
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
948
('call', 'BzrDir.find_repository', ('quack/',)),
949
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
950
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
951
('call', 'stat', ('/quack/.bzr/repository',)),
954
self.assertEqual(network_name, repo._format.network_name())
956
def test_backwards_compat_2(self):
957
# fallback to find_repositoryV2
958
reference_format = self.get_repo_format()
959
network_name = reference_format.network_name()
960
server_url = 'bzr://example.com/'
961
self.permit_url(server_url)
962
client = FakeClient(server_url)
963
client.add_unknown_method_response('BzrDir.find_repositoryV3')
964
client.add_success_response('ok', '', 'no', 'no', 'no')
965
# A real repository instance will be created to determine the network
967
client.add_success_response_with_body(
968
"Bazaar-NG meta directory, format 1\n", 'ok')
969
client.add_success_response_with_body(
970
reference_format.get_format_string(), 'ok')
971
# PackRepository wants to do a stat
972
client.add_success_response('stat', '0', '65535')
973
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
975
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
977
repo = bzrdir.open_repository()
979
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
980
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
981
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
982
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
983
('call', 'stat', ('/quack/.bzr/repository',)),
986
self.assertEqual(network_name, repo._format.network_name())
988
def test_current_server(self):
989
reference_format = self.get_repo_format()
990
network_name = reference_format.network_name()
991
transport = MemoryTransport()
992
transport.mkdir('quack')
993
transport = transport.clone('quack')
994
client = FakeClient(transport.base)
995
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
996
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
998
repo = bzrdir.open_repository()
1000
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
1002
self.assertEqual(network_name, repo._format.network_name())
1005
class TestBzrDirFormatInitializeEx(TestRemote):
1007
def test_success(self):
1008
"""Simple test for typical successful call."""
1009
fmt = RemoteBzrDirFormat()
1010
default_format_name = BzrDirFormat.get_default_format().network_name()
1011
transport = self.get_transport()
1012
client = FakeClient(transport.base)
1013
client.add_expected_call(
1014
'BzrDirFormat.initialize_ex_1.16',
1015
(default_format_name, 'path', 'False', 'False', 'False', '',
1016
'', '', '', 'False'),
1018
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
1019
'bzrdir fmt', 'False', '', '', 'repo lock token'))
1020
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1021
# it's currently hard to test that without supplying a real remote
1022
# transport connected to a real server.
1023
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
1024
transport, False, False, False, None, None, None, None, False)
1025
self.assertFinished(client)
1027
def test_error(self):
1028
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1029
corresponding error from the client.
1031
fmt = RemoteBzrDirFormat()
1032
default_format_name = BzrDirFormat.get_default_format().network_name()
1033
transport = self.get_transport()
1034
client = FakeClient(transport.base)
1035
client.add_expected_call(
1036
'BzrDirFormat.initialize_ex_1.16',
1037
(default_format_name, 'path', 'False', 'False', 'False', '',
1038
'', '', '', 'False'),
1040
('PermissionDenied', 'path', 'extra info'))
1041
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1042
# it's currently hard to test that without supplying a real remote
1043
# transport connected to a real server.
1044
err = self.assertRaises(errors.PermissionDenied,
1045
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1046
False, False, False, None, None, None, None, False)
1047
self.assertEqual('path', err.path)
1048
self.assertEqual(': extra info', err.extra)
1049
self.assertFinished(client)
1051
def test_error_from_real_server(self):
1052
"""Integration test for error translation."""
1053
transport = self.make_smart_server('foo')
1054
transport = transport.clone('no-such-path')
1055
fmt = RemoteBzrDirFormat()
1056
err = self.assertRaises(errors.NoSuchFile,
1057
fmt.initialize_on_transport_ex, transport, create_prefix=False)
200
RemoteBzrDirFormat.probe_transport, OldServerTransport())
1060
203
class OldSmartClient(object):
1085
225
return OldSmartClient()
1088
class RemoteBzrDirTestCase(TestRemote):
1090
def make_remote_bzrdir(self, transport, client):
1091
"""Make a RemotebzrDir using 'client' as the _client."""
1092
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1096
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1098
def lock_remote_branch(self, branch):
1099
"""Trick a RemoteBranch into thinking it is locked."""
1100
branch._lock_mode = 'w'
1101
branch._lock_count = 2
1102
branch._lock_token = 'branch token'
1103
branch._repo_lock_token = 'repo token'
1104
branch.repository._lock_mode = 'w'
1105
branch.repository._lock_count = 2
1106
branch.repository._lock_token = 'repo token'
1108
def make_remote_branch(self, transport, client):
1109
"""Make a RemoteBranch using 'client' as its _SmartClient.
1111
A RemoteBzrDir and RemoteRepository will also be created to fill out
1112
the RemoteBranch, albeit with stub values for some of their attributes.
1114
# we do not want bzrdir to make any remote calls, so use False as its
1115
# _client. If it tries to make a remote call, this will fail
1117
bzrdir = self.make_remote_bzrdir(transport, False)
1118
repo = RemoteRepository(bzrdir, None, _client=client)
1119
branch_format = self.get_branch_format()
1120
format = RemoteBranchFormat(network_name=branch_format.network_name())
1121
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1124
class TestBranchBreakLock(RemoteBranchTestCase):
1126
def test_break_lock(self):
1127
transport_path = 'quack'
1128
transport = MemoryTransport()
1129
client = FakeClient(transport.base)
1130
client.add_expected_call(
1131
'Branch.get_stacked_on_url', ('quack/',),
1132
'error', ('NotStacked',))
1133
client.add_expected_call(
1134
'Branch.break_lock', ('quack/',),
1136
transport.mkdir('quack')
1137
transport = transport.clone('quack')
1138
branch = self.make_remote_branch(transport, client)
1140
self.assertFinished(client)
1143
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1145
def test_get_physical_lock_status_yes(self):
1146
transport = MemoryTransport()
1147
client = FakeClient(transport.base)
1148
client.add_expected_call(
1149
'Branch.get_stacked_on_url', ('quack/',),
1150
'error', ('NotStacked',))
1151
client.add_expected_call(
1152
'Branch.get_physical_lock_status', ('quack/',),
1153
'success', ('yes',))
1154
transport.mkdir('quack')
1155
transport = transport.clone('quack')
1156
branch = self.make_remote_branch(transport, client)
1157
result = branch.get_physical_lock_status()
1158
self.assertFinished(client)
1159
self.assertEqual(True, result)
1161
def test_get_physical_lock_status_no(self):
1162
transport = MemoryTransport()
1163
client = FakeClient(transport.base)
1164
client.add_expected_call(
1165
'Branch.get_stacked_on_url', ('quack/',),
1166
'error', ('NotStacked',))
1167
client.add_expected_call(
1168
'Branch.get_physical_lock_status', ('quack/',),
1170
transport.mkdir('quack')
1171
transport = transport.clone('quack')
1172
branch = self.make_remote_branch(transport, client)
1173
result = branch.get_physical_lock_status()
1174
self.assertFinished(client)
1175
self.assertEqual(False, result)
1178
class TestBranchGetParent(RemoteBranchTestCase):
1180
def test_no_parent(self):
1181
# in an empty branch we decode the response properly
1182
transport = MemoryTransport()
1183
client = FakeClient(transport.base)
1184
client.add_expected_call(
1185
'Branch.get_stacked_on_url', ('quack/',),
1186
'error', ('NotStacked',))
1187
client.add_expected_call(
1188
'Branch.get_parent', ('quack/',),
1190
transport.mkdir('quack')
1191
transport = transport.clone('quack')
1192
branch = self.make_remote_branch(transport, client)
1193
result = branch.get_parent()
1194
self.assertFinished(client)
1195
self.assertEqual(None, result)
1197
def test_parent_relative(self):
1198
transport = MemoryTransport()
1199
client = FakeClient(transport.base)
1200
client.add_expected_call(
1201
'Branch.get_stacked_on_url', ('kwaak/',),
1202
'error', ('NotStacked',))
1203
client.add_expected_call(
1204
'Branch.get_parent', ('kwaak/',),
1205
'success', ('../foo/',))
1206
transport.mkdir('kwaak')
1207
transport = transport.clone('kwaak')
1208
branch = self.make_remote_branch(transport, client)
1209
result = branch.get_parent()
1210
self.assertEqual(transport.clone('../foo').base, result)
1212
def test_parent_absolute(self):
1213
transport = MemoryTransport()
1214
client = FakeClient(transport.base)
1215
client.add_expected_call(
1216
'Branch.get_stacked_on_url', ('kwaak/',),
1217
'error', ('NotStacked',))
1218
client.add_expected_call(
1219
'Branch.get_parent', ('kwaak/',),
1220
'success', ('http://foo/',))
1221
transport.mkdir('kwaak')
1222
transport = transport.clone('kwaak')
1223
branch = self.make_remote_branch(transport, client)
1224
result = branch.get_parent()
1225
self.assertEqual('http://foo/', result)
1226
self.assertFinished(client)
1229
class TestBranchSetParentLocation(RemoteBranchTestCase):
1231
def test_no_parent(self):
1232
# We call the verb when setting parent to None
1233
transport = MemoryTransport()
1234
client = FakeClient(transport.base)
1235
client.add_expected_call(
1236
'Branch.get_stacked_on_url', ('quack/',),
1237
'error', ('NotStacked',))
1238
client.add_expected_call(
1239
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1241
transport.mkdir('quack')
1242
transport = transport.clone('quack')
1243
branch = self.make_remote_branch(transport, client)
1244
branch._lock_token = 'b'
1245
branch._repo_lock_token = 'r'
1246
branch._set_parent_location(None)
1247
self.assertFinished(client)
1249
def test_parent(self):
1250
transport = MemoryTransport()
1251
client = FakeClient(transport.base)
1252
client.add_expected_call(
1253
'Branch.get_stacked_on_url', ('kwaak/',),
1254
'error', ('NotStacked',))
1255
client.add_expected_call(
1256
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1258
transport.mkdir('kwaak')
1259
transport = transport.clone('kwaak')
1260
branch = self.make_remote_branch(transport, client)
1261
branch._lock_token = 'b'
1262
branch._repo_lock_token = 'r'
1263
branch._set_parent_location('foo')
1264
self.assertFinished(client)
1266
def test_backwards_compat(self):
1267
self.setup_smart_server_with_call_log()
1268
branch = self.make_branch('.')
1269
self.reset_smart_call_log()
1270
verb = 'Branch.set_parent_location'
1271
self.disable_verb(verb)
1272
branch.set_parent('http://foo/')
1273
self.assertLength(12, self.hpss_calls)
1276
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1278
def test_backwards_compat(self):
1279
self.setup_smart_server_with_call_log()
1280
branch = self.make_branch('.')
1281
self.reset_smart_call_log()
1282
verb = 'Branch.get_tags_bytes'
1283
self.disable_verb(verb)
1284
branch.tags.get_tag_dict()
1285
call_count = len([call for call in self.hpss_calls if
1286
call.call.method == verb])
1287
self.assertEqual(1, call_count)
1289
def test_trivial(self):
1290
transport = MemoryTransport()
1291
client = FakeClient(transport.base)
1292
client.add_expected_call(
1293
'Branch.get_stacked_on_url', ('quack/',),
1294
'error', ('NotStacked',))
1295
client.add_expected_call(
1296
'Branch.get_tags_bytes', ('quack/',),
1298
transport.mkdir('quack')
1299
transport = transport.clone('quack')
1300
branch = self.make_remote_branch(transport, client)
1301
result = branch.tags.get_tag_dict()
1302
self.assertFinished(client)
1303
self.assertEqual({}, result)
1306
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1308
def test_trivial(self):
1309
transport = MemoryTransport()
1310
client = FakeClient(transport.base)
1311
client.add_expected_call(
1312
'Branch.get_stacked_on_url', ('quack/',),
1313
'error', ('NotStacked',))
1314
client.add_expected_call(
1315
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1317
transport.mkdir('quack')
1318
transport = transport.clone('quack')
1319
branch = self.make_remote_branch(transport, client)
1320
self.lock_remote_branch(branch)
1321
branch._set_tags_bytes('tags bytes')
1322
self.assertFinished(client)
1323
self.assertEqual('tags bytes', client._calls[-1][-1])
1325
def test_backwards_compatible(self):
1326
transport = MemoryTransport()
1327
client = FakeClient(transport.base)
1328
client.add_expected_call(
1329
'Branch.get_stacked_on_url', ('quack/',),
1330
'error', ('NotStacked',))
1331
client.add_expected_call(
1332
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1333
'unknown', ('Branch.set_tags_bytes',))
1334
transport.mkdir('quack')
1335
transport = transport.clone('quack')
1336
branch = self.make_remote_branch(transport, client)
1337
self.lock_remote_branch(branch)
1338
class StubRealBranch(object):
1341
def _set_tags_bytes(self, bytes):
1342
self.calls.append(('set_tags_bytes', bytes))
1343
real_branch = StubRealBranch()
1344
branch._real_branch = real_branch
1345
branch._set_tags_bytes('tags bytes')
1346
# Call a second time, to exercise the 'remote version already inferred'
1348
branch._set_tags_bytes('tags bytes')
1349
self.assertFinished(client)
1351
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1354
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1356
def test_uses_last_revision_info_and_tags_by_default(self):
1357
transport = MemoryTransport()
1358
client = FakeClient(transport.base)
1359
client.add_expected_call(
1360
'Branch.get_stacked_on_url', ('quack/',),
1361
'error', ('NotStacked',))
1362
client.add_expected_call(
1363
'Branch.last_revision_info', ('quack/',),
1364
'success', ('ok', '1', 'rev-tip'))
1365
client.add_expected_call(
1366
'Branch.get_config_file', ('quack/',),
1367
'success', ('ok',), '')
1368
transport.mkdir('quack')
1369
transport = transport.clone('quack')
1370
branch = self.make_remote_branch(transport, client)
1371
result = branch.heads_to_fetch()
1372
self.assertFinished(client)
1373
self.assertEqual((set(['rev-tip']), set()), result)
1375
def test_uses_last_revision_info_and_tags_when_set(self):
1376
transport = MemoryTransport()
1377
client = FakeClient(transport.base)
1378
client.add_expected_call(
1379
'Branch.get_stacked_on_url', ('quack/',),
1380
'error', ('NotStacked',))
1381
client.add_expected_call(
1382
'Branch.last_revision_info', ('quack/',),
1383
'success', ('ok', '1', 'rev-tip'))
1384
client.add_expected_call(
1385
'Branch.get_config_file', ('quack/',),
1386
'success', ('ok',), 'branch.fetch_tags = True')
1387
# XXX: this will break if the default format's serialization of tags
1388
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1389
client.add_expected_call(
1390
'Branch.get_tags_bytes', ('quack/',),
1391
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1392
transport.mkdir('quack')
1393
transport = transport.clone('quack')
1394
branch = self.make_remote_branch(transport, client)
1395
result = branch.heads_to_fetch()
1396
self.assertFinished(client)
1398
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1400
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1401
transport = MemoryTransport()
1402
client = FakeClient(transport.base)
1403
client.add_expected_call(
1404
'Branch.get_stacked_on_url', ('quack/',),
1405
'error', ('NotStacked',))
1406
client.add_expected_call(
1407
'Branch.heads_to_fetch', ('quack/',),
1408
'success', (['tip'], ['tagged-1', 'tagged-2']))
1409
transport.mkdir('quack')
1410
transport = transport.clone('quack')
1411
branch = self.make_remote_branch(transport, client)
1412
branch._format._use_default_local_heads_to_fetch = lambda: False
1413
result = branch.heads_to_fetch()
1414
self.assertFinished(client)
1415
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1417
def make_branch_with_tags(self):
1418
self.setup_smart_server_with_call_log()
1419
# Make a branch with a single revision.
1420
builder = self.make_branch_builder('foo')
1421
builder.start_series()
1422
builder.build_snapshot('tip', None, [
1423
('add', ('', 'root-id', 'directory', ''))])
1424
builder.finish_series()
1425
branch = builder.get_branch()
1426
# Add two tags to that branch
1427
branch.tags.set_tag('tag-1', 'rev-1')
1428
branch.tags.set_tag('tag-2', 'rev-2')
1431
def test_backwards_compatible(self):
1432
branch = self.make_branch_with_tags()
1433
c = branch.get_config()
1434
c.set_user_option('branch.fetch_tags', 'True')
1435
self.addCleanup(branch.lock_read().unlock)
1436
# Disable the heads_to_fetch verb
1437
verb = 'Branch.heads_to_fetch'
1438
self.disable_verb(verb)
1439
self.reset_smart_call_log()
1440
result = branch.heads_to_fetch()
1441
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1443
['Branch.last_revision_info', 'Branch.get_config_file',
1444
'Branch.get_tags_bytes'],
1445
[call.call.method for call in self.hpss_calls])
1447
def test_backwards_compatible_no_tags(self):
1448
branch = self.make_branch_with_tags()
1449
c = branch.get_config()
1450
c.set_user_option('branch.fetch_tags', 'False')
1451
self.addCleanup(branch.lock_read().unlock)
1452
# Disable the heads_to_fetch verb
1453
verb = 'Branch.heads_to_fetch'
1454
self.disable_verb(verb)
1455
self.reset_smart_call_log()
1456
result = branch.heads_to_fetch()
1457
self.assertEqual((set(['tip']), set()), result)
1459
['Branch.last_revision_info', 'Branch.get_config_file'],
1460
[call.call.method for call in self.hpss_calls])
1463
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
228
class TestBranchLastRevisionInfo(tests.TestCase):
1465
230
def test_empty_branch(self):
1466
231
# in an empty branch we decode the response properly
232
client = FakeClient([(('ok', '0', 'null:'), )])
1467
233
transport = MemoryTransport()
1468
client = FakeClient(transport.base)
1469
client.add_expected_call(
1470
'Branch.get_stacked_on_url', ('quack/',),
1471
'error', ('NotStacked',))
1472
client.add_expected_call(
1473
'Branch.last_revision_info', ('quack/',),
1474
'success', ('ok', '0', 'null:'))
1475
234
transport.mkdir('quack')
1476
235
transport = transport.clone('quack')
1477
branch = self.make_remote_branch(transport, client)
236
# we do not want bzrdir to make any remote calls
237
bzrdir = RemoteBzrDir(transport, _client=False)
238
branch = RemoteBranch(bzrdir, None, _client=client)
1478
239
result = branch.last_revision_info()
1479
self.assertFinished(client)
242
[('call', 'Branch.last_revision_info', ('///quack/',))],
1480
244
self.assertEqual((0, NULL_REVISION), result)
1482
246
def test_non_empty_branch(self):
1483
247
# in a non-empty branch we also decode the response properly
1484
248
revid = u'\xc8'.encode('utf8')
249
client = FakeClient([(('ok', '2', revid), )])
1485
250
transport = MemoryTransport()
1486
client = FakeClient(transport.base)
1487
client.add_expected_call(
1488
'Branch.get_stacked_on_url', ('kwaak/',),
1489
'error', ('NotStacked',))
1490
client.add_expected_call(
1491
'Branch.last_revision_info', ('kwaak/',),
1492
'success', ('ok', '2', revid))
1493
251
transport.mkdir('kwaak')
1494
252
transport = transport.clone('kwaak')
1495
branch = self.make_remote_branch(transport, client)
253
# we do not want bzrdir to make any remote calls
254
bzrdir = RemoteBzrDir(transport, _client=False)
255
branch = RemoteBranch(bzrdir, None, _client=client)
1496
256
result = branch.last_revision_info()
259
[('call', 'Branch.last_revision_info', ('///kwaak/',))],
1497
261
self.assertEqual((2, revid), result)
1500
class TestBranch_get_stacked_on_url(TestRemote):
1501
"""Test Branch._get_stacked_on_url rpc"""
1503
def test_get_stacked_on_invalid_url(self):
1504
# test that asking for a stacked on url the server can't access works.
1505
# This isn't perfect, but then as we're in the same process there
1506
# really isn't anything we can do to be 100% sure that the server
1507
# doesn't just open in - this test probably needs to be rewritten using
1508
# a spawn()ed server.
1509
stacked_branch = self.make_branch('stacked', format='1.9')
1510
memory_branch = self.make_branch('base', format='1.9')
1511
vfs_url = self.get_vfs_only_url('base')
1512
stacked_branch.set_stacked_on_url(vfs_url)
1513
transport = stacked_branch.bzrdir.root_transport
1514
client = FakeClient(transport.base)
1515
client.add_expected_call(
1516
'Branch.get_stacked_on_url', ('stacked/',),
1517
'success', ('ok', vfs_url))
1518
# XXX: Multiple calls are bad, this second call documents what is
1520
client.add_expected_call(
1521
'Branch.get_stacked_on_url', ('stacked/',),
1522
'success', ('ok', vfs_url))
1523
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1525
repo_fmt = remote.RemoteRepositoryFormat()
1526
repo_fmt._custom_format = stacked_branch.repository._format
1527
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1529
result = branch.get_stacked_on_url()
1530
self.assertEqual(vfs_url, result)
1532
def test_backwards_compatible(self):
1533
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1534
base_branch = self.make_branch('base', format='1.6')
1535
stacked_branch = self.make_branch('stacked', format='1.6')
1536
stacked_branch.set_stacked_on_url('../base')
1537
client = FakeClient(self.get_url())
1538
branch_network_name = self.get_branch_format().network_name()
1539
client.add_expected_call(
1540
'BzrDir.open_branchV3', ('stacked/',),
1541
'success', ('branch', branch_network_name))
1542
client.add_expected_call(
1543
'BzrDir.find_repositoryV3', ('stacked/',),
1544
'success', ('ok', '', 'no', 'no', 'yes',
1545
stacked_branch.repository._format.network_name()))
1546
# called twice, once from constructor and then again by us
1547
client.add_expected_call(
1548
'Branch.get_stacked_on_url', ('stacked/',),
1549
'unknown', ('Branch.get_stacked_on_url',))
1550
client.add_expected_call(
1551
'Branch.get_stacked_on_url', ('stacked/',),
1552
'unknown', ('Branch.get_stacked_on_url',))
1553
# this will also do vfs access, but that goes direct to the transport
1554
# and isn't seen by the FakeClient.
1555
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1556
RemoteBzrDirFormat(), _client=client)
1557
branch = bzrdir.open_branch()
1558
result = branch.get_stacked_on_url()
1559
self.assertEqual('../base', result)
1560
self.assertFinished(client)
1561
# it's in the fallback list both for the RemoteRepository and its vfs
1563
self.assertEqual(1, len(branch.repository._fallback_repositories))
1565
len(branch.repository._real_repository._fallback_repositories))
1567
def test_get_stacked_on_real_branch(self):
1568
base_branch = self.make_branch('base')
1569
stacked_branch = self.make_branch('stacked')
1570
stacked_branch.set_stacked_on_url('../base')
1571
reference_format = self.get_repo_format()
1572
network_name = reference_format.network_name()
1573
client = FakeClient(self.get_url())
1574
branch_network_name = self.get_branch_format().network_name()
1575
client.add_expected_call(
1576
'BzrDir.open_branchV3', ('stacked/',),
1577
'success', ('branch', branch_network_name))
1578
client.add_expected_call(
1579
'BzrDir.find_repositoryV3', ('stacked/',),
1580
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1581
# called twice, once from constructor and then again by us
1582
client.add_expected_call(
1583
'Branch.get_stacked_on_url', ('stacked/',),
1584
'success', ('ok', '../base'))
1585
client.add_expected_call(
1586
'Branch.get_stacked_on_url', ('stacked/',),
1587
'success', ('ok', '../base'))
1588
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1589
RemoteBzrDirFormat(), _client=client)
1590
branch = bzrdir.open_branch()
1591
result = branch.get_stacked_on_url()
1592
self.assertEqual('../base', result)
1593
self.assertFinished(client)
1594
# it's in the fallback list both for the RemoteRepository.
1595
self.assertEqual(1, len(branch.repository._fallback_repositories))
1596
# And we haven't had to construct a real repository.
1597
self.assertEqual(None, branch.repository._real_repository)
1600
class TestBranchSetLastRevision(RemoteBranchTestCase):
264
class TestBranchSetLastRevision(tests.TestCase):
1602
266
def test_set_empty(self):
1603
# _set_last_revision_info('null:') is translated to calling
267
# set_revision_history([]) is translated to calling
1604
268
# Branch.set_last_revision(path, '') on the wire.
269
client = FakeClient([
271
(('ok', 'branch token', 'repo token'), ),
1605
276
transport = MemoryTransport()
1606
277
transport.mkdir('branch')
1607
278
transport = transport.clone('branch')
1609
client = FakeClient(transport.base)
1610
client.add_expected_call(
1611
'Branch.get_stacked_on_url', ('branch/',),
1612
'error', ('NotStacked',))
1613
client.add_expected_call(
1614
'Branch.lock_write', ('branch/', '', ''),
1615
'success', ('ok', 'branch token', 'repo token'))
1616
client.add_expected_call(
1617
'Branch.last_revision_info',
1619
'success', ('ok', '0', 'null:'))
1620
client.add_expected_call(
1621
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1623
client.add_expected_call(
1624
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1626
branch = self.make_remote_branch(transport, client)
280
bzrdir = RemoteBzrDir(transport, _client=False)
281
branch = RemoteBranch(bzrdir, None, _client=client)
282
# This is a hack to work around the problem that RemoteBranch currently
283
# unnecessarily invokes _ensure_real upon a call to lock_write.
284
branch._ensure_real = lambda: None
1627
285
branch.lock_write()
1628
result = branch._set_last_revision(NULL_REVISION)
287
result = branch.set_revision_history([])
289
[('call', 'Branch.set_last_revision',
290
('///branch/', 'branch token', 'repo token', 'null:'))],
1630
293
self.assertEqual(None, result)
1631
self.assertFinished(client)
1633
295
def test_set_nonempty(self):
1634
# set_last_revision_info(N, rev-idN) is translated to calling
296
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1635
297
# Branch.set_last_revision(path, rev-idN) on the wire.
1636
transport = MemoryTransport()
1637
transport.mkdir('branch')
1638
transport = transport.clone('branch')
1640
client = FakeClient(transport.base)
1641
client.add_expected_call(
1642
'Branch.get_stacked_on_url', ('branch/',),
1643
'error', ('NotStacked',))
1644
client.add_expected_call(
1645
'Branch.lock_write', ('branch/', '', ''),
1646
'success', ('ok', 'branch token', 'repo token'))
1647
client.add_expected_call(
1648
'Branch.last_revision_info',
1650
'success', ('ok', '0', 'null:'))
1652
encoded_body = bz2.compress('\n'.join(lines))
1653
client.add_success_response_with_body(encoded_body, 'ok')
1654
client.add_expected_call(
1655
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1657
client.add_expected_call(
1658
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1660
branch = self.make_remote_branch(transport, client)
1661
# Lock the branch, reset the record of remote calls.
1663
result = branch._set_last_revision('rev-id2')
1665
self.assertEqual(None, result)
1666
self.assertFinished(client)
1668
def test_no_such_revision(self):
1669
transport = MemoryTransport()
1670
transport.mkdir('branch')
1671
transport = transport.clone('branch')
1672
# A response of 'NoSuchRevision' is translated into an exception.
1673
client = FakeClient(transport.base)
1674
client.add_expected_call(
1675
'Branch.get_stacked_on_url', ('branch/',),
1676
'error', ('NotStacked',))
1677
client.add_expected_call(
1678
'Branch.lock_write', ('branch/', '', ''),
1679
'success', ('ok', 'branch token', 'repo token'))
1680
client.add_expected_call(
1681
'Branch.last_revision_info',
1683
'success', ('ok', '0', 'null:'))
1684
# get_graph calls to construct the revision history, for the set_rh
1687
encoded_body = bz2.compress('\n'.join(lines))
1688
client.add_success_response_with_body(encoded_body, 'ok')
1689
client.add_expected_call(
1690
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1691
'error', ('NoSuchRevision', 'rev-id'))
1692
client.add_expected_call(
1693
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1696
branch = self.make_remote_branch(transport, client)
1699
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1701
self.assertFinished(client)
1703
def test_tip_change_rejected(self):
1704
"""TipChangeRejected responses cause a TipChangeRejected exception to
1707
transport = MemoryTransport()
1708
transport.mkdir('branch')
1709
transport = transport.clone('branch')
1710
client = FakeClient(transport.base)
1711
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1712
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1713
client.add_expected_call(
1714
'Branch.get_stacked_on_url', ('branch/',),
1715
'error', ('NotStacked',))
1716
client.add_expected_call(
1717
'Branch.lock_write', ('branch/', '', ''),
1718
'success', ('ok', 'branch token', 'repo token'))
1719
client.add_expected_call(
1720
'Branch.last_revision_info',
1722
'success', ('ok', '0', 'null:'))
1724
encoded_body = bz2.compress('\n'.join(lines))
1725
client.add_success_response_with_body(encoded_body, 'ok')
1726
client.add_expected_call(
1727
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1728
'error', ('TipChangeRejected', rejection_msg_utf8))
1729
client.add_expected_call(
1730
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1732
branch = self.make_remote_branch(transport, client)
1734
# The 'TipChangeRejected' error response triggered by calling
1735
# set_last_revision_info causes a TipChangeRejected exception.
1736
err = self.assertRaises(
1737
errors.TipChangeRejected,
1738
branch._set_last_revision, 'rev-id')
1739
# The UTF-8 message from the response has been decoded into a unicode
1741
self.assertIsInstance(err.msg, unicode)
1742
self.assertEqual(rejection_msg_unicode, err.msg)
1744
self.assertFinished(client)
1747
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1749
def test_set_last_revision_info(self):
1750
# set_last_revision_info(num, 'rev-id') is translated to calling
1751
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1752
transport = MemoryTransport()
1753
transport.mkdir('branch')
1754
transport = transport.clone('branch')
1755
client = FakeClient(transport.base)
1756
# get_stacked_on_url
1757
client.add_error_response('NotStacked')
1759
client.add_success_response('ok', 'branch token', 'repo token')
1760
# query the current revision
1761
client.add_success_response('ok', '0', 'null:')
1763
client.add_success_response('ok')
1765
client.add_success_response('ok')
1767
branch = self.make_remote_branch(transport, client)
1768
# Lock the branch, reset the record of remote calls.
1771
result = branch.set_last_revision_info(1234, 'a-revision-id')
1773
[('call', 'Branch.last_revision_info', ('branch/',)),
1774
('call', 'Branch.set_last_revision_info',
1775
('branch/', 'branch token', 'repo token',
1776
'1234', 'a-revision-id'))],
1778
self.assertEqual(None, result)
1780
def test_no_such_revision(self):
1781
# A response of 'NoSuchRevision' is translated into an exception.
1782
transport = MemoryTransport()
1783
transport.mkdir('branch')
1784
transport = transport.clone('branch')
1785
client = FakeClient(transport.base)
1786
# get_stacked_on_url
1787
client.add_error_response('NotStacked')
1789
client.add_success_response('ok', 'branch token', 'repo token')
1791
client.add_error_response('NoSuchRevision', 'revid')
1793
client.add_success_response('ok')
1795
branch = self.make_remote_branch(transport, client)
1796
# Lock the branch, reset the record of remote calls.
1801
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1804
def test_backwards_compatibility(self):
1805
"""If the server does not support the Branch.set_last_revision_info
1806
verb (which is new in 1.4), then the client falls back to VFS methods.
1808
# This test is a little messy. Unlike most tests in this file, it
1809
# doesn't purely test what a Remote* object sends over the wire, and
1810
# how it reacts to responses from the wire. It instead relies partly
1811
# on asserting that the RemoteBranch will call
1812
# self._real_branch.set_last_revision_info(...).
1814
# First, set up our RemoteBranch with a FakeClient that raises
1815
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1816
transport = MemoryTransport()
1817
transport.mkdir('branch')
1818
transport = transport.clone('branch')
1819
client = FakeClient(transport.base)
1820
client.add_expected_call(
1821
'Branch.get_stacked_on_url', ('branch/',),
1822
'error', ('NotStacked',))
1823
client.add_expected_call(
1824
'Branch.last_revision_info',
1826
'success', ('ok', '0', 'null:'))
1827
client.add_expected_call(
1828
'Branch.set_last_revision_info',
1829
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1830
'unknown', 'Branch.set_last_revision_info')
1832
branch = self.make_remote_branch(transport, client)
1833
class StubRealBranch(object):
1836
def set_last_revision_info(self, revno, revision_id):
1838
('set_last_revision_info', revno, revision_id))
1839
def _clear_cached_state(self):
1841
real_branch = StubRealBranch()
1842
branch._real_branch = real_branch
1843
self.lock_remote_branch(branch)
1845
# Call set_last_revision_info, and verify it behaved as expected.
1846
result = branch.set_last_revision_info(1234, 'a-revision-id')
1848
[('set_last_revision_info', 1234, 'a-revision-id')],
1850
self.assertFinished(client)
1852
def test_unexpected_error(self):
1853
# If the server sends an error the client doesn't understand, it gets
1854
# turned into an UnknownErrorFromSmartServer, which is presented as a
1855
# non-internal error to the user.
1856
transport = MemoryTransport()
1857
transport.mkdir('branch')
1858
transport = transport.clone('branch')
1859
client = FakeClient(transport.base)
1860
# get_stacked_on_url
1861
client.add_error_response('NotStacked')
1863
client.add_success_response('ok', 'branch token', 'repo token')
1865
client.add_error_response('UnexpectedError')
1867
client.add_success_response('ok')
1869
branch = self.make_remote_branch(transport, client)
1870
# Lock the branch, reset the record of remote calls.
1874
err = self.assertRaises(
1875
errors.UnknownErrorFromSmartServer,
1876
branch.set_last_revision_info, 123, 'revid')
1877
self.assertEqual(('UnexpectedError',), err.error_tuple)
1880
def test_tip_change_rejected(self):
1881
"""TipChangeRejected responses cause a TipChangeRejected exception to
1884
transport = MemoryTransport()
1885
transport.mkdir('branch')
1886
transport = transport.clone('branch')
1887
client = FakeClient(transport.base)
1888
# get_stacked_on_url
1889
client.add_error_response('NotStacked')
1891
client.add_success_response('ok', 'branch token', 'repo token')
1893
client.add_error_response('TipChangeRejected', 'rejection message')
1895
client.add_success_response('ok')
1897
branch = self.make_remote_branch(transport, client)
1898
# Lock the branch, reset the record of remote calls.
1900
self.addCleanup(branch.unlock)
1903
# The 'TipChangeRejected' error response triggered by calling
1904
# set_last_revision_info causes a TipChangeRejected exception.
1905
err = self.assertRaises(
1906
errors.TipChangeRejected,
1907
branch.set_last_revision_info, 123, 'revid')
1908
self.assertEqual('rejection message', err.msg)
1911
class TestBranchGetSetConfig(RemoteBranchTestCase):
1913
def test_get_branch_conf(self):
1914
# in an empty branch we decode the response properly
1915
client = FakeClient()
1916
client.add_expected_call(
1917
'Branch.get_stacked_on_url', ('memory:///',),
1918
'error', ('NotStacked',),)
1919
client.add_success_response_with_body('# config file body', 'ok')
1920
transport = MemoryTransport()
1921
branch = self.make_remote_branch(transport, client)
1922
config = branch.get_config()
1923
config.has_explicit_nickname()
1925
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1926
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1929
def test_get_multi_line_branch_conf(self):
1930
# Make sure that multiple-line branch.conf files are supported
1932
# https://bugs.launchpad.net/bzr/+bug/354075
1933
client = FakeClient()
1934
client.add_expected_call(
1935
'Branch.get_stacked_on_url', ('memory:///',),
1936
'error', ('NotStacked',),)
1937
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1938
transport = MemoryTransport()
1939
branch = self.make_remote_branch(transport, client)
1940
config = branch.get_config()
1941
self.assertEqual(u'2', config.get_user_option('b'))
1943
def test_set_option(self):
1944
client = FakeClient()
1945
client.add_expected_call(
1946
'Branch.get_stacked_on_url', ('memory:///',),
1947
'error', ('NotStacked',),)
1948
client.add_expected_call(
1949
'Branch.lock_write', ('memory:///', '', ''),
1950
'success', ('ok', 'branch token', 'repo token'))
1951
client.add_expected_call(
1952
'Branch.set_config_option', ('memory:///', 'branch token',
1953
'repo token', 'foo', 'bar', ''),
1955
client.add_expected_call(
1956
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1958
transport = MemoryTransport()
1959
branch = self.make_remote_branch(transport, client)
1961
config = branch._get_config()
1962
config.set_option('foo', 'bar')
1964
self.assertFinished(client)
1966
def test_set_option_with_dict(self):
1967
client = FakeClient()
1968
client.add_expected_call(
1969
'Branch.get_stacked_on_url', ('memory:///',),
1970
'error', ('NotStacked',),)
1971
client.add_expected_call(
1972
'Branch.lock_write', ('memory:///', '', ''),
1973
'success', ('ok', 'branch token', 'repo token'))
1974
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1975
client.add_expected_call(
1976
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1977
'repo token', encoded_dict_value, 'foo', ''),
1979
client.add_expected_call(
1980
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1982
transport = MemoryTransport()
1983
branch = self.make_remote_branch(transport, client)
1985
config = branch._get_config()
1987
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1990
self.assertFinished(client)
1992
def test_backwards_compat_set_option(self):
1993
self.setup_smart_server_with_call_log()
1994
branch = self.make_branch('.')
1995
verb = 'Branch.set_config_option'
1996
self.disable_verb(verb)
1998
self.addCleanup(branch.unlock)
1999
self.reset_smart_call_log()
2000
branch._get_config().set_option('value', 'name')
2001
self.assertLength(10, self.hpss_calls)
2002
self.assertEqual('value', branch._get_config().get_option('name'))
2004
def test_backwards_compat_set_option_with_dict(self):
2005
self.setup_smart_server_with_call_log()
2006
branch = self.make_branch('.')
2007
verb = 'Branch.set_config_option_dict'
2008
self.disable_verb(verb)
2010
self.addCleanup(branch.unlock)
2011
self.reset_smart_call_log()
2012
config = branch._get_config()
2013
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2014
config.set_option(value_dict, 'name')
2015
self.assertLength(10, self.hpss_calls)
2016
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2019
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2021
def test_get_branch_conf(self):
2022
# in an empty branch we decode the response properly
2023
client = FakeClient()
2024
client.add_expected_call(
2025
'Branch.get_stacked_on_url', ('memory:///',),
2026
'error', ('NotStacked',),)
2027
client.add_success_response_with_body('# config file body', 'ok')
2028
transport = MemoryTransport()
2029
branch = self.make_remote_branch(transport, client)
2030
config = branch.get_config_stack()
2032
config.get("log_format")
2034
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2035
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
2038
def test_set_branch_conf(self):
2039
client = FakeClient()
2040
client.add_expected_call(
2041
'Branch.get_stacked_on_url', ('memory:///',),
2042
'error', ('NotStacked',),)
2043
client.add_expected_call(
2044
'Branch.lock_write', ('memory:///', '', ''),
2045
'success', ('ok', 'branch token', 'repo token'))
2046
client.add_expected_call(
2047
'Branch.get_config_file', ('memory:///', ),
2048
'success', ('ok', ), "# line 1\n")
2049
client.add_expected_call(
2050
'Branch.put_config_file', ('memory:///', 'branch token',
2053
client.add_expected_call(
2054
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2056
transport = MemoryTransport()
2057
branch = self.make_remote_branch(transport, client)
2059
config = branch.get_config_stack()
2060
config.set('email', 'The Dude <lebowski@example.com>')
2062
self.assertFinished(client)
2064
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2065
('call', 'Branch.lock_write', ('memory:///', '', '')),
2066
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2067
('call_with_body_bytes_expecting_body', 'Branch.put_config_file',
2068
('memory:///', 'branch token', 'repo token'),
2069
'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2070
('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
2074
class TestBranchLockWrite(RemoteBranchTestCase):
298
client = FakeClient([
300
(('ok', 'branch token', 'repo token'), ),
305
transport = MemoryTransport()
306
transport.mkdir('branch')
307
transport = transport.clone('branch')
309
bzrdir = RemoteBzrDir(transport, _client=False)
310
branch = RemoteBranch(bzrdir, None, _client=client)
311
# This is a hack to work around the problem that RemoteBranch currently
312
# unnecessarily invokes _ensure_real upon a call to lock_write.
313
branch._ensure_real = lambda: None
314
# Lock the branch, reset the record of remote calls.
318
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
320
[('call', 'Branch.set_last_revision',
321
('///branch/', 'branch token', 'repo token', 'rev-id2'))],
324
self.assertEqual(None, result)
326
def test_no_such_revision(self):
327
# A response of 'NoSuchRevision' is translated into an exception.
328
client = FakeClient([
330
(('ok', 'branch token', 'repo token'), ),
332
(('NoSuchRevision', 'rev-id'), ),
335
transport = MemoryTransport()
336
transport.mkdir('branch')
337
transport = transport.clone('branch')
339
bzrdir = RemoteBzrDir(transport, _client=False)
340
branch = RemoteBranch(bzrdir, None, _client=client)
341
branch._ensure_real = lambda: None
346
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
350
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
351
"""Test branch.control_files api munging...
353
We special case RemoteBranch.control_files.get('branch.conf') to
354
call a specific API so that RemoteBranch's can intercept configuration
355
file reading, allowing them to signal to the client about things like
356
'email is configured for commits'.
359
def test_get_branch_conf(self):
360
# in an empty branch we decode the response properly
361
client = FakeClient([(('ok', ), 'config file body')])
362
# we need to make a real branch because the remote_branch.control_files
363
# will trigger _ensure_real.
364
branch = self.make_branch('quack')
365
transport = branch.bzrdir.root_transport
366
# we do not want bzrdir to make any remote calls
367
bzrdir = RemoteBzrDir(transport, _client=False)
368
branch = RemoteBranch(bzrdir, None, _client=client)
369
result = branch.control_files.get('branch.conf')
371
[('call_expecting_body', 'Branch.get_config_file', ('///quack/',))],
373
self.assertEqual('config file body', result.read())
376
class TestBranchLockWrite(tests.TestCase):
2076
378
def test_lock_write_unlockable(self):
379
client = FakeClient([(('UnlockableTransport', ), '')])
2077
380
transport = MemoryTransport()
2078
client = FakeClient(transport.base)
2079
client.add_expected_call(
2080
'Branch.get_stacked_on_url', ('quack/',),
2081
'error', ('NotStacked',),)
2082
client.add_expected_call(
2083
'Branch.lock_write', ('quack/', '', ''),
2084
'error', ('UnlockableTransport',))
2085
381
transport.mkdir('quack')
2086
382
transport = transport.clone('quack')
2087
branch = self.make_remote_branch(transport, client)
383
# we do not want bzrdir to make any remote calls
384
bzrdir = RemoteBzrDir(transport, _client=False)
385
branch = RemoteBranch(bzrdir, None, _client=client)
2088
386
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2089
self.assertFinished(client)
2092
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2094
def test_simple(self):
2095
transport = MemoryTransport()
2096
client = FakeClient(transport.base)
2097
client.add_expected_call(
2098
'Branch.get_stacked_on_url', ('quack/',),
2099
'error', ('NotStacked',),)
2100
client.add_expected_call(
2101
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2102
'success', ('ok', '0',),)
2103
client.add_expected_call(
2104
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2105
'error', ('NoSuchRevision', 'unknown',),)
2106
transport.mkdir('quack')
2107
transport = transport.clone('quack')
2108
branch = self.make_remote_branch(transport, client)
2109
self.assertEquals(0, branch.revision_id_to_revno('null:'))
2110
self.assertRaises(errors.NoSuchRevision,
2111
branch.revision_id_to_revno, 'unknown')
2112
self.assertFinished(client)
2114
def test_dotted(self):
2115
transport = MemoryTransport()
2116
client = FakeClient(transport.base)
2117
client.add_expected_call(
2118
'Branch.get_stacked_on_url', ('quack/',),
2119
'error', ('NotStacked',),)
2120
client.add_expected_call(
2121
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2122
'success', ('ok', '0',),)
2123
client.add_expected_call(
2124
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2125
'error', ('NoSuchRevision', 'unknown',),)
2126
transport.mkdir('quack')
2127
transport = transport.clone('quack')
2128
branch = self.make_remote_branch(transport, client)
2129
self.assertEquals((0, ), branch.revision_id_to_dotted_revno('null:'))
2130
self.assertRaises(errors.NoSuchRevision,
2131
branch.revision_id_to_dotted_revno, 'unknown')
2132
self.assertFinished(client)
2134
def test_dotted_no_smart_verb(self):
2135
self.setup_smart_server_with_call_log()
2136
branch = self.make_branch('.')
2137
self.disable_verb('Branch.revision_id_to_revno')
2138
self.reset_smart_call_log()
2139
self.assertEquals((0, ),
2140
branch.revision_id_to_dotted_revno('null:'))
2141
self.assertLength(7, self.hpss_calls)
2144
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2146
def test__get_config(self):
2147
client = FakeClient()
2148
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
2149
transport = MemoryTransport()
2150
bzrdir = self.make_remote_bzrdir(transport, client)
2151
config = bzrdir.get_config()
2152
self.assertEqual('/', config.get_default_stack_on())
2153
387
self.assertEqual(
2154
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
388
[('call', 'Branch.lock_write', ('///quack/', '', ''))],
2157
def test_set_option_uses_vfs(self):
2158
self.setup_smart_server_with_call_log()
2159
bzrdir = self.make_bzrdir('.')
2160
self.reset_smart_call_log()
2161
config = bzrdir.get_config()
2162
config.set_default_stack_on('/')
2163
self.assertLength(3, self.hpss_calls)
2165
def test_backwards_compat_get_option(self):
2166
self.setup_smart_server_with_call_log()
2167
bzrdir = self.make_bzrdir('.')
2168
verb = 'BzrDir.get_config_file'
2169
self.disable_verb(verb)
2170
self.reset_smart_call_log()
2171
self.assertEqual(None,
2172
bzrdir._get_config().get_option('default_stack_on'))
2173
self.assertLength(3, self.hpss_calls)
2176
392
class TestTransportIsReadonly(tests.TestCase):
2178
394
def test_true(self):
2179
client = FakeClient()
2180
client.add_success_response('yes')
395
client = FakeClient([(('yes',), '')])
2181
396
transport = RemoteTransport('bzr://example.com/', medium=False,
2183
398
self.assertEqual(True, transport.is_readonly())
2389
class TestRepositoryBreakLock(TestRemoteRepository):
2391
def test_break_lock(self):
2392
transport_path = 'quack'
2393
repo, client = self.setup_fake_client_and_repository(transport_path)
2394
client.add_success_response('ok')
2397
[('call', 'Repository.break_lock', ('quack/',))],
2401
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2403
def test_get_serializer_format(self):
2404
transport_path = 'hill'
2405
repo, client = self.setup_fake_client_and_repository(transport_path)
2406
client.add_success_response('ok', '7')
2407
self.assertEquals('7', repo.get_serializer_format())
2409
[('call', 'VersionedFileRepository.get_serializer_format',
2414
class TestRepositoryReconcile(TestRemoteRepository):
2416
def test_reconcile(self):
2417
transport_path = 'hill'
2418
repo, client = self.setup_fake_client_and_repository(transport_path)
2419
body = ("garbage_inventories: 2\n"
2420
"inconsistent_parents: 3\n")
2421
client.add_expected_call(
2422
'Repository.lock_write', ('hill/', ''),
2423
'success', ('ok', 'a token'))
2424
client.add_success_response_with_body(body, 'ok')
2425
reconciler = repo.reconcile()
2427
[('call', 'Repository.lock_write', ('hill/', '')),
2428
('call_expecting_body', 'Repository.reconcile',
2429
('hill/', 'a token'))],
2431
self.assertEquals(2, reconciler.garbage_inventories)
2432
self.assertEquals(3, reconciler.inconsistent_parents)
2435
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2437
def test_text(self):
2438
# ('ok',), body with signature text
2439
transport_path = 'quack'
2440
repo, client = self.setup_fake_client_and_repository(transport_path)
2441
client.add_success_response_with_body(
2443
self.assertEquals("THETEXT", repo.get_signature_text("revid"))
2445
[('call_expecting_body', 'Repository.get_revision_signature_text',
2446
('quack/', 'revid'))],
2449
def test_no_signature(self):
2450
transport_path = 'quick'
2451
repo, client = self.setup_fake_client_and_repository(transport_path)
2452
client.add_error_response('nosuchrevision', 'unknown')
2453
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2456
[('call_expecting_body', 'Repository.get_revision_signature_text',
2457
('quick/', 'unknown'))],
2461
class TestRepositoryGetGraph(TestRemoteRepository):
2463
def test_get_graph(self):
2464
# get_graph returns a graph with a custom parents provider.
2465
transport_path = 'quack'
2466
repo, client = self.setup_fake_client_and_repository(transport_path)
2467
graph = repo.get_graph()
2468
self.assertNotEqual(graph._parents_provider, repo)
2471
class TestRepositoryAddSignatureText(TestRemoteRepository):
2473
def test_add_signature_text(self):
2474
transport_path = 'quack'
2475
repo, client = self.setup_fake_client_and_repository(transport_path)
2476
client.add_expected_call(
2477
'Repository.lock_write', ('quack/', ''),
2478
'success', ('ok', 'a token'))
2479
client.add_expected_call(
2480
'Repository.start_write_group', ('quack/', 'a token'),
2481
'success', ('ok', ('token1', )))
2482
client.add_expected_call(
2483
'Repository.add_signature_text', ('quack/', 'a token', 'rev1',
2485
'success', ('ok', ), None)
2487
repo.start_write_group()
2489
repo.add_signature_text("rev1", "every bloody emperor"))
2491
('call_with_body_bytes_expecting_body',
2492
'Repository.add_signature_text',
2493
('quack/', 'a token', 'rev1', 'token1'),
2494
'every bloody emperor'),
2498
class TestRepositoryGetParentMap(TestRemoteRepository):
2500
def test_get_parent_map_caching(self):
2501
# get_parent_map returns from cache until unlock()
2502
# setup a reponse with two revisions
2503
r1 = u'\u0e33'.encode('utf8')
2504
r2 = u'\u0dab'.encode('utf8')
2505
lines = [' '.join([r2, r1]), r1]
2506
encoded_body = bz2.compress('\n'.join(lines))
2508
transport_path = 'quack'
2509
repo, client = self.setup_fake_client_and_repository(transport_path)
2510
client.add_success_response_with_body(encoded_body, 'ok')
2511
client.add_success_response_with_body(encoded_body, 'ok')
2513
graph = repo.get_graph()
2514
parents = graph.get_parent_map([r2])
2515
self.assertEqual({r2: (r1,)}, parents)
2516
# locking and unlocking deeper should not reset
2519
parents = graph.get_parent_map([r1])
2520
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2522
[('call_with_body_bytes_expecting_body',
2523
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2527
# now we call again, and it should use the second response.
2529
graph = repo.get_graph()
2530
parents = graph.get_parent_map([r1])
2531
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2533
[('call_with_body_bytes_expecting_body',
2534
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2536
('call_with_body_bytes_expecting_body',
2537
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2543
def test_get_parent_map_reconnects_if_unknown_method(self):
2544
transport_path = 'quack'
2545
rev_id = 'revision-id'
2546
repo, client = self.setup_fake_client_and_repository(transport_path)
2547
client.add_unknown_method_response('Repository.get_parent_map')
2548
client.add_success_response_with_body(rev_id, 'ok')
2549
self.assertFalse(client._medium._is_remote_before((1, 2)))
2550
parents = repo.get_parent_map([rev_id])
2552
[('call_with_body_bytes_expecting_body',
2553
'Repository.get_parent_map',
2554
('quack/', 'include-missing:', rev_id), '\n\n0'),
2555
('disconnect medium',),
2556
('call_expecting_body', 'Repository.get_revision_graph',
2559
# The medium is now marked as being connected to an older server
2560
self.assertTrue(client._medium._is_remote_before((1, 2)))
2561
self.assertEqual({rev_id: ('null:',)}, parents)
2563
def test_get_parent_map_fallback_parentless_node(self):
2564
"""get_parent_map falls back to get_revision_graph on old servers. The
2565
results from get_revision_graph are tweaked to match the get_parent_map
2568
Specifically, a {key: ()} result from get_revision_graph means "no
2569
parents" for that key, which in get_parent_map results should be
2570
represented as {key: ('null:',)}.
2572
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2574
rev_id = 'revision-id'
2575
transport_path = 'quack'
2576
repo, client = self.setup_fake_client_and_repository(transport_path)
2577
client.add_success_response_with_body(rev_id, 'ok')
2578
client._medium._remember_remote_is_before((1, 2))
2579
parents = repo.get_parent_map([rev_id])
2581
[('call_expecting_body', 'Repository.get_revision_graph',
2584
self.assertEqual({rev_id: ('null:',)}, parents)
2586
def test_get_parent_map_unexpected_response(self):
2587
repo, client = self.setup_fake_client_and_repository('path')
2588
client.add_success_response('something unexpected!')
2590
errors.UnexpectedSmartServerResponse,
2591
repo.get_parent_map, ['a-revision-id'])
2593
def test_get_parent_map_negative_caches_missing_keys(self):
2594
self.setup_smart_server_with_call_log()
2595
repo = self.make_repository('foo')
2596
self.assertIsInstance(repo, RemoteRepository)
2598
self.addCleanup(repo.unlock)
2599
self.reset_smart_call_log()
2600
graph = repo.get_graph()
2601
self.assertEqual({},
2602
graph.get_parent_map(['some-missing', 'other-missing']))
2603
self.assertLength(1, self.hpss_calls)
2604
# No call if we repeat this
2605
self.reset_smart_call_log()
2606
graph = repo.get_graph()
2607
self.assertEqual({},
2608
graph.get_parent_map(['some-missing', 'other-missing']))
2609
self.assertLength(0, self.hpss_calls)
2610
# Asking for more unknown keys makes a request.
2611
self.reset_smart_call_log()
2612
graph = repo.get_graph()
2613
self.assertEqual({},
2614
graph.get_parent_map(['some-missing', 'other-missing',
2616
self.assertLength(1, self.hpss_calls)
2618
def disableExtraResults(self):
2619
self.overrideAttr(SmartServerRepositoryGetParentMap,
2620
'no_extra_results', True)
2622
def test_null_cached_missing_and_stop_key(self):
2623
self.setup_smart_server_with_call_log()
2624
# Make a branch with a single revision.
2625
builder = self.make_branch_builder('foo')
2626
builder.start_series()
2627
builder.build_snapshot('first', None, [
2628
('add', ('', 'root-id', 'directory', ''))])
2629
builder.finish_series()
2630
branch = builder.get_branch()
2631
repo = branch.repository
2632
self.assertIsInstance(repo, RemoteRepository)
2633
# Stop the server from sending extra results.
2634
self.disableExtraResults()
2636
self.addCleanup(repo.unlock)
2637
self.reset_smart_call_log()
2638
graph = repo.get_graph()
2639
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2640
# 'first' it will be a candidate for the stop_keys of subsequent
2641
# requests, and because 'null:' was queried but not returned it will be
2642
# cached as missing.
2643
self.assertEqual({'first': ('null:',)},
2644
graph.get_parent_map(['first', 'null:']))
2645
# Now query for another key. This request will pass along a recipe of
2646
# start and stop keys describing the already cached results, and this
2647
# recipe's revision count must be correct (or else it will trigger an
2648
# error from the server).
2649
self.assertEqual({}, graph.get_parent_map(['another-key']))
2650
# This assertion guards against disableExtraResults silently failing to
2651
# work, thus invalidating the test.
2652
self.assertLength(2, self.hpss_calls)
2654
def test_get_parent_map_gets_ghosts_from_result(self):
2655
# asking for a revision should negatively cache close ghosts in its
2657
self.setup_smart_server_with_call_log()
2658
tree = self.make_branch_and_memory_tree('foo')
2661
builder = treebuilder.TreeBuilder()
2662
builder.start_tree(tree)
2664
builder.finish_tree()
2665
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2666
rev_id = tree.commit('')
2670
self.addCleanup(tree.unlock)
2671
repo = tree.branch.repository
2672
self.assertIsInstance(repo, RemoteRepository)
2674
repo.get_parent_map([rev_id])
2675
self.reset_smart_call_log()
2676
# Now asking for rev_id's ghost parent should not make calls
2677
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2678
self.assertLength(0, self.hpss_calls)
2680
def test_exposes_get_cached_parent_map(self):
2681
"""RemoteRepository exposes get_cached_parent_map from
2684
r1 = u'\u0e33'.encode('utf8')
2685
r2 = u'\u0dab'.encode('utf8')
2686
lines = [' '.join([r2, r1]), r1]
2687
encoded_body = bz2.compress('\n'.join(lines))
2689
transport_path = 'quack'
2690
repo, client = self.setup_fake_client_and_repository(transport_path)
2691
client.add_success_response_with_body(encoded_body, 'ok')
2693
# get_cached_parent_map should *not* trigger an RPC
2694
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2695
self.assertEqual([], client._calls)
2696
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2697
self.assertEqual({r1: (NULL_REVISION,)},
2698
repo.get_cached_parent_map([r1]))
2700
[('call_with_body_bytes_expecting_body',
2701
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2707
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2709
def test_allows_new_revisions(self):
2710
"""get_parent_map's results can be updated by commit."""
2711
smart_server = test_server.SmartTCPServer_for_testing()
2712
self.start_server(smart_server)
2713
self.make_branch('branch')
2714
branch = Branch.open(smart_server.get_url() + '/branch')
2715
tree = branch.create_checkout('tree', lightweight=True)
2717
self.addCleanup(tree.unlock)
2718
graph = tree.branch.repository.get_graph()
2719
# This provides an opportunity for the missing rev-id to be cached.
2720
self.assertEqual({}, graph.get_parent_map(['rev1']))
2721
tree.commit('message', rev_id='rev1')
2722
graph = tree.branch.repository.get_graph()
2723
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2726
class TestRepositoryGetRevisions(TestRemoteRepository):
2728
def test_hpss_missing_revision(self):
2729
transport_path = 'quack'
2730
repo, client = self.setup_fake_client_and_repository(transport_path)
2731
client.add_success_response_with_body(
2733
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2734
['somerev1', 'anotherrev2'])
2736
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2737
('quack/', ), "somerev1\nanotherrev2")],
2740
def test_hpss_get_single_revision(self):
2741
transport_path = 'quack'
2742
repo, client = self.setup_fake_client_and_repository(transport_path)
2743
somerev1 = Revision("somerev1")
2744
somerev1.committer = "Joe Committer <joe@example.com>"
2745
somerev1.timestamp = 1321828927
2746
somerev1.timezone = -60
2747
somerev1.inventory_sha1 = "691b39be74c67b1212a75fcb19c433aaed903c2b"
2748
somerev1.message = "Message"
2749
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2751
# Split up body into two bits to make sure the zlib compression object
2752
# gets data fed twice.
2753
client.add_success_response_with_body(
2754
[body[:10], body[10:]], 'ok', '10')
2755
revs = repo.get_revisions(['somerev1'])
2756
self.assertEquals(revs, [somerev1])
2758
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2759
('quack/', ), "somerev1")],
2763
531
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2765
533
def test_null_revision(self):
2766
534
# a null revision has the predictable result {}, we should have no wire
2767
535
# traffic when calling it with this argument
536
responses = [(('notused', ), '')]
2768
537
transport_path = 'empty'
2769
repo, client = self.setup_fake_client_and_repository(transport_path)
2770
client.add_success_response('notused')
2771
# actual RemoteRepository.get_revision_graph is gone, but there's an
2772
# equivalent private method for testing
2773
result = repo._get_revision_graph(NULL_REVISION)
538
repo, client = self.setup_fake_client_and_repository(
539
responses, transport_path)
540
result = repo.get_revision_graph(NULL_REVISION)
2774
541
self.assertEqual([], client._calls)
2775
542
self.assertEqual({}, result)
3189
686
def test_none(self):
3190
687
# repo.has_revision(None) should not cause any traffic.
3191
688
transport_path = 'quack'
3192
repo, client = self.setup_fake_client_and_repository(transport_path)
690
repo, client = self.setup_fake_client_and_repository(
691
responses, transport_path)
3194
693
# The null revision is always there, so has_revision(None) == True.
3195
self.assertEqual(True, repo.has_revision(NULL_REVISION))
694
self.assertEqual(True, repo.has_revision(None))
3197
696
# The remote repo shouldn't be accessed.
3198
697
self.assertEqual([], client._calls)
3201
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3202
"""Test Repository.iter_file_bytes."""
3204
def test_single(self):
3205
transport_path = 'quack'
3206
repo, client = self.setup_fake_client_and_repository(transport_path)
3207
client.add_expected_call(
3208
'Repository.iter_files_bytes', ('quack/', ),
3209
'success', ('ok',), iter(["ok\x000", "\n", zlib.compress("mydata" * 10)]))
3210
for (identifier, byte_stream) in repo.iter_files_bytes([("somefile",
3211
"somerev", "myid")]):
3212
self.assertEquals("myid", identifier)
3213
self.assertEquals("".join(byte_stream), "mydata" * 10)
3215
def test_missing(self):
3216
transport_path = 'quack'
3217
repo, client = self.setup_fake_client_and_repository(transport_path)
3218
client.add_expected_call(
3219
'Repository.iter_files_bytes',
3221
'error', ('RevisionNotPresent', 'somefile', 'somerev'),
3222
iter(["absent\0somefile\0somerev\n"]))
3223
self.assertRaises(errors.RevisionNotPresent, list,
3224
repo.iter_files_bytes(
3225
[("somefile", "somerev", "myid")]))
3228
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3229
"""Base class for Repository.insert_stream and .insert_stream_1.19
3233
def checkInsertEmptyStream(self, repo, client):
3234
"""Insert an empty stream, checking the result.
3236
This checks that there are no resume_tokens or missing_keys, and that
3237
the client is finished.
3239
sink = repo._get_sink()
3240
fmt = repository.format_registry.get_default()
3241
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3242
self.assertEqual([], resume_tokens)
3243
self.assertEqual(set(), missing_keys)
3244
self.assertFinished(client)
3247
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3248
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3251
This test case is very similar to TestRepositoryInsertStream_1_19.
3255
TestRemoteRepository.setUp(self)
3256
self.disable_verb('Repository.insert_stream_1.19')
3258
def test_unlocked_repo(self):
3259
transport_path = 'quack'
3260
repo, client = self.setup_fake_client_and_repository(transport_path)
3261
client.add_expected_call(
3262
'Repository.insert_stream_1.19', ('quack/', ''),
3263
'unknown', ('Repository.insert_stream_1.19',))
3264
client.add_expected_call(
3265
'Repository.insert_stream', ('quack/', ''),
3267
client.add_expected_call(
3268
'Repository.insert_stream', ('quack/', ''),
3270
self.checkInsertEmptyStream(repo, client)
3272
def test_locked_repo_with_no_lock_token(self):
3273
transport_path = 'quack'
3274
repo, client = self.setup_fake_client_and_repository(transport_path)
3275
client.add_expected_call(
3276
'Repository.lock_write', ('quack/', ''),
3277
'success', ('ok', ''))
3278
client.add_expected_call(
3279
'Repository.insert_stream_1.19', ('quack/', ''),
3280
'unknown', ('Repository.insert_stream_1.19',))
3281
client.add_expected_call(
3282
'Repository.insert_stream', ('quack/', ''),
3284
client.add_expected_call(
3285
'Repository.insert_stream', ('quack/', ''),
3288
self.checkInsertEmptyStream(repo, client)
3290
def test_locked_repo_with_lock_token(self):
3291
transport_path = 'quack'
3292
repo, client = self.setup_fake_client_and_repository(transport_path)
3293
client.add_expected_call(
3294
'Repository.lock_write', ('quack/', ''),
3295
'success', ('ok', 'a token'))
3296
client.add_expected_call(
3297
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3298
'unknown', ('Repository.insert_stream_1.19',))
3299
client.add_expected_call(
3300
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3302
client.add_expected_call(
3303
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3306
self.checkInsertEmptyStream(repo, client)
3308
def test_stream_with_inventory_deltas(self):
3309
"""'inventory-deltas' substreams cannot be sent to the
3310
Repository.insert_stream verb, because not all servers that implement
3311
that verb will accept them. So when one is encountered the RemoteSink
3312
immediately stops using that verb and falls back to VFS insert_stream.
3314
transport_path = 'quack'
3315
repo, client = self.setup_fake_client_and_repository(transport_path)
3316
client.add_expected_call(
3317
'Repository.insert_stream_1.19', ('quack/', ''),
3318
'unknown', ('Repository.insert_stream_1.19',))
3319
client.add_expected_call(
3320
'Repository.insert_stream', ('quack/', ''),
3322
client.add_expected_call(
3323
'Repository.insert_stream', ('quack/', ''),
3325
# Create a fake real repository for insert_stream to fall back on, so
3326
# that we can directly see the records the RemoteSink passes to the
3331
def insert_stream(self, stream, src_format, resume_tokens):
3332
for substream_kind, substream in stream:
3333
self.records.append(
3334
(substream_kind, [record.key for record in substream]))
3335
return ['fake tokens'], ['fake missing keys']
3336
fake_real_sink = FakeRealSink()
3337
class FakeRealRepository:
3338
def _get_sink(self):
3339
return fake_real_sink
3340
def is_in_write_group(self):
3342
def refresh_data(self):
3344
repo._real_repository = FakeRealRepository()
3345
sink = repo._get_sink()
3346
fmt = repository.format_registry.get_default()
3347
stream = self.make_stream_with_inv_deltas(fmt)
3348
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3349
# Every record from the first inventory delta should have been sent to
3351
expected_records = [
3352
('inventory-deltas', [('rev2',), ('rev3',)]),
3353
('texts', [('some-rev', 'some-file')])]
3354
self.assertEqual(expected_records, fake_real_sink.records)
3355
# The return values from the real sink's insert_stream are propagated
3356
# back to the original caller.
3357
self.assertEqual(['fake tokens'], resume_tokens)
3358
self.assertEqual(['fake missing keys'], missing_keys)
3359
self.assertFinished(client)
3361
def make_stream_with_inv_deltas(self, fmt):
3362
"""Make a simple stream with an inventory delta followed by more
3363
records and more substreams to test that all records and substreams
3364
from that point on are used.
3366
This sends, in order:
3367
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3369
* texts substream: (some-rev, some-file)
3371
# Define a stream using generators so that it isn't rewindable.
3372
inv = inventory.Inventory(revision_id='rev1')
3373
inv.root.revision = 'rev1'
3374
def stream_with_inv_delta():
3375
yield ('inventories', inventories_substream())
3376
yield ('inventory-deltas', inventory_delta_substream())
3378
versionedfile.FulltextContentFactory(
3379
('some-rev', 'some-file'), (), None, 'content')])
3380
def inventories_substream():
3381
# An empty inventory fulltext. This will be streamed normally.
3382
text = fmt._serializer.write_inventory_to_string(inv)
3383
yield versionedfile.FulltextContentFactory(
3384
('rev1',), (), None, text)
3385
def inventory_delta_substream():
3386
# An inventory delta. This can't be streamed via this verb, so it
3387
# will trigger a fallback to VFS insert_stream.
3388
entry = inv.make_entry(
3389
'directory', 'newdir', inv.root.file_id, 'newdir-id')
3390
entry.revision = 'ghost'
3391
delta = [(None, 'newdir', 'newdir-id', entry)]
3392
serializer = inventory_delta.InventoryDeltaSerializer(
3393
versioned_root=True, tree_references=False)
3394
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
3395
yield versionedfile.ChunkedContentFactory(
3396
('rev2',), (('rev1',)), None, lines)
3398
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
3399
yield versionedfile.ChunkedContentFactory(
3400
('rev3',), (('rev1',)), None, lines)
3401
return stream_with_inv_delta()
3404
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3406
def test_unlocked_repo(self):
3407
transport_path = 'quack'
3408
repo, client = self.setup_fake_client_and_repository(transport_path)
3409
client.add_expected_call(
3410
'Repository.insert_stream_1.19', ('quack/', ''),
3412
client.add_expected_call(
3413
'Repository.insert_stream_1.19', ('quack/', ''),
3415
self.checkInsertEmptyStream(repo, client)
3417
def test_locked_repo_with_no_lock_token(self):
3418
transport_path = 'quack'
3419
repo, client = self.setup_fake_client_and_repository(transport_path)
3420
client.add_expected_call(
3421
'Repository.lock_write', ('quack/', ''),
3422
'success', ('ok', ''))
3423
client.add_expected_call(
3424
'Repository.insert_stream_1.19', ('quack/', ''),
3426
client.add_expected_call(
3427
'Repository.insert_stream_1.19', ('quack/', ''),
3430
self.checkInsertEmptyStream(repo, client)
3432
def test_locked_repo_with_lock_token(self):
3433
transport_path = 'quack'
3434
repo, client = self.setup_fake_client_and_repository(transport_path)
3435
client.add_expected_call(
3436
'Repository.lock_write', ('quack/', ''),
3437
'success', ('ok', 'a token'))
3438
client.add_expected_call(
3439
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3441
client.add_expected_call(
3442
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3445
self.checkInsertEmptyStream(repo, client)
3448
700
class TestRepositoryTarball(TestRemoteRepository):
3450
702
# This is a canned tarball reponse we can validate against
3497
769
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3498
770
self.assertTrue(isinstance(src_repo, RemoteRepository))
3499
771
src_repo.copy_content_into(dest_repo)
3502
class _StubRealPackRepository(object):
3504
def __init__(self, calls):
3506
self._pack_collection = _StubPackCollection(calls)
3508
def start_write_group(self):
3509
self.calls.append(('start_write_group',))
3511
def is_in_write_group(self):
3514
def refresh_data(self):
3515
self.calls.append(('pack collection reload_pack_names',))
3518
class _StubPackCollection(object):
3520
def __init__(self, calls):
3524
self.calls.append(('pack collection autopack',))
3527
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3528
"""Tests for RemoteRepository.autopack implementation."""
3531
"""When the server returns 'ok' and there's no _real_repository, then
3532
nothing else happens: the autopack method is done.
3534
transport_path = 'quack'
3535
repo, client = self.setup_fake_client_and_repository(transport_path)
3536
client.add_expected_call(
3537
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
3539
self.assertFinished(client)
3541
def test_ok_with_real_repo(self):
3542
"""When the server returns 'ok' and there is a _real_repository, then
3543
the _real_repository's reload_pack_name's method will be called.
3545
transport_path = 'quack'
3546
repo, client = self.setup_fake_client_and_repository(transport_path)
3547
client.add_expected_call(
3548
'PackRepository.autopack', ('quack/',),
3550
repo._real_repository = _StubRealPackRepository(client._calls)
3553
[('call', 'PackRepository.autopack', ('quack/',)),
3554
('pack collection reload_pack_names',)],
3557
def test_backwards_compatibility(self):
3558
"""If the server does not recognise the PackRepository.autopack verb,
3559
fallback to the real_repository's implementation.
3561
transport_path = 'quack'
3562
repo, client = self.setup_fake_client_and_repository(transport_path)
3563
client.add_unknown_method_response('PackRepository.autopack')
3564
def stub_ensure_real():
3565
client._calls.append(('_ensure_real',))
3566
repo._real_repository = _StubRealPackRepository(client._calls)
3567
repo._ensure_real = stub_ensure_real
3570
[('call', 'PackRepository.autopack', ('quack/',)),
3572
('pack collection autopack',)],
3575
def test_oom_error_reporting(self):
3576
"""An out-of-memory condition on the server is reported clearly"""
3577
transport_path = 'quack'
3578
repo, client = self.setup_fake_client_and_repository(transport_path)
3579
client.add_expected_call(
3580
'PackRepository.autopack', ('quack/',),
3581
'error', ('MemoryError',))
3582
err = self.assertRaises(errors.BzrError, repo.autopack)
3583
self.assertContainsRe(str(err), "^remote server out of mem")
3586
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3587
"""Base class for unit tests for bzrlib.remote._translate_error."""
3589
def translateTuple(self, error_tuple, **context):
3590
"""Call _translate_error with an ErrorFromSmartServer built from the
3593
:param error_tuple: A tuple of a smart server response, as would be
3594
passed to an ErrorFromSmartServer.
3595
:kwargs context: context items to call _translate_error with.
3597
:returns: The error raised by _translate_error.
3599
# Raise the ErrorFromSmartServer before passing it as an argument,
3600
# because _translate_error may need to re-raise it with a bare 'raise'
3602
server_error = errors.ErrorFromSmartServer(error_tuple)
3603
translated_error = self.translateErrorFromSmartServer(
3604
server_error, **context)
3605
return translated_error
3607
def translateErrorFromSmartServer(self, error_object, **context):
3608
"""Like translateTuple, but takes an already constructed
3609
ErrorFromSmartServer rather than a tuple.
3613
except errors.ErrorFromSmartServer, server_error:
3614
translated_error = self.assertRaises(
3615
errors.BzrError, remote._translate_error, server_error,
3617
return translated_error
3620
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3621
"""Unit tests for bzrlib.remote._translate_error.
3623
Given an ErrorFromSmartServer (which has an error tuple from a smart
3624
server) and some context, _translate_error raises more specific errors from
3627
This test case covers the cases where _translate_error succeeds in
3628
translating an ErrorFromSmartServer to something better. See
3629
TestErrorTranslationRobustness for other cases.
3632
def test_NoSuchRevision(self):
3633
branch = self.make_branch('')
3635
translated_error = self.translateTuple(
3636
('NoSuchRevision', revid), branch=branch)
3637
expected_error = errors.NoSuchRevision(branch, revid)
3638
self.assertEqual(expected_error, translated_error)
3640
def test_nosuchrevision(self):
3641
repository = self.make_repository('')
3643
translated_error = self.translateTuple(
3644
('nosuchrevision', revid), repository=repository)
3645
expected_error = errors.NoSuchRevision(repository, revid)
3646
self.assertEqual(expected_error, translated_error)
3648
def test_nobranch(self):
3649
bzrdir = self.make_bzrdir('')
3650
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3651
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3652
self.assertEqual(expected_error, translated_error)
3654
def test_nobranch_one_arg(self):
3655
bzrdir = self.make_bzrdir('')
3656
translated_error = self.translateTuple(
3657
('nobranch', 'extra detail'), bzrdir=bzrdir)
3658
expected_error = errors.NotBranchError(
3659
path=bzrdir.root_transport.base,
3660
detail='extra detail')
3661
self.assertEqual(expected_error, translated_error)
3663
def test_norepository(self):
3664
bzrdir = self.make_bzrdir('')
3665
translated_error = self.translateTuple(('norepository',),
3667
expected_error = errors.NoRepositoryPresent(bzrdir)
3668
self.assertEqual(expected_error, translated_error)
3670
def test_LockContention(self):
3671
translated_error = self.translateTuple(('LockContention',))
3672
expected_error = errors.LockContention('(remote lock)')
3673
self.assertEqual(expected_error, translated_error)
3675
def test_UnlockableTransport(self):
3676
bzrdir = self.make_bzrdir('')
3677
translated_error = self.translateTuple(
3678
('UnlockableTransport',), bzrdir=bzrdir)
3679
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3680
self.assertEqual(expected_error, translated_error)
3682
def test_LockFailed(self):
3683
lock = 'str() of a server lock'
3684
why = 'str() of why'
3685
translated_error = self.translateTuple(('LockFailed', lock, why))
3686
expected_error = errors.LockFailed(lock, why)
3687
self.assertEqual(expected_error, translated_error)
3689
def test_TokenMismatch(self):
3690
token = 'a lock token'
3691
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3692
expected_error = errors.TokenMismatch(token, '(remote token)')
3693
self.assertEqual(expected_error, translated_error)
3695
def test_Diverged(self):
3696
branch = self.make_branch('a')
3697
other_branch = self.make_branch('b')
3698
translated_error = self.translateTuple(
3699
('Diverged',), branch=branch, other_branch=other_branch)
3700
expected_error = errors.DivergedBranches(branch, other_branch)
3701
self.assertEqual(expected_error, translated_error)
3703
def test_NotStacked(self):
3704
branch = self.make_branch('')
3705
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3706
expected_error = errors.NotStacked(branch)
3707
self.assertEqual(expected_error, translated_error)
3709
def test_ReadError_no_args(self):
3711
translated_error = self.translateTuple(('ReadError',), path=path)
3712
expected_error = errors.ReadError(path)
3713
self.assertEqual(expected_error, translated_error)
3715
def test_ReadError(self):
3717
translated_error = self.translateTuple(('ReadError', path))
3718
expected_error = errors.ReadError(path)
3719
self.assertEqual(expected_error, translated_error)
3721
def test_IncompatibleRepositories(self):
3722
translated_error = self.translateTuple(('IncompatibleRepositories',
3723
"repo1", "repo2", "details here"))
3724
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3726
self.assertEqual(expected_error, translated_error)
3728
def test_PermissionDenied_no_args(self):
3730
translated_error = self.translateTuple(('PermissionDenied',),
3732
expected_error = errors.PermissionDenied(path)
3733
self.assertEqual(expected_error, translated_error)
3735
def test_PermissionDenied_one_arg(self):
3737
translated_error = self.translateTuple(('PermissionDenied', path))
3738
expected_error = errors.PermissionDenied(path)
3739
self.assertEqual(expected_error, translated_error)
3741
def test_PermissionDenied_one_arg_and_context(self):
3742
"""Given a choice between a path from the local context and a path on
3743
the wire, _translate_error prefers the path from the local context.
3745
local_path = 'local path'
3746
remote_path = 'remote path'
3747
translated_error = self.translateTuple(
3748
('PermissionDenied', remote_path), path=local_path)
3749
expected_error = errors.PermissionDenied(local_path)
3750
self.assertEqual(expected_error, translated_error)
3752
def test_PermissionDenied_two_args(self):
3754
extra = 'a string with extra info'
3755
translated_error = self.translateTuple(
3756
('PermissionDenied', path, extra))
3757
expected_error = errors.PermissionDenied(path, extra)
3758
self.assertEqual(expected_error, translated_error)
3760
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3762
def test_NoSuchFile_context_path(self):
3763
local_path = "local path"
3764
translated_error = self.translateTuple(('ReadError', "remote path"),
3766
expected_error = errors.ReadError(local_path)
3767
self.assertEqual(expected_error, translated_error)
3769
def test_NoSuchFile_without_context(self):
3770
remote_path = "remote path"
3771
translated_error = self.translateTuple(('ReadError', remote_path))
3772
expected_error = errors.ReadError(remote_path)
3773
self.assertEqual(expected_error, translated_error)
3775
def test_ReadOnlyError(self):
3776
translated_error = self.translateTuple(('ReadOnlyError',))
3777
expected_error = errors.TransportNotPossible("readonly transport")
3778
self.assertEqual(expected_error, translated_error)
3780
def test_MemoryError(self):
3781
translated_error = self.translateTuple(('MemoryError',))
3782
self.assertStartsWith(str(translated_error),
3783
"remote server out of memory")
3785
def test_generic_IndexError_no_classname(self):
3786
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3787
translated_error = self.translateErrorFromSmartServer(err)
3788
expected_error = errors.UnknownErrorFromSmartServer(err)
3789
self.assertEqual(expected_error, translated_error)
3791
# GZ 2011-03-02: TODO test generic non-ascii error string
3793
def test_generic_KeyError(self):
3794
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3795
translated_error = self.translateErrorFromSmartServer(err)
3796
expected_error = errors.UnknownErrorFromSmartServer(err)
3797
self.assertEqual(expected_error, translated_error)
3800
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3801
"""Unit tests for bzrlib.remote._translate_error's robustness.
3803
TestErrorTranslationSuccess is for cases where _translate_error can
3804
translate successfully. This class about how _translate_err behaves when
3805
it fails to translate: it re-raises the original error.
3808
def test_unrecognised_server_error(self):
3809
"""If the error code from the server is not recognised, the original
3810
ErrorFromSmartServer is propagated unmodified.
3812
error_tuple = ('An unknown error tuple',)
3813
server_error = errors.ErrorFromSmartServer(error_tuple)
3814
translated_error = self.translateErrorFromSmartServer(server_error)
3815
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3816
self.assertEqual(expected_error, translated_error)
3818
def test_context_missing_a_key(self):
3819
"""In case of a bug in the client, or perhaps an unexpected response
3820
from a server, _translate_error returns the original error tuple from
3821
the server and mutters a warning.
3823
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3824
# in the context dict. So let's give it an empty context dict instead
3825
# to exercise its error recovery.
3827
error_tuple = ('NoSuchRevision', 'revid')
3828
server_error = errors.ErrorFromSmartServer(error_tuple)
3829
translated_error = self.translateErrorFromSmartServer(server_error)
3830
self.assertEqual(server_error, translated_error)
3831
# In addition to re-raising ErrorFromSmartServer, some debug info has
3832
# been muttered to the log file for developer to look at.
3833
self.assertContainsRe(
3835
"Missing key 'branch' in context")
3837
def test_path_missing(self):
3838
"""Some translations (PermissionDenied, ReadError) can determine the
3839
'path' variable from either the wire or the local context. If neither
3840
has it, then an error is raised.
3842
error_tuple = ('ReadError',)
3843
server_error = errors.ErrorFromSmartServer(error_tuple)
3844
translated_error = self.translateErrorFromSmartServer(server_error)
3845
self.assertEqual(server_error, translated_error)
3846
# In addition to re-raising ErrorFromSmartServer, some debug info has
3847
# been muttered to the log file for developer to look at.
3848
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3851
class TestStacking(tests.TestCaseWithTransport):
3852
"""Tests for operations on stacked remote repositories.
3854
The underlying format type must support stacking.
3857
def test_access_stacked_remote(self):
3858
# based on <http://launchpad.net/bugs/261315>
3859
# make a branch stacked on another repository containing an empty
3860
# revision, then open it over hpss - we should be able to see that
3862
base_transport = self.get_transport()
3863
base_builder = self.make_branch_builder('base', format='1.9')
3864
base_builder.start_series()
3865
base_revid = base_builder.build_snapshot('rev-id', None,
3866
[('add', ('', None, 'directory', None))],
3868
base_builder.finish_series()
3869
stacked_branch = self.make_branch('stacked', format='1.9')
3870
stacked_branch.set_stacked_on_url('../base')
3871
# start a server looking at this
3872
smart_server = test_server.SmartTCPServer_for_testing()
3873
self.start_server(smart_server)
3874
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3875
# can get its branch and repository
3876
remote_branch = remote_bzrdir.open_branch()
3877
remote_repo = remote_branch.repository
3878
remote_repo.lock_read()
3880
# it should have an appropriate fallback repository, which should also
3881
# be a RemoteRepository
3882
self.assertLength(1, remote_repo._fallback_repositories)
3883
self.assertIsInstance(remote_repo._fallback_repositories[0],
3885
# and it has the revision committed to the underlying repository;
3886
# these have varying implementations so we try several of them
3887
self.assertTrue(remote_repo.has_revisions([base_revid]))
3888
self.assertTrue(remote_repo.has_revision(base_revid))
3889
self.assertEqual(remote_repo.get_revision(base_revid).message,
3892
remote_repo.unlock()
3894
def prepare_stacked_remote_branch(self):
3895
"""Get stacked_upon and stacked branches with content in each."""
3896
self.setup_smart_server_with_call_log()
3897
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3898
tree1.commit('rev1', rev_id='rev1')
3899
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3900
).open_workingtree()
3901
local_tree = tree2.branch.create_checkout('local')
3902
local_tree.commit('local changes make me feel good.')
3903
branch2 = Branch.open(self.get_url('tree2'))
3905
self.addCleanup(branch2.unlock)
3906
return tree1.branch, branch2
3908
def test_stacked_get_parent_map(self):
3909
# the public implementation of get_parent_map obeys stacking
3910
_, branch = self.prepare_stacked_remote_branch()
3911
repo = branch.repository
3912
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3914
def test_unstacked_get_parent_map(self):
3915
# _unstacked_provider.get_parent_map ignores stacking
3916
_, branch = self.prepare_stacked_remote_branch()
3917
provider = branch.repository._unstacked_provider
3918
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3920
def fetch_stream_to_rev_order(self, stream):
3922
for kind, substream in stream:
3923
if not kind == 'revisions':
3926
for content in substream:
3927
result.append(content.key[-1])
3930
def get_ordered_revs(self, format, order, branch_factory=None):
3931
"""Get a list of the revisions in a stream to format format.
3933
:param format: The format of the target.
3934
:param order: the order that target should have requested.
3935
:param branch_factory: A callable to create a trunk and stacked branch
3936
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3937
:result: The revision ids in the stream, in the order seen,
3938
the topological order of revisions in the source.
3940
unordered_format = bzrdir.format_registry.get(format)()
3941
target_repository_format = unordered_format.repository_format
3943
self.assertEqual(order, target_repository_format._fetch_order)
3944
if branch_factory is None:
3945
branch_factory = self.prepare_stacked_remote_branch
3946
_, stacked = branch_factory()
3947
source = stacked.repository._get_source(target_repository_format)
3948
tip = stacked.last_revision()
3949
stacked.repository._ensure_real()
3950
graph = stacked.repository.get_graph()
3951
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3952
if r != NULL_REVISION]
3954
search = vf_search.PendingAncestryResult([tip], stacked.repository)
3955
self.reset_smart_call_log()
3956
stream = source.get_stream(search)
3957
# We trust that if a revision is in the stream the rest of the new
3958
# content for it is too, as per our main fetch tests; here we are
3959
# checking that the revisions are actually included at all, and their
3961
return self.fetch_stream_to_rev_order(stream), revs
3963
def test_stacked_get_stream_unordered(self):
3964
# Repository._get_source.get_stream() from a stacked repository with
3965
# unordered yields the full data from both stacked and stacked upon
3967
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3968
self.assertEqual(set(expected_revs), set(rev_ord))
3969
# Getting unordered results should have made a streaming data request
3970
# from the server, then one from the backing branch.
3971
self.assertLength(2, self.hpss_calls)
3973
def test_stacked_on_stacked_get_stream_unordered(self):
3974
# Repository._get_source.get_stream() from a stacked repository which
3975
# is itself stacked yields the full data from all three sources.
3976
def make_stacked_stacked():
3977
_, stacked = self.prepare_stacked_remote_branch()
3978
tree = stacked.bzrdir.sprout('tree3', stacked=True
3979
).open_workingtree()
3980
local_tree = tree.branch.create_checkout('local-tree3')
3981
local_tree.commit('more local changes are better')
3982
branch = Branch.open(self.get_url('tree3'))
3984
self.addCleanup(branch.unlock)
3986
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3987
branch_factory=make_stacked_stacked)
3988
self.assertEqual(set(expected_revs), set(rev_ord))
3989
# Getting unordered results should have made a streaming data request
3990
# from the server, and one from each backing repo
3991
self.assertLength(3, self.hpss_calls)
3993
def test_stacked_get_stream_topological(self):
3994
# Repository._get_source.get_stream() from a stacked repository with
3995
# topological sorting yields the full data from both stacked and
3996
# stacked upon sources in topological order.
3997
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3998
self.assertEqual(expected_revs, rev_ord)
3999
# Getting topological sort requires VFS calls still - one of which is
4000
# pushing up from the bound branch.
4001
self.assertLength(14, self.hpss_calls)
4003
def test_stacked_get_stream_groupcompress(self):
4004
# Repository._get_source.get_stream() from a stacked repository with
4005
# groupcompress sorting yields the full data from both stacked and
4006
# stacked upon sources in groupcompress order.
4007
raise tests.TestSkipped('No groupcompress ordered format available')
4008
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4009
self.assertEqual(expected_revs, reversed(rev_ord))
4010
# Getting unordered results should have made a streaming data request
4011
# from the backing branch, and one from the stacked on branch.
4012
self.assertLength(2, self.hpss_calls)
4014
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4015
# When pulling some fixed amount of content that is more than the
4016
# source has (because some is coming from a fallback branch, no error
4017
# should be received. This was reported as bug 360791.
4018
# Need three branches: a trunk, a stacked branch, and a preexisting
4019
# branch pulling content from stacked and trunk.
4020
self.setup_smart_server_with_call_log()
4021
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4022
r1 = trunk.commit('start')
4023
stacked_branch = trunk.branch.create_clone_on_transport(
4024
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4025
local = self.make_branch('local', format='1.9-rich-root')
4026
local.repository.fetch(stacked_branch.repository,
4027
stacked_branch.last_revision())
4030
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4033
super(TestRemoteBranchEffort, self).setUp()
4034
# Create a smart server that publishes whatever the backing VFS server
4036
self.smart_server = test_server.SmartTCPServer_for_testing()
4037
self.start_server(self.smart_server, self.get_server())
4038
# Log all HPSS calls into self.hpss_calls.
4039
_SmartClient.hooks.install_named_hook(
4040
'call', self.capture_hpss_call, None)
4041
self.hpss_calls = []
4043
def capture_hpss_call(self, params):
4044
self.hpss_calls.append(params.method)
4046
def test_copy_content_into_avoids_revision_history(self):
4047
local = self.make_branch('local')
4048
builder = self.make_branch_builder('remote')
4049
builder.build_commit(message="Commit.")
4050
remote_branch_url = self.smart_server.get_url() + 'remote'
4051
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4052
local.repository.fetch(remote_branch.repository)
4053
self.hpss_calls = []
4054
remote_branch.copy_content_into(local)
4055
self.assertFalse('Branch.revision_history' in self.hpss_calls)
4057
def test_fetch_everything_needs_just_one_call(self):
4058
local = self.make_branch('local')
4059
builder = self.make_branch_builder('remote')
4060
builder.build_commit(message="Commit.")
4061
remote_branch_url = self.smart_server.get_url() + 'remote'
4062
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4063
self.hpss_calls = []
4064
local.repository.fetch(
4065
remote_branch.repository,
4066
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4067
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
4069
def override_verb(self, verb_name, verb):
4070
request_handlers = request.request_handlers
4071
orig_verb = request_handlers.get(verb_name)
4072
orig_info = request_handlers.get_info(verb_name)
4073
request_handlers.register(verb_name, verb, override_existing=True)
4074
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4075
override_existing=True, info=orig_info)
4077
def test_fetch_everything_backwards_compat(self):
4078
"""Can fetch with EverythingResult even with pre 2.4 servers.
4080
Pre-2.4 do not support 'everything' searches with the
4081
Repository.get_stream_1.19 verb.
4084
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4085
"""A version of the Repository.get_stream_1.19 verb patched to
4086
reject 'everything' searches the way 2.3 and earlier do.
4088
def recreate_search(self, repository, search_bytes,
4089
discard_excess=False):
4090
verb_log.append(search_bytes.split('\n', 1)[0])
4091
if search_bytes == 'everything':
4093
request.FailedSmartServerResponse(('BadSearch',)))
4094
return super(OldGetStreamVerb,
4095
self).recreate_search(repository, search_bytes,
4096
discard_excess=discard_excess)
4097
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
4098
local = self.make_branch('local')
4099
builder = self.make_branch_builder('remote')
4100
builder.build_commit(message="Commit.")
4101
remote_branch_url = self.smart_server.get_url() + 'remote'
4102
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4103
self.hpss_calls = []
4104
local.repository.fetch(
4105
remote_branch.repository,
4106
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4107
# make sure the overridden verb was used
4108
self.assertLength(1, verb_log)
4109
# more than one HPSS call is needed, but because it's a VFS callback
4110
# its hard to predict exactly how many.
4111
self.assertTrue(len(self.hpss_calls) > 1)
4114
class TestUpdateBoundBranchWithModifiedBoundLocation(
4115
tests.TestCaseWithTransport):
4116
"""Ensure correct handling of bound_location modifications.
4118
This is tested against a smart server as http://pad.lv/786980 was about a
4119
ReadOnlyError (write attempt during a read-only transaction) which can only
4120
happen in this context.
4124
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4125
self.transport_server = test_server.SmartTCPServer_for_testing
4127
def make_master_and_checkout(self, master_name, checkout_name):
4128
# Create the master branch and its associated checkout
4129
self.master = self.make_branch_and_tree(master_name)
4130
self.checkout = self.master.branch.create_checkout(checkout_name)
4131
# Modify the master branch so there is something to update
4132
self.master.commit('add stuff')
4133
self.last_revid = self.master.commit('even more stuff')
4134
self.bound_location = self.checkout.branch.get_bound_location()
4136
def assertUpdateSucceeds(self, new_location):
4137
self.checkout.branch.set_bound_location(new_location)
4138
self.checkout.update()
4139
self.assertEquals(self.last_revid, self.checkout.last_revision())
4141
def test_without_final_slash(self):
4142
self.make_master_and_checkout('master', 'checkout')
4143
# For unclear reasons some users have a bound_location without a final
4144
# '/', simulate that by forcing such a value
4145
self.assertEndsWith(self.bound_location, '/')
4146
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4148
def test_plus_sign(self):
4149
self.make_master_and_checkout('+master', 'checkout')
4150
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4152
def test_tilda(self):
4153
# Embed ~ in the middle of the path just to avoid any $HOME
4155
self.make_master_and_checkout('mas~ter', 'checkout')
4156
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4159
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4161
def test_no_context(self):
4162
class OutOfCoffee(errors.BzrError):
4163
"""A dummy exception for testing."""
4165
def __init__(self, urgency):
4166
self.urgency = urgency
4167
remote.no_context_error_translators.register("OutOfCoffee",
4168
lambda err: OutOfCoffee(err.error_args[0]))
4169
transport = MemoryTransport()
4170
client = FakeClient(transport.base)
4171
client.add_expected_call(
4172
'Branch.get_stacked_on_url', ('quack/',),
4173
'error', ('NotStacked',))
4174
client.add_expected_call(
4175
'Branch.last_revision_info',
4177
'error', ('OutOfCoffee', 'low'))
4178
transport.mkdir('quack')
4179
transport = transport.clone('quack')
4180
branch = self.make_remote_branch(transport, client)
4181
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4182
self.assertFinished(client)
4184
def test_with_context(self):
4185
class OutOfTea(errors.BzrError):
4186
def __init__(self, branch, urgency):
4187
self.branch = branch
4188
self.urgency = urgency
4189
remote.error_translators.register("OutOfTea",
4190
lambda err, find, path: OutOfTea(err.error_args[0],
4192
transport = MemoryTransport()
4193
client = FakeClient(transport.base)
4194
client.add_expected_call(
4195
'Branch.get_stacked_on_url', ('quack/',),
4196
'error', ('NotStacked',))
4197
client.add_expected_call(
4198
'Branch.last_revision_info',
4200
'error', ('OutOfTea', 'low'))
4201
transport.mkdir('quack')
4202
transport = transport.clone('quack')
4203
branch = self.make_remote_branch(transport, client)
4204
self.assertRaises(OutOfTea, branch.last_revision_info)
4205
self.assertFinished(client)
4208
class TestRepositoryPack(TestRemoteRepository):
4210
def test_pack(self):
4211
transport_path = 'quack'
4212
repo, client = self.setup_fake_client_and_repository(transport_path)
4213
client.add_expected_call(
4214
'Repository.lock_write', ('quack/', ''),
4215
'success', ('ok', 'token'))
4216
client.add_expected_call(
4217
'Repository.pack', ('quack/', 'token', 'False'),
4218
'success', ('ok',), )
4219
client.add_expected_call(
4220
'Repository.unlock', ('quack/', 'token'),
4221
'success', ('ok', ))
4224
def test_pack_with_hint(self):
4225
transport_path = 'quack'
4226
repo, client = self.setup_fake_client_and_repository(transport_path)
4227
client.add_expected_call(
4228
'Repository.lock_write', ('quack/', ''),
4229
'success', ('ok', 'token'))
4230
client.add_expected_call(
4231
'Repository.pack', ('quack/', 'token', 'False'),
4232
'success', ('ok',), )
4233
client.add_expected_call(
4234
'Repository.unlock', ('quack/', 'token', 'False'),
4235
'success', ('ok', ))
4236
repo.pack(['hinta', 'hintb'])
4239
class TestRepositoryIterInventories(TestRemoteRepository):
4240
"""Test Repository.iter_inventories."""
4242
def _serialize_inv_delta(self, old_name, new_name, delta):
4243
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4244
return "".join(serializer.delta_to_lines(old_name, new_name, delta))
4246
def test_single_empty(self):
4247
transport_path = 'quack'
4248
repo, client = self.setup_fake_client_and_repository(transport_path)
4249
fmt = bzrdir.format_registry.get('2a')().repository_format
4251
stream = [('inventory-deltas', [
4252
versionedfile.FulltextContentFactory('somerevid', None, None,
4253
self._serialize_inv_delta('null:', 'somerevid', []))])]
4254
client.add_expected_call(
4255
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4256
'success', ('ok', ),
4257
_stream_to_byte_stream(stream, fmt))
4258
ret = list(repo.iter_inventories(["somerevid"]))
4259
self.assertLength(1, ret)
4261
self.assertEquals("somerevid", inv.revision_id)
4263
def test_empty(self):
4264
transport_path = 'quack'
4265
repo, client = self.setup_fake_client_and_repository(transport_path)
4266
ret = list(repo.iter_inventories([]))
4267
self.assertEquals(ret, [])
4269
def test_missing(self):
4270
transport_path = 'quack'
4271
repo, client = self.setup_fake_client_and_repository(transport_path)
4272
client.add_expected_call(
4273
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4274
'success', ('ok', ), iter([]))
4275
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(