102
146
b = BzrDir.open_from_transport(self.transport).open_branch()
103
147
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)
106
193
class FakeProtocol(object):
107
194
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
109
def __init__(self, body):
110
self._body_buffer = StringIO(body)
196
def __init__(self, body, fake_client):
198
self._body_buffer = None
199
self._fake_client = fake_client
112
201
def read_body_bytes(self, count=-1):
113
return self._body_buffer.read(count)
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):
116
216
class FakeClient(_SmartClient):
117
217
"""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.
123
:param respones: A list of response-tuple, body-data pairs to be sent
126
self.responses = responses
219
def __init__(self, fake_medium_base='fake base'):
220
"""Create a FakeClient."""
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,))
129
286
def call(self, method, *args):
287
self._check_call(method, args)
130
288
self._calls.append(('call', method, args))
131
return self.responses.pop(0)[0]
289
return self._get_next_response()[1]
133
291
def call_expecting_body(self, method, *args):
292
self._check_call(method, args)
134
293
self._calls.append(('call_expecting_body', method, args))
135
result = self.responses.pop(0)
136
return result[0], FakeProtocol(result[1])
139
class TestBzrDirOpenBranch(tests.TestCase):
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 TestBzrDirGetBranches(TestRemote):
546
def test_get_branches(self):
547
transport = MemoryTransport()
548
client = FakeClient(transport.base)
549
reference_bzrdir_format = bzrdir.format_registry.get('default')()
550
branch_name = reference_bzrdir_format.get_branch_format().network_name()
551
client.add_success_response_with_body(
553
"foo": ("branch", branch_name),
554
"": ("branch", branch_name)}), "success")
555
client.add_success_response(
556
'ok', '', 'no', 'no', 'no',
557
reference_bzrdir_format.repository_format.network_name())
558
client.add_error_response('NotStacked')
559
client.add_success_response(
560
'ok', '', 'no', 'no', 'no',
561
reference_bzrdir_format.repository_format.network_name())
562
client.add_error_response('NotStacked')
563
transport.mkdir('quack')
564
transport = transport.clone('quack')
565
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
567
result = a_bzrdir.get_branches()
568
self.assertEquals(set(["", "foo"]), set(result.keys()))
570
[('call_expecting_body', 'BzrDir.get_branches', ('quack/',)),
571
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
572
('call', 'Branch.get_stacked_on_url', ('quack/', )),
573
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
574
('call', 'Branch.get_stacked_on_url', ('quack/', ))],
578
class TestBzrDirDestroyBranch(TestRemote):
580
def test_destroy_default(self):
581
transport = self.get_transport('quack')
582
referenced = self.make_branch('referenced')
583
client = FakeClient(transport.base)
584
client.add_expected_call(
585
'BzrDir.destroy_branch', ('quack/', ),
587
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
589
a_bzrdir.destroy_branch()
590
self.assertFinished(client)
593
class TestBzrDirHasWorkingTree(TestRemote):
595
def test_has_workingtree(self):
596
transport = self.get_transport('quack')
597
client = FakeClient(transport.base)
598
client.add_expected_call(
599
'BzrDir.has_workingtree', ('quack/',),
600
'success', ('yes',)),
601
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
603
self.assertTrue(a_bzrdir.has_workingtree())
604
self.assertFinished(client)
606
def test_no_workingtree(self):
607
transport = self.get_transport('quack')
608
client = FakeClient(transport.base)
609
client.add_expected_call(
610
'BzrDir.has_workingtree', ('quack/',),
612
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
614
self.assertFalse(a_bzrdir.has_workingtree())
615
self.assertFinished(client)
618
class TestBzrDirDestroyRepository(TestRemote):
620
def test_destroy_repository(self):
621
transport = self.get_transport('quack')
622
client = FakeClient(transport.base)
623
client.add_expected_call(
624
'BzrDir.destroy_repository', ('quack/',),
626
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
628
a_bzrdir.destroy_repository()
629
self.assertFinished(client)
632
class TestBzrDirOpen(TestRemote):
634
def make_fake_client_and_transport(self, path='quack'):
635
transport = MemoryTransport()
636
transport.mkdir(path)
637
transport = transport.clone(path)
638
client = FakeClient(transport.base)
639
return client, transport
641
def test_absent(self):
642
client, transport = self.make_fake_client_and_transport()
643
client.add_expected_call(
644
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
645
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
646
RemoteBzrDirFormat(), _client=client, _force_probe=True)
647
self.assertFinished(client)
649
def test_present_without_workingtree(self):
650
client, transport = self.make_fake_client_and_transport()
651
client.add_expected_call(
652
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
653
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
654
_client=client, _force_probe=True)
655
self.assertIsInstance(bd, RemoteBzrDir)
656
self.assertFalse(bd.has_workingtree())
657
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
658
self.assertFinished(client)
660
def test_present_with_workingtree(self):
661
client, transport = self.make_fake_client_and_transport()
662
client.add_expected_call(
663
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
664
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
665
_client=client, _force_probe=True)
666
self.assertIsInstance(bd, RemoteBzrDir)
667
self.assertTrue(bd.has_workingtree())
668
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
669
self.assertFinished(client)
671
def test_backwards_compat(self):
672
client, transport = self.make_fake_client_and_transport()
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)
682
def test_backwards_compat_hpss_v2(self):
683
client, transport = self.make_fake_client_and_transport()
684
# Monkey-patch fake client to simulate real-world behaviour with v2
685
# server: upon first RPC call detect the protocol version, and because
686
# the version is 2 also do _remember_remote_is_before((1, 6)) before
687
# continuing with the RPC.
688
orig_check_call = client._check_call
689
def check_call(method, args):
690
client._medium._protocol_version = 2
691
client._medium._remember_remote_is_before((1, 6))
692
client._check_call = orig_check_call
693
client._check_call(method, args)
694
client._check_call = check_call
695
client.add_expected_call(
696
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
697
client.add_expected_call(
698
'BzrDir.open', ('quack/',), 'success', ('yes',))
699
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
700
_client=client, _force_probe=True)
701
self.assertIsInstance(bd, RemoteBzrDir)
702
self.assertFinished(client)
705
class TestBzrDirOpenBranch(TestRemote):
707
def test_backwards_compat(self):
708
self.setup_smart_server_with_call_log()
709
self.make_branch('.')
710
a_dir = BzrDir.open(self.get_url('.'))
711
self.reset_smart_call_log()
712
verb = 'BzrDir.open_branchV3'
713
self.disable_verb(verb)
714
format = a_dir.open_branch()
715
call_count = len([call for call in self.hpss_calls if
716
call.call.method == verb])
717
self.assertEqual(1, call_count)
141
719
def test_branch_present(self):
142
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )])
720
reference_format = self.get_repo_format()
721
network_name = reference_format.network_name()
722
branch_network_name = self.get_branch_format().network_name()
143
723
transport = MemoryTransport()
144
724
transport.mkdir('quack')
145
725
transport = transport.clone('quack')
146
bzrdir = RemoteBzrDir(transport, _client=client)
726
client = FakeClient(transport.base)
727
client.add_expected_call(
728
'BzrDir.open_branchV3', ('quack/',),
729
'success', ('branch', branch_network_name))
730
client.add_expected_call(
731
'BzrDir.find_repositoryV3', ('quack/',),
732
'success', ('ok', '', 'no', 'no', 'no', network_name))
733
client.add_expected_call(
734
'Branch.get_stacked_on_url', ('quack/',),
735
'error', ('NotStacked',))
736
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
147
738
result = bzrdir.open_branch()
149
[('call', 'BzrDir.open_branch', ('///quack/',)),
150
('call', 'BzrDir.find_repository', ('///quack/',))],
152
739
self.assertIsInstance(result, RemoteBranch)
153
740
self.assertEqual(bzrdir, result.bzrdir)
741
self.assertFinished(client)
155
743
def test_branch_missing(self):
156
client = FakeClient([(('nobranch',), )])
157
744
transport = MemoryTransport()
158
745
transport.mkdir('quack')
159
746
transport = transport.clone('quack')
160
bzrdir = RemoteBzrDir(transport, _client=client)
747
client = FakeClient(transport.base)
748
client.add_error_response('nobranch')
749
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
161
751
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
162
752
self.assertEqual(
163
[('call', 'BzrDir.open_branch', ('///quack/',))],
753
[('call', 'BzrDir.open_branchV3', ('quack/',))],
166
def check_open_repository(self, rich_root, subtrees):
756
def test__get_tree_branch(self):
757
# _get_tree_branch is a form of open_branch, but it should only ask for
758
# branch opening, not any other network requests.
760
def open_branch(name=None, possible_transports=None):
761
calls.append("Called")
763
transport = MemoryTransport()
764
# no requests on the network - catches other api calls being made.
765
client = FakeClient(transport.base)
766
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
768
# patch the open_branch call to record that it was called.
769
bzrdir.open_branch = open_branch
770
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
771
self.assertEqual(["Called"], calls)
772
self.assertEqual([], client._calls)
774
def test_url_quoting_of_path(self):
775
# Relpaths on the wire should not be URL-escaped. So "~" should be
776
# transmitted as "~", not "%7E".
777
transport = RemoteTCPTransport('bzr://localhost/~hello/')
778
client = FakeClient(transport.base)
779
reference_format = self.get_repo_format()
780
network_name = reference_format.network_name()
781
branch_network_name = self.get_branch_format().network_name()
782
client.add_expected_call(
783
'BzrDir.open_branchV3', ('~hello/',),
784
'success', ('branch', branch_network_name))
785
client.add_expected_call(
786
'BzrDir.find_repositoryV3', ('~hello/',),
787
'success', ('ok', '', 'no', 'no', 'no', network_name))
788
client.add_expected_call(
789
'Branch.get_stacked_on_url', ('~hello/',),
790
'error', ('NotStacked',))
791
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
793
result = bzrdir.open_branch()
794
self.assertFinished(client)
796
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
797
reference_format = self.get_repo_format()
798
network_name = reference_format.network_name()
799
transport = MemoryTransport()
800
transport.mkdir('quack')
801
transport = transport.clone('quack')
168
803
rich_response = 'yes'
191
827
self.check_open_repository(False, True)
192
828
self.check_open_repository(True, False)
193
829
self.check_open_repository(False, False)
830
self.check_open_repository(False, False, 'yes')
195
832
def test_old_server(self):
196
833
"""RemoteBzrDirFormat should fail to probe if the server version is too
199
836
self.assertRaises(errors.NotBranchError,
200
RemoteBzrDirFormat.probe_transport, OldServerTransport())
837
RemoteBzrProber.probe_transport, OldServerTransport())
840
class TestBzrDirCreateBranch(TestRemote):
842
def test_backwards_compat(self):
843
self.setup_smart_server_with_call_log()
844
repo = self.make_repository('.')
845
self.reset_smart_call_log()
846
self.disable_verb('BzrDir.create_branch')
847
branch = repo.bzrdir.create_branch()
848
create_branch_call_count = len([call for call in self.hpss_calls if
849
call.call.method == 'BzrDir.create_branch'])
850
self.assertEqual(1, create_branch_call_count)
852
def test_current_server(self):
853
transport = self.get_transport('.')
854
transport = transport.clone('quack')
855
self.make_repository('quack')
856
client = FakeClient(transport.base)
857
reference_bzrdir_format = bzrdir.format_registry.get('default')()
858
reference_format = reference_bzrdir_format.get_branch_format()
859
network_name = reference_format.network_name()
860
reference_repo_fmt = reference_bzrdir_format.repository_format
861
reference_repo_name = reference_repo_fmt.network_name()
862
client.add_expected_call(
863
'BzrDir.create_branch', ('quack/', network_name),
864
'success', ('ok', network_name, '', 'no', 'no', 'yes',
865
reference_repo_name))
866
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
868
branch = a_bzrdir.create_branch()
869
# We should have got a remote branch
870
self.assertIsInstance(branch, remote.RemoteBranch)
871
# its format should have the settings from the response
872
format = branch._format
873
self.assertEqual(network_name, format.network_name())
875
def test_already_open_repo_and_reused_medium(self):
876
"""Bug 726584: create_branch(..., repository=repo) should work
877
regardless of what the smart medium's base URL is.
879
self.transport_server = test_server.SmartTCPServer_for_testing
880
transport = self.get_transport('.')
881
repo = self.make_repository('quack')
882
# Client's medium rooted a transport root (not at the bzrdir)
883
client = FakeClient(transport.base)
884
transport = transport.clone('quack')
885
reference_bzrdir_format = bzrdir.format_registry.get('default')()
886
reference_format = reference_bzrdir_format.get_branch_format()
887
network_name = reference_format.network_name()
888
reference_repo_fmt = reference_bzrdir_format.repository_format
889
reference_repo_name = reference_repo_fmt.network_name()
890
client.add_expected_call(
891
'BzrDir.create_branch', ('extra/quack/', network_name),
892
'success', ('ok', network_name, '', 'no', 'no', 'yes',
893
reference_repo_name))
894
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
896
branch = a_bzrdir.create_branch(repository=repo)
897
# We should have got a remote branch
898
self.assertIsInstance(branch, remote.RemoteBranch)
899
# its format should have the settings from the response
900
format = branch._format
901
self.assertEqual(network_name, format.network_name())
904
class TestBzrDirCreateRepository(TestRemote):
906
def test_backwards_compat(self):
907
self.setup_smart_server_with_call_log()
908
bzrdir = self.make_bzrdir('.')
909
self.reset_smart_call_log()
910
self.disable_verb('BzrDir.create_repository')
911
repo = bzrdir.create_repository()
912
create_repo_call_count = len([call for call in self.hpss_calls if
913
call.call.method == 'BzrDir.create_repository'])
914
self.assertEqual(1, create_repo_call_count)
916
def test_current_server(self):
917
transport = self.get_transport('.')
918
transport = transport.clone('quack')
919
self.make_bzrdir('quack')
920
client = FakeClient(transport.base)
921
reference_bzrdir_format = bzrdir.format_registry.get('default')()
922
reference_format = reference_bzrdir_format.repository_format
923
network_name = reference_format.network_name()
924
client.add_expected_call(
925
'BzrDir.create_repository', ('quack/',
926
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
928
'success', ('ok', 'yes', 'yes', 'yes', network_name))
929
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
931
repo = a_bzrdir.create_repository()
932
# We should have got a remote repository
933
self.assertIsInstance(repo, remote.RemoteRepository)
934
# its format should have the settings from the response
935
format = repo._format
936
self.assertTrue(format.rich_root_data)
937
self.assertTrue(format.supports_tree_reference)
938
self.assertTrue(format.supports_external_lookups)
939
self.assertEqual(network_name, format.network_name())
942
class TestBzrDirOpenRepository(TestRemote):
944
def test_backwards_compat_1_2_3(self):
945
# fallback all the way to the first version.
946
reference_format = self.get_repo_format()
947
network_name = reference_format.network_name()
948
server_url = 'bzr://example.com/'
949
self.permit_url(server_url)
950
client = FakeClient(server_url)
951
client.add_unknown_method_response('BzrDir.find_repositoryV3')
952
client.add_unknown_method_response('BzrDir.find_repositoryV2')
953
client.add_success_response('ok', '', 'no', 'no')
954
# A real repository instance will be created to determine the network
956
client.add_success_response_with_body(
957
"Bazaar-NG meta directory, format 1\n", 'ok')
958
client.add_success_response('stat', '0', '65535')
959
client.add_success_response_with_body(
960
reference_format.get_format_string(), 'ok')
961
# PackRepository wants to do a stat
962
client.add_success_response('stat', '0', '65535')
963
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
965
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
967
repo = bzrdir.open_repository()
969
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
970
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
971
('call', 'BzrDir.find_repository', ('quack/',)),
972
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
973
('call', 'stat', ('/quack/.bzr',)),
974
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
975
('call', 'stat', ('/quack/.bzr/repository',)),
978
self.assertEqual(network_name, repo._format.network_name())
980
def test_backwards_compat_2(self):
981
# fallback to find_repositoryV2
982
reference_format = self.get_repo_format()
983
network_name = reference_format.network_name()
984
server_url = 'bzr://example.com/'
985
self.permit_url(server_url)
986
client = FakeClient(server_url)
987
client.add_unknown_method_response('BzrDir.find_repositoryV3')
988
client.add_success_response('ok', '', 'no', 'no', 'no')
989
# A real repository instance will be created to determine the network
991
client.add_success_response_with_body(
992
"Bazaar-NG meta directory, format 1\n", 'ok')
993
client.add_success_response('stat', '0', '65535')
994
client.add_success_response_with_body(
995
reference_format.get_format_string(), 'ok')
996
# PackRepository wants to do a stat
997
client.add_success_response('stat', '0', '65535')
998
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
1000
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
1002
repo = bzrdir.open_repository()
1004
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
1005
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
1006
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
1007
('call', 'stat', ('/quack/.bzr',)),
1008
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
1009
('call', 'stat', ('/quack/.bzr/repository',)),
1012
self.assertEqual(network_name, repo._format.network_name())
1014
def test_current_server(self):
1015
reference_format = self.get_repo_format()
1016
network_name = reference_format.network_name()
1017
transport = MemoryTransport()
1018
transport.mkdir('quack')
1019
transport = transport.clone('quack')
1020
client = FakeClient(transport.base)
1021
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
1022
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1024
repo = bzrdir.open_repository()
1026
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
1028
self.assertEqual(network_name, repo._format.network_name())
1031
class TestBzrDirFormatInitializeEx(TestRemote):
1033
def test_success(self):
1034
"""Simple test for typical successful call."""
1035
fmt = RemoteBzrDirFormat()
1036
default_format_name = BzrDirFormat.get_default_format().network_name()
1037
transport = self.get_transport()
1038
client = FakeClient(transport.base)
1039
client.add_expected_call(
1040
'BzrDirFormat.initialize_ex_1.16',
1041
(default_format_name, 'path', 'False', 'False', 'False', '',
1042
'', '', '', 'False'),
1044
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
1045
'bzrdir fmt', 'False', '', '', 'repo lock token'))
1046
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1047
# it's currently hard to test that without supplying a real remote
1048
# transport connected to a real server.
1049
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
1050
transport, False, False, False, None, None, None, None, False)
1051
self.assertFinished(client)
1053
def test_error(self):
1054
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1055
corresponding error from the client.
1057
fmt = RemoteBzrDirFormat()
1058
default_format_name = BzrDirFormat.get_default_format().network_name()
1059
transport = self.get_transport()
1060
client = FakeClient(transport.base)
1061
client.add_expected_call(
1062
'BzrDirFormat.initialize_ex_1.16',
1063
(default_format_name, 'path', 'False', 'False', 'False', '',
1064
'', '', '', 'False'),
1066
('PermissionDenied', 'path', 'extra info'))
1067
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1068
# it's currently hard to test that without supplying a real remote
1069
# transport connected to a real server.
1070
err = self.assertRaises(errors.PermissionDenied,
1071
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1072
False, False, False, None, None, None, None, False)
1073
self.assertEqual('path', err.path)
1074
self.assertEqual(': extra info', err.extra)
1075
self.assertFinished(client)
1077
def test_error_from_real_server(self):
1078
"""Integration test for error translation."""
1079
transport = self.make_smart_server('foo')
1080
transport = transport.clone('no-such-path')
1081
fmt = RemoteBzrDirFormat()
1082
err = self.assertRaises(errors.NoSuchFile,
1083
fmt.initialize_on_transport_ex, transport, create_prefix=False)
203
1086
class OldSmartClient(object):
225
1111
return OldSmartClient()
228
class TestBranchLastRevisionInfo(tests.TestCase):
1114
class RemoteBzrDirTestCase(TestRemote):
1116
def make_remote_bzrdir(self, transport, client):
1117
"""Make a RemotebzrDir using 'client' as the _client."""
1118
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1122
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1124
def lock_remote_branch(self, branch):
1125
"""Trick a RemoteBranch into thinking it is locked."""
1126
branch._lock_mode = 'w'
1127
branch._lock_count = 2
1128
branch._lock_token = 'branch token'
1129
branch._repo_lock_token = 'repo token'
1130
branch.repository._lock_mode = 'w'
1131
branch.repository._lock_count = 2
1132
branch.repository._lock_token = 'repo token'
1134
def make_remote_branch(self, transport, client):
1135
"""Make a RemoteBranch using 'client' as its _SmartClient.
1137
A RemoteBzrDir and RemoteRepository will also be created to fill out
1138
the RemoteBranch, albeit with stub values for some of their attributes.
1140
# we do not want bzrdir to make any remote calls, so use False as its
1141
# _client. If it tries to make a remote call, this will fail
1143
bzrdir = self.make_remote_bzrdir(transport, False)
1144
repo = RemoteRepository(bzrdir, None, _client=client)
1145
branch_format = self.get_branch_format()
1146
format = RemoteBranchFormat(network_name=branch_format.network_name())
1147
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1150
class TestBranchBreakLock(RemoteBranchTestCase):
1152
def test_break_lock(self):
1153
transport_path = 'quack'
1154
transport = MemoryTransport()
1155
client = FakeClient(transport.base)
1156
client.add_expected_call(
1157
'Branch.get_stacked_on_url', ('quack/',),
1158
'error', ('NotStacked',))
1159
client.add_expected_call(
1160
'Branch.break_lock', ('quack/',),
1162
transport.mkdir('quack')
1163
transport = transport.clone('quack')
1164
branch = self.make_remote_branch(transport, client)
1166
self.assertFinished(client)
1169
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1171
def test_get_physical_lock_status_yes(self):
1172
transport = MemoryTransport()
1173
client = FakeClient(transport.base)
1174
client.add_expected_call(
1175
'Branch.get_stacked_on_url', ('quack/',),
1176
'error', ('NotStacked',))
1177
client.add_expected_call(
1178
'Branch.get_physical_lock_status', ('quack/',),
1179
'success', ('yes',))
1180
transport.mkdir('quack')
1181
transport = transport.clone('quack')
1182
branch = self.make_remote_branch(transport, client)
1183
result = branch.get_physical_lock_status()
1184
self.assertFinished(client)
1185
self.assertEqual(True, result)
1187
def test_get_physical_lock_status_no(self):
1188
transport = MemoryTransport()
1189
client = FakeClient(transport.base)
1190
client.add_expected_call(
1191
'Branch.get_stacked_on_url', ('quack/',),
1192
'error', ('NotStacked',))
1193
client.add_expected_call(
1194
'Branch.get_physical_lock_status', ('quack/',),
1196
transport.mkdir('quack')
1197
transport = transport.clone('quack')
1198
branch = self.make_remote_branch(transport, client)
1199
result = branch.get_physical_lock_status()
1200
self.assertFinished(client)
1201
self.assertEqual(False, result)
1204
class TestBranchGetParent(RemoteBranchTestCase):
1206
def test_no_parent(self):
1207
# in an empty branch we decode the response properly
1208
transport = MemoryTransport()
1209
client = FakeClient(transport.base)
1210
client.add_expected_call(
1211
'Branch.get_stacked_on_url', ('quack/',),
1212
'error', ('NotStacked',))
1213
client.add_expected_call(
1214
'Branch.get_parent', ('quack/',),
1216
transport.mkdir('quack')
1217
transport = transport.clone('quack')
1218
branch = self.make_remote_branch(transport, client)
1219
result = branch.get_parent()
1220
self.assertFinished(client)
1221
self.assertEqual(None, result)
1223
def test_parent_relative(self):
1224
transport = MemoryTransport()
1225
client = FakeClient(transport.base)
1226
client.add_expected_call(
1227
'Branch.get_stacked_on_url', ('kwaak/',),
1228
'error', ('NotStacked',))
1229
client.add_expected_call(
1230
'Branch.get_parent', ('kwaak/',),
1231
'success', ('../foo/',))
1232
transport.mkdir('kwaak')
1233
transport = transport.clone('kwaak')
1234
branch = self.make_remote_branch(transport, client)
1235
result = branch.get_parent()
1236
self.assertEqual(transport.clone('../foo').base, result)
1238
def test_parent_absolute(self):
1239
transport = MemoryTransport()
1240
client = FakeClient(transport.base)
1241
client.add_expected_call(
1242
'Branch.get_stacked_on_url', ('kwaak/',),
1243
'error', ('NotStacked',))
1244
client.add_expected_call(
1245
'Branch.get_parent', ('kwaak/',),
1246
'success', ('http://foo/',))
1247
transport.mkdir('kwaak')
1248
transport = transport.clone('kwaak')
1249
branch = self.make_remote_branch(transport, client)
1250
result = branch.get_parent()
1251
self.assertEqual('http://foo/', result)
1252
self.assertFinished(client)
1255
class TestBranchSetParentLocation(RemoteBranchTestCase):
1257
def test_no_parent(self):
1258
# We call the verb when setting parent to None
1259
transport = MemoryTransport()
1260
client = FakeClient(transport.base)
1261
client.add_expected_call(
1262
'Branch.get_stacked_on_url', ('quack/',),
1263
'error', ('NotStacked',))
1264
client.add_expected_call(
1265
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1267
transport.mkdir('quack')
1268
transport = transport.clone('quack')
1269
branch = self.make_remote_branch(transport, client)
1270
branch._lock_token = 'b'
1271
branch._repo_lock_token = 'r'
1272
branch._set_parent_location(None)
1273
self.assertFinished(client)
1275
def test_parent(self):
1276
transport = MemoryTransport()
1277
client = FakeClient(transport.base)
1278
client.add_expected_call(
1279
'Branch.get_stacked_on_url', ('kwaak/',),
1280
'error', ('NotStacked',))
1281
client.add_expected_call(
1282
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1284
transport.mkdir('kwaak')
1285
transport = transport.clone('kwaak')
1286
branch = self.make_remote_branch(transport, client)
1287
branch._lock_token = 'b'
1288
branch._repo_lock_token = 'r'
1289
branch._set_parent_location('foo')
1290
self.assertFinished(client)
1292
def test_backwards_compat(self):
1293
self.setup_smart_server_with_call_log()
1294
branch = self.make_branch('.')
1295
self.reset_smart_call_log()
1296
verb = 'Branch.set_parent_location'
1297
self.disable_verb(verb)
1298
branch.set_parent('http://foo/')
1299
self.assertLength(14, self.hpss_calls)
1302
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1304
def test_backwards_compat(self):
1305
self.setup_smart_server_with_call_log()
1306
branch = self.make_branch('.')
1307
self.reset_smart_call_log()
1308
verb = 'Branch.get_tags_bytes'
1309
self.disable_verb(verb)
1310
branch.tags.get_tag_dict()
1311
call_count = len([call for call in self.hpss_calls if
1312
call.call.method == verb])
1313
self.assertEqual(1, call_count)
1315
def test_trivial(self):
1316
transport = MemoryTransport()
1317
client = FakeClient(transport.base)
1318
client.add_expected_call(
1319
'Branch.get_stacked_on_url', ('quack/',),
1320
'error', ('NotStacked',))
1321
client.add_expected_call(
1322
'Branch.get_tags_bytes', ('quack/',),
1324
transport.mkdir('quack')
1325
transport = transport.clone('quack')
1326
branch = self.make_remote_branch(transport, client)
1327
result = branch.tags.get_tag_dict()
1328
self.assertFinished(client)
1329
self.assertEqual({}, result)
1332
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1334
def test_trivial(self):
1335
transport = MemoryTransport()
1336
client = FakeClient(transport.base)
1337
client.add_expected_call(
1338
'Branch.get_stacked_on_url', ('quack/',),
1339
'error', ('NotStacked',))
1340
client.add_expected_call(
1341
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1343
transport.mkdir('quack')
1344
transport = transport.clone('quack')
1345
branch = self.make_remote_branch(transport, client)
1346
self.lock_remote_branch(branch)
1347
branch._set_tags_bytes('tags bytes')
1348
self.assertFinished(client)
1349
self.assertEqual('tags bytes', client._calls[-1][-1])
1351
def test_backwards_compatible(self):
1352
transport = MemoryTransport()
1353
client = FakeClient(transport.base)
1354
client.add_expected_call(
1355
'Branch.get_stacked_on_url', ('quack/',),
1356
'error', ('NotStacked',))
1357
client.add_expected_call(
1358
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1359
'unknown', ('Branch.set_tags_bytes',))
1360
transport.mkdir('quack')
1361
transport = transport.clone('quack')
1362
branch = self.make_remote_branch(transport, client)
1363
self.lock_remote_branch(branch)
1364
class StubRealBranch(object):
1367
def _set_tags_bytes(self, bytes):
1368
self.calls.append(('set_tags_bytes', bytes))
1369
real_branch = StubRealBranch()
1370
branch._real_branch = real_branch
1371
branch._set_tags_bytes('tags bytes')
1372
# Call a second time, to exercise the 'remote version already inferred'
1374
branch._set_tags_bytes('tags bytes')
1375
self.assertFinished(client)
1377
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1380
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1382
def test_uses_last_revision_info_and_tags_by_default(self):
1383
transport = MemoryTransport()
1384
client = FakeClient(transport.base)
1385
client.add_expected_call(
1386
'Branch.get_stacked_on_url', ('quack/',),
1387
'error', ('NotStacked',))
1388
client.add_expected_call(
1389
'Branch.last_revision_info', ('quack/',),
1390
'success', ('ok', '1', 'rev-tip'))
1391
client.add_expected_call(
1392
'Branch.get_config_file', ('quack/',),
1393
'success', ('ok',), '')
1394
transport.mkdir('quack')
1395
transport = transport.clone('quack')
1396
branch = self.make_remote_branch(transport, client)
1397
result = branch.heads_to_fetch()
1398
self.assertFinished(client)
1399
self.assertEqual((set(['rev-tip']), set()), result)
1401
def test_uses_last_revision_info_and_tags_when_set(self):
1402
transport = MemoryTransport()
1403
client = FakeClient(transport.base)
1404
client.add_expected_call(
1405
'Branch.get_stacked_on_url', ('quack/',),
1406
'error', ('NotStacked',))
1407
client.add_expected_call(
1408
'Branch.last_revision_info', ('quack/',),
1409
'success', ('ok', '1', 'rev-tip'))
1410
client.add_expected_call(
1411
'Branch.get_config_file', ('quack/',),
1412
'success', ('ok',), 'branch.fetch_tags = True')
1413
# XXX: this will break if the default format's serialization of tags
1414
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1415
client.add_expected_call(
1416
'Branch.get_tags_bytes', ('quack/',),
1417
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1418
transport.mkdir('quack')
1419
transport = transport.clone('quack')
1420
branch = self.make_remote_branch(transport, client)
1421
result = branch.heads_to_fetch()
1422
self.assertFinished(client)
1424
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1426
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1427
transport = MemoryTransport()
1428
client = FakeClient(transport.base)
1429
client.add_expected_call(
1430
'Branch.get_stacked_on_url', ('quack/',),
1431
'error', ('NotStacked',))
1432
client.add_expected_call(
1433
'Branch.heads_to_fetch', ('quack/',),
1434
'success', (['tip'], ['tagged-1', 'tagged-2']))
1435
transport.mkdir('quack')
1436
transport = transport.clone('quack')
1437
branch = self.make_remote_branch(transport, client)
1438
branch._format._use_default_local_heads_to_fetch = lambda: False
1439
result = branch.heads_to_fetch()
1440
self.assertFinished(client)
1441
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1443
def make_branch_with_tags(self):
1444
self.setup_smart_server_with_call_log()
1445
# Make a branch with a single revision.
1446
builder = self.make_branch_builder('foo')
1447
builder.start_series()
1448
builder.build_snapshot('tip', None, [
1449
('add', ('', 'root-id', 'directory', ''))])
1450
builder.finish_series()
1451
branch = builder.get_branch()
1452
# Add two tags to that branch
1453
branch.tags.set_tag('tag-1', 'rev-1')
1454
branch.tags.set_tag('tag-2', 'rev-2')
1457
def test_backwards_compatible(self):
1458
br = self.make_branch_with_tags()
1459
br.get_config_stack().set('branch.fetch_tags', True)
1460
self.addCleanup(br.lock_read().unlock)
1461
# Disable the heads_to_fetch verb
1462
verb = 'Branch.heads_to_fetch'
1463
self.disable_verb(verb)
1464
self.reset_smart_call_log()
1465
result = br.heads_to_fetch()
1466
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1468
['Branch.last_revision_info', 'Branch.get_tags_bytes'],
1469
[call.call.method for call in self.hpss_calls])
1471
def test_backwards_compatible_no_tags(self):
1472
br = self.make_branch_with_tags()
1473
br.get_config_stack().set('branch.fetch_tags', False)
1474
self.addCleanup(br.lock_read().unlock)
1475
# Disable the heads_to_fetch verb
1476
verb = 'Branch.heads_to_fetch'
1477
self.disable_verb(verb)
1478
self.reset_smart_call_log()
1479
result = br.heads_to_fetch()
1480
self.assertEqual((set(['tip']), set()), result)
1482
['Branch.last_revision_info'],
1483
[call.call.method for call in self.hpss_calls])
1486
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
230
1488
def test_empty_branch(self):
231
1489
# in an empty branch we decode the response properly
232
client = FakeClient([(('ok', '0', 'null:'), )])
233
1490
transport = MemoryTransport()
1491
client = FakeClient(transport.base)
1492
client.add_expected_call(
1493
'Branch.get_stacked_on_url', ('quack/',),
1494
'error', ('NotStacked',))
1495
client.add_expected_call(
1496
'Branch.last_revision_info', ('quack/',),
1497
'success', ('ok', '0', 'null:'))
234
1498
transport.mkdir('quack')
235
1499
transport = transport.clone('quack')
236
# we do not want bzrdir to make any remote calls
237
bzrdir = RemoteBzrDir(transport, _client=False)
238
branch = RemoteBranch(bzrdir, None, _client=client)
1500
branch = self.make_remote_branch(transport, client)
239
1501
result = branch.last_revision_info()
242
[('call', 'Branch.last_revision_info', ('///quack/',))],
1502
self.assertFinished(client)
244
1503
self.assertEqual((0, NULL_REVISION), result)
246
1505
def test_non_empty_branch(self):
247
1506
# in a non-empty branch we also decode the response properly
248
1507
revid = u'\xc8'.encode('utf8')
249
client = FakeClient([(('ok', '2', revid), )])
250
1508
transport = MemoryTransport()
1509
client = FakeClient(transport.base)
1510
client.add_expected_call(
1511
'Branch.get_stacked_on_url', ('kwaak/',),
1512
'error', ('NotStacked',))
1513
client.add_expected_call(
1514
'Branch.last_revision_info', ('kwaak/',),
1515
'success', ('ok', '2', revid))
251
1516
transport.mkdir('kwaak')
252
1517
transport = transport.clone('kwaak')
253
# we do not want bzrdir to make any remote calls
254
bzrdir = RemoteBzrDir(transport, _client=False)
255
branch = RemoteBranch(bzrdir, None, _client=client)
1518
branch = self.make_remote_branch(transport, client)
256
1519
result = branch.last_revision_info()
259
[('call', 'Branch.last_revision_info', ('///kwaak/',))],
261
1520
self.assertEqual((2, revid), result)
264
class TestBranchSetLastRevision(tests.TestCase):
1523
class TestBranch_get_stacked_on_url(TestRemote):
1524
"""Test Branch._get_stacked_on_url rpc"""
1526
def test_get_stacked_on_invalid_url(self):
1527
# test that asking for a stacked on url the server can't access works.
1528
# This isn't perfect, but then as we're in the same process there
1529
# really isn't anything we can do to be 100% sure that the server
1530
# doesn't just open in - this test probably needs to be rewritten using
1531
# a spawn()ed server.
1532
stacked_branch = self.make_branch('stacked', format='1.9')
1533
memory_branch = self.make_branch('base', format='1.9')
1534
vfs_url = self.get_vfs_only_url('base')
1535
stacked_branch.set_stacked_on_url(vfs_url)
1536
transport = stacked_branch.bzrdir.root_transport
1537
client = FakeClient(transport.base)
1538
client.add_expected_call(
1539
'Branch.get_stacked_on_url', ('stacked/',),
1540
'success', ('ok', vfs_url))
1541
# XXX: Multiple calls are bad, this second call documents what is
1543
client.add_expected_call(
1544
'Branch.get_stacked_on_url', ('stacked/',),
1545
'success', ('ok', vfs_url))
1546
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1548
repo_fmt = remote.RemoteRepositoryFormat()
1549
repo_fmt._custom_format = stacked_branch.repository._format
1550
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1552
result = branch.get_stacked_on_url()
1553
self.assertEqual(vfs_url, result)
1555
def test_backwards_compatible(self):
1556
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1557
base_branch = self.make_branch('base', format='1.6')
1558
stacked_branch = self.make_branch('stacked', format='1.6')
1559
stacked_branch.set_stacked_on_url('../base')
1560
client = FakeClient(self.get_url())
1561
branch_network_name = self.get_branch_format().network_name()
1562
client.add_expected_call(
1563
'BzrDir.open_branchV3', ('stacked/',),
1564
'success', ('branch', branch_network_name))
1565
client.add_expected_call(
1566
'BzrDir.find_repositoryV3', ('stacked/',),
1567
'success', ('ok', '', 'no', 'no', 'yes',
1568
stacked_branch.repository._format.network_name()))
1569
# called twice, once from constructor and then again by us
1570
client.add_expected_call(
1571
'Branch.get_stacked_on_url', ('stacked/',),
1572
'unknown', ('Branch.get_stacked_on_url',))
1573
client.add_expected_call(
1574
'Branch.get_stacked_on_url', ('stacked/',),
1575
'unknown', ('Branch.get_stacked_on_url',))
1576
# this will also do vfs access, but that goes direct to the transport
1577
# and isn't seen by the FakeClient.
1578
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1579
RemoteBzrDirFormat(), _client=client)
1580
branch = bzrdir.open_branch()
1581
result = branch.get_stacked_on_url()
1582
self.assertEqual('../base', result)
1583
self.assertFinished(client)
1584
# it's in the fallback list both for the RemoteRepository and its vfs
1586
self.assertEqual(1, len(branch.repository._fallback_repositories))
1588
len(branch.repository._real_repository._fallback_repositories))
1590
def test_get_stacked_on_real_branch(self):
1591
base_branch = self.make_branch('base')
1592
stacked_branch = self.make_branch('stacked')
1593
stacked_branch.set_stacked_on_url('../base')
1594
reference_format = self.get_repo_format()
1595
network_name = reference_format.network_name()
1596
client = FakeClient(self.get_url())
1597
branch_network_name = self.get_branch_format().network_name()
1598
client.add_expected_call(
1599
'BzrDir.open_branchV3', ('stacked/',),
1600
'success', ('branch', branch_network_name))
1601
client.add_expected_call(
1602
'BzrDir.find_repositoryV3', ('stacked/',),
1603
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1604
# called twice, once from constructor and then again by us
1605
client.add_expected_call(
1606
'Branch.get_stacked_on_url', ('stacked/',),
1607
'success', ('ok', '../base'))
1608
client.add_expected_call(
1609
'Branch.get_stacked_on_url', ('stacked/',),
1610
'success', ('ok', '../base'))
1611
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1612
RemoteBzrDirFormat(), _client=client)
1613
branch = bzrdir.open_branch()
1614
result = branch.get_stacked_on_url()
1615
self.assertEqual('../base', result)
1616
self.assertFinished(client)
1617
# it's in the fallback list both for the RemoteRepository.
1618
self.assertEqual(1, len(branch.repository._fallback_repositories))
1619
# And we haven't had to construct a real repository.
1620
self.assertEqual(None, branch.repository._real_repository)
1623
class TestBranchSetLastRevision(RemoteBranchTestCase):
266
1625
def test_set_empty(self):
267
# set_revision_history([]) is translated to calling
1626
# _set_last_revision_info('null:') is translated to calling
268
1627
# Branch.set_last_revision(path, '') on the wire.
269
client = FakeClient([
271
(('ok', 'branch token', 'repo token'), ),
276
1628
transport = MemoryTransport()
277
1629
transport.mkdir('branch')
278
1630
transport = transport.clone('branch')
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
1632
client = FakeClient(transport.base)
1633
client.add_expected_call(
1634
'Branch.get_stacked_on_url', ('branch/',),
1635
'error', ('NotStacked',))
1636
client.add_expected_call(
1637
'Branch.lock_write', ('branch/', '', ''),
1638
'success', ('ok', 'branch token', 'repo token'))
1639
client.add_expected_call(
1640
'Branch.last_revision_info',
1642
'success', ('ok', '0', 'null:'))
1643
client.add_expected_call(
1644
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1646
client.add_expected_call(
1647
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1649
branch = self.make_remote_branch(transport, client)
285
1650
branch.lock_write()
287
result = branch.set_revision_history([])
289
[('call', 'Branch.set_last_revision',
290
('///branch/', 'branch token', 'repo token', 'null:'))],
1651
result = branch._set_last_revision(NULL_REVISION)
293
1653
self.assertEqual(None, result)
1654
self.assertFinished(client)
295
1656
def test_set_nonempty(self):
296
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1657
# set_last_revision_info(N, rev-idN) is translated to calling
297
1658
# Branch.set_last_revision(path, rev-idN) on the wire.
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):
1659
transport = MemoryTransport()
1660
transport.mkdir('branch')
1661
transport = transport.clone('branch')
1663
client = FakeClient(transport.base)
1664
client.add_expected_call(
1665
'Branch.get_stacked_on_url', ('branch/',),
1666
'error', ('NotStacked',))
1667
client.add_expected_call(
1668
'Branch.lock_write', ('branch/', '', ''),
1669
'success', ('ok', 'branch token', 'repo token'))
1670
client.add_expected_call(
1671
'Branch.last_revision_info',
1673
'success', ('ok', '0', 'null:'))
1675
encoded_body = bz2.compress('\n'.join(lines))
1676
client.add_success_response_with_body(encoded_body, 'ok')
1677
client.add_expected_call(
1678
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1680
client.add_expected_call(
1681
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1683
branch = self.make_remote_branch(transport, client)
1684
# Lock the branch, reset the record of remote calls.
1686
result = branch._set_last_revision('rev-id2')
1688
self.assertEqual(None, result)
1689
self.assertFinished(client)
1691
def test_no_such_revision(self):
1692
transport = MemoryTransport()
1693
transport.mkdir('branch')
1694
transport = transport.clone('branch')
1695
# A response of 'NoSuchRevision' is translated into an exception.
1696
client = FakeClient(transport.base)
1697
client.add_expected_call(
1698
'Branch.get_stacked_on_url', ('branch/',),
1699
'error', ('NotStacked',))
1700
client.add_expected_call(
1701
'Branch.lock_write', ('branch/', '', ''),
1702
'success', ('ok', 'branch token', 'repo token'))
1703
client.add_expected_call(
1704
'Branch.last_revision_info',
1706
'success', ('ok', '0', 'null:'))
1707
# get_graph calls to construct the revision history, for the set_rh
1710
encoded_body = bz2.compress('\n'.join(lines))
1711
client.add_success_response_with_body(encoded_body, 'ok')
1712
client.add_expected_call(
1713
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1714
'error', ('NoSuchRevision', 'rev-id'))
1715
client.add_expected_call(
1716
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1719
branch = self.make_remote_branch(transport, client)
1722
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1724
self.assertFinished(client)
1726
def test_tip_change_rejected(self):
1727
"""TipChangeRejected responses cause a TipChangeRejected exception to
1730
transport = MemoryTransport()
1731
transport.mkdir('branch')
1732
transport = transport.clone('branch')
1733
client = FakeClient(transport.base)
1734
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1735
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1736
client.add_expected_call(
1737
'Branch.get_stacked_on_url', ('branch/',),
1738
'error', ('NotStacked',))
1739
client.add_expected_call(
1740
'Branch.lock_write', ('branch/', '', ''),
1741
'success', ('ok', 'branch token', 'repo token'))
1742
client.add_expected_call(
1743
'Branch.last_revision_info',
1745
'success', ('ok', '0', 'null:'))
1747
encoded_body = bz2.compress('\n'.join(lines))
1748
client.add_success_response_with_body(encoded_body, 'ok')
1749
client.add_expected_call(
1750
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1751
'error', ('TipChangeRejected', rejection_msg_utf8))
1752
client.add_expected_call(
1753
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1755
branch = self.make_remote_branch(transport, client)
1757
# The 'TipChangeRejected' error response triggered by calling
1758
# set_last_revision_info causes a TipChangeRejected exception.
1759
err = self.assertRaises(
1760
errors.TipChangeRejected,
1761
branch._set_last_revision, 'rev-id')
1762
# The UTF-8 message from the response has been decoded into a unicode
1764
self.assertIsInstance(err.msg, unicode)
1765
self.assertEqual(rejection_msg_unicode, err.msg)
1767
self.assertFinished(client)
1770
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1772
def test_set_last_revision_info(self):
1773
# set_last_revision_info(num, 'rev-id') is translated to calling
1774
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1775
transport = MemoryTransport()
1776
transport.mkdir('branch')
1777
transport = transport.clone('branch')
1778
client = FakeClient(transport.base)
1779
# get_stacked_on_url
1780
client.add_error_response('NotStacked')
1782
client.add_success_response('ok', 'branch token', 'repo token')
1783
# query the current revision
1784
client.add_success_response('ok', '0', 'null:')
1786
client.add_success_response('ok')
1788
client.add_success_response('ok')
1790
branch = self.make_remote_branch(transport, client)
1791
# Lock the branch, reset the record of remote calls.
1794
result = branch.set_last_revision_info(1234, 'a-revision-id')
1796
[('call', 'Branch.last_revision_info', ('branch/',)),
1797
('call', 'Branch.set_last_revision_info',
1798
('branch/', 'branch token', 'repo token',
1799
'1234', 'a-revision-id'))],
1801
self.assertEqual(None, result)
1803
def test_no_such_revision(self):
1804
# A response of 'NoSuchRevision' is translated into an exception.
1805
transport = MemoryTransport()
1806
transport.mkdir('branch')
1807
transport = transport.clone('branch')
1808
client = FakeClient(transport.base)
1809
# get_stacked_on_url
1810
client.add_error_response('NotStacked')
1812
client.add_success_response('ok', 'branch token', 'repo token')
1814
client.add_error_response('NoSuchRevision', 'revid')
1816
client.add_success_response('ok')
1818
branch = self.make_remote_branch(transport, client)
1819
# Lock the branch, reset the record of remote calls.
1824
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1827
def test_backwards_compatibility(self):
1828
"""If the server does not support the Branch.set_last_revision_info
1829
verb (which is new in 1.4), then the client falls back to VFS methods.
1831
# This test is a little messy. Unlike most tests in this file, it
1832
# doesn't purely test what a Remote* object sends over the wire, and
1833
# how it reacts to responses from the wire. It instead relies partly
1834
# on asserting that the RemoteBranch will call
1835
# self._real_branch.set_last_revision_info(...).
1837
# First, set up our RemoteBranch with a FakeClient that raises
1838
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1839
transport = MemoryTransport()
1840
transport.mkdir('branch')
1841
transport = transport.clone('branch')
1842
client = FakeClient(transport.base)
1843
client.add_expected_call(
1844
'Branch.get_stacked_on_url', ('branch/',),
1845
'error', ('NotStacked',))
1846
client.add_expected_call(
1847
'Branch.last_revision_info',
1849
'success', ('ok', '0', 'null:'))
1850
client.add_expected_call(
1851
'Branch.set_last_revision_info',
1852
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1853
'unknown', 'Branch.set_last_revision_info')
1855
branch = self.make_remote_branch(transport, client)
1856
class StubRealBranch(object):
1859
def set_last_revision_info(self, revno, revision_id):
1861
('set_last_revision_info', revno, revision_id))
1862
def _clear_cached_state(self):
1864
real_branch = StubRealBranch()
1865
branch._real_branch = real_branch
1866
self.lock_remote_branch(branch)
1868
# Call set_last_revision_info, and verify it behaved as expected.
1869
result = branch.set_last_revision_info(1234, 'a-revision-id')
1871
[('set_last_revision_info', 1234, 'a-revision-id')],
1873
self.assertFinished(client)
1875
def test_unexpected_error(self):
1876
# If the server sends an error the client doesn't understand, it gets
1877
# turned into an UnknownErrorFromSmartServer, which is presented as a
1878
# non-internal error to the user.
1879
transport = MemoryTransport()
1880
transport.mkdir('branch')
1881
transport = transport.clone('branch')
1882
client = FakeClient(transport.base)
1883
# get_stacked_on_url
1884
client.add_error_response('NotStacked')
1886
client.add_success_response('ok', 'branch token', 'repo token')
1888
client.add_error_response('UnexpectedError')
1890
client.add_success_response('ok')
1892
branch = self.make_remote_branch(transport, client)
1893
# Lock the branch, reset the record of remote calls.
1897
err = self.assertRaises(
1898
errors.UnknownErrorFromSmartServer,
1899
branch.set_last_revision_info, 123, 'revid')
1900
self.assertEqual(('UnexpectedError',), err.error_tuple)
1903
def test_tip_change_rejected(self):
1904
"""TipChangeRejected responses cause a TipChangeRejected exception to
1907
transport = MemoryTransport()
1908
transport.mkdir('branch')
1909
transport = transport.clone('branch')
1910
client = FakeClient(transport.base)
1911
# get_stacked_on_url
1912
client.add_error_response('NotStacked')
1914
client.add_success_response('ok', 'branch token', 'repo token')
1916
client.add_error_response('TipChangeRejected', 'rejection message')
1918
client.add_success_response('ok')
1920
branch = self.make_remote_branch(transport, client)
1921
# Lock the branch, reset the record of remote calls.
1923
self.addCleanup(branch.unlock)
1926
# The 'TipChangeRejected' error response triggered by calling
1927
# set_last_revision_info causes a TipChangeRejected exception.
1928
err = self.assertRaises(
1929
errors.TipChangeRejected,
1930
branch.set_last_revision_info, 123, 'revid')
1931
self.assertEqual('rejection message', err.msg)
1934
class TestBranchGetSetConfig(RemoteBranchTestCase):
1936
def test_get_branch_conf(self):
1937
# in an empty branch we decode the response properly
1938
client = FakeClient()
1939
client.add_expected_call(
1940
'Branch.get_stacked_on_url', ('memory:///',),
1941
'error', ('NotStacked',),)
1942
client.add_success_response_with_body('# config file body', 'ok')
1943
transport = MemoryTransport()
1944
branch = self.make_remote_branch(transport, client)
1945
config = branch.get_config()
1946
config.has_explicit_nickname()
1948
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1949
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1952
def test_get_multi_line_branch_conf(self):
1953
# Make sure that multiple-line branch.conf files are supported
1955
# https://bugs.launchpad.net/bzr/+bug/354075
1956
client = FakeClient()
1957
client.add_expected_call(
1958
'Branch.get_stacked_on_url', ('memory:///',),
1959
'error', ('NotStacked',),)
1960
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1961
transport = MemoryTransport()
1962
branch = self.make_remote_branch(transport, client)
1963
config = branch.get_config()
1964
self.assertEqual(u'2', config.get_user_option('b'))
1966
def test_set_option(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
client.add_expected_call(
1975
'Branch.set_config_option', ('memory:///', 'branch token',
1976
'repo token', 'foo', 'bar', ''),
1978
client.add_expected_call(
1979
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1981
transport = MemoryTransport()
1982
branch = self.make_remote_branch(transport, client)
1984
config = branch._get_config()
1985
config.set_option('foo', 'bar')
1987
self.assertFinished(client)
1989
def test_set_option_with_dict(self):
1990
client = FakeClient()
1991
client.add_expected_call(
1992
'Branch.get_stacked_on_url', ('memory:///',),
1993
'error', ('NotStacked',),)
1994
client.add_expected_call(
1995
'Branch.lock_write', ('memory:///', '', ''),
1996
'success', ('ok', 'branch token', 'repo token'))
1997
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1998
client.add_expected_call(
1999
'Branch.set_config_option_dict', ('memory:///', 'branch token',
2000
'repo token', encoded_dict_value, 'foo', ''),
2002
client.add_expected_call(
2003
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2005
transport = MemoryTransport()
2006
branch = self.make_remote_branch(transport, client)
2008
config = branch._get_config()
2010
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
2013
self.assertFinished(client)
2015
def test_backwards_compat_set_option(self):
2016
self.setup_smart_server_with_call_log()
2017
branch = self.make_branch('.')
2018
verb = 'Branch.set_config_option'
2019
self.disable_verb(verb)
2021
self.addCleanup(branch.unlock)
2022
self.reset_smart_call_log()
2023
branch._get_config().set_option('value', 'name')
2024
self.assertLength(11, self.hpss_calls)
2025
self.assertEqual('value', branch._get_config().get_option('name'))
2027
def test_backwards_compat_set_option_with_dict(self):
2028
self.setup_smart_server_with_call_log()
2029
branch = self.make_branch('.')
2030
verb = 'Branch.set_config_option_dict'
2031
self.disable_verb(verb)
2033
self.addCleanup(branch.unlock)
2034
self.reset_smart_call_log()
2035
config = branch._get_config()
2036
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2037
config.set_option(value_dict, 'name')
2038
self.assertLength(11, self.hpss_calls)
2039
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2042
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2044
def test_get_branch_conf(self):
2045
# in an empty branch we decode the response properly
2046
client = FakeClient()
2047
client.add_expected_call(
2048
'Branch.get_stacked_on_url', ('memory:///',),
2049
'error', ('NotStacked',),)
2050
client.add_success_response_with_body('# config file body', 'ok')
2051
transport = MemoryTransport()
2052
branch = self.make_remote_branch(transport, client)
2053
config = branch.get_config_stack()
2055
config.get("log_format")
2057
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2058
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
2061
def test_set_branch_conf(self):
2062
client = FakeClient()
2063
client.add_expected_call(
2064
'Branch.get_stacked_on_url', ('memory:///',),
2065
'error', ('NotStacked',),)
2066
client.add_expected_call(
2067
'Branch.lock_write', ('memory:///', '', ''),
2068
'success', ('ok', 'branch token', 'repo token'))
2069
client.add_expected_call(
2070
'Branch.get_config_file', ('memory:///', ),
2071
'success', ('ok', ), "# line 1\n")
2072
client.add_expected_call(
2073
'Branch.get_config_file', ('memory:///', ),
2074
'success', ('ok', ), "# line 1\n")
2075
client.add_expected_call(
2076
'Branch.put_config_file', ('memory:///', 'branch token',
2079
client.add_expected_call(
2080
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2082
transport = MemoryTransport()
2083
branch = self.make_remote_branch(transport, client)
2085
config = branch.get_config_stack()
2086
config.set('email', 'The Dude <lebowski@example.com>')
2088
self.assertFinished(client)
2090
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2091
('call', 'Branch.lock_write', ('memory:///', '', '')),
2092
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2093
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2094
('call_with_body_bytes_expecting_body', 'Branch.put_config_file',
2095
('memory:///', 'branch token', 'repo token'),
2096
'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2097
('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
2101
class TestBranchLockWrite(RemoteBranchTestCase):
378
2103
def test_lock_write_unlockable(self):
379
client = FakeClient([(('UnlockableTransport', ), '')])
380
2104
transport = MemoryTransport()
2105
client = FakeClient(transport.base)
2106
client.add_expected_call(
2107
'Branch.get_stacked_on_url', ('quack/',),
2108
'error', ('NotStacked',),)
2109
client.add_expected_call(
2110
'Branch.lock_write', ('quack/', '', ''),
2111
'error', ('UnlockableTransport',))
381
2112
transport.mkdir('quack')
382
2113
transport = transport.clone('quack')
383
# we do not want bzrdir to make any remote calls
384
bzrdir = RemoteBzrDir(transport, _client=False)
385
branch = RemoteBranch(bzrdir, None, _client=client)
2114
branch = self.make_remote_branch(transport, client)
386
2115
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2116
self.assertFinished(client)
2119
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2121
def test_simple(self):
2122
transport = MemoryTransport()
2123
client = FakeClient(transport.base)
2124
client.add_expected_call(
2125
'Branch.get_stacked_on_url', ('quack/',),
2126
'error', ('NotStacked',),)
2127
client.add_expected_call(
2128
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2129
'success', ('ok', '0',),)
2130
client.add_expected_call(
2131
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2132
'error', ('NoSuchRevision', 'unknown',),)
2133
transport.mkdir('quack')
2134
transport = transport.clone('quack')
2135
branch = self.make_remote_branch(transport, client)
2136
self.assertEquals(0, branch.revision_id_to_revno('null:'))
2137
self.assertRaises(errors.NoSuchRevision,
2138
branch.revision_id_to_revno, 'unknown')
2139
self.assertFinished(client)
2141
def test_dotted(self):
2142
transport = MemoryTransport()
2143
client = FakeClient(transport.base)
2144
client.add_expected_call(
2145
'Branch.get_stacked_on_url', ('quack/',),
2146
'error', ('NotStacked',),)
2147
client.add_expected_call(
2148
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2149
'success', ('ok', '0',),)
2150
client.add_expected_call(
2151
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2152
'error', ('NoSuchRevision', 'unknown',),)
2153
transport.mkdir('quack')
2154
transport = transport.clone('quack')
2155
branch = self.make_remote_branch(transport, client)
2156
self.assertEquals((0, ), branch.revision_id_to_dotted_revno('null:'))
2157
self.assertRaises(errors.NoSuchRevision,
2158
branch.revision_id_to_dotted_revno, 'unknown')
2159
self.assertFinished(client)
2161
def test_dotted_no_smart_verb(self):
2162
self.setup_smart_server_with_call_log()
2163
branch = self.make_branch('.')
2164
self.disable_verb('Branch.revision_id_to_revno')
2165
self.reset_smart_call_log()
2166
self.assertEquals((0, ),
2167
branch.revision_id_to_dotted_revno('null:'))
2168
self.assertLength(8, self.hpss_calls)
2171
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2173
def test__get_config(self):
2174
client = FakeClient()
2175
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
2176
transport = MemoryTransport()
2177
bzrdir = self.make_remote_bzrdir(transport, client)
2178
config = bzrdir.get_config()
2179
self.assertEqual('/', config.get_default_stack_on())
387
2180
self.assertEqual(
388
[('call', 'Branch.lock_write', ('///quack/', '', ''))],
2181
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
2184
def test_set_option_uses_vfs(self):
2185
self.setup_smart_server_with_call_log()
2186
bzrdir = self.make_bzrdir('.')
2187
self.reset_smart_call_log()
2188
config = bzrdir.get_config()
2189
config.set_default_stack_on('/')
2190
self.assertLength(4, self.hpss_calls)
2192
def test_backwards_compat_get_option(self):
2193
self.setup_smart_server_with_call_log()
2194
bzrdir = self.make_bzrdir('.')
2195
verb = 'BzrDir.get_config_file'
2196
self.disable_verb(verb)
2197
self.reset_smart_call_log()
2198
self.assertEqual(None,
2199
bzrdir._get_config().get_option('default_stack_on'))
2200
self.assertLength(4, self.hpss_calls)
392
2203
class TestTransportIsReadonly(tests.TestCase):
394
2205
def test_true(self):
395
client = FakeClient([(('yes',), '')])
2206
client = FakeClient()
2207
client.add_success_response('yes')
396
2208
transport = RemoteTransport('bzr://example.com/', medium=False,
398
2210
self.assertEqual(True, transport.is_readonly())
2416
class TestRepositoryBreakLock(TestRemoteRepository):
2418
def test_break_lock(self):
2419
transport_path = 'quack'
2420
repo, client = self.setup_fake_client_and_repository(transport_path)
2421
client.add_success_response('ok')
2424
[('call', 'Repository.break_lock', ('quack/',))],
2428
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2430
def test_get_serializer_format(self):
2431
transport_path = 'hill'
2432
repo, client = self.setup_fake_client_and_repository(transport_path)
2433
client.add_success_response('ok', '7')
2434
self.assertEquals('7', repo.get_serializer_format())
2436
[('call', 'VersionedFileRepository.get_serializer_format',
2441
class TestRepositoryReconcile(TestRemoteRepository):
2443
def test_reconcile(self):
2444
transport_path = 'hill'
2445
repo, client = self.setup_fake_client_and_repository(transport_path)
2446
body = ("garbage_inventories: 2\n"
2447
"inconsistent_parents: 3\n")
2448
client.add_expected_call(
2449
'Repository.lock_write', ('hill/', ''),
2450
'success', ('ok', 'a token'))
2451
client.add_success_response_with_body(body, 'ok')
2452
reconciler = repo.reconcile()
2454
[('call', 'Repository.lock_write', ('hill/', '')),
2455
('call_expecting_body', 'Repository.reconcile',
2456
('hill/', 'a token'))],
2458
self.assertEquals(2, reconciler.garbage_inventories)
2459
self.assertEquals(3, reconciler.inconsistent_parents)
2462
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2464
def test_text(self):
2465
# ('ok',), body with signature text
2466
transport_path = 'quack'
2467
repo, client = self.setup_fake_client_and_repository(transport_path)
2468
client.add_success_response_with_body(
2470
self.assertEquals("THETEXT", repo.get_signature_text("revid"))
2472
[('call_expecting_body', 'Repository.get_revision_signature_text',
2473
('quack/', 'revid'))],
2476
def test_no_signature(self):
2477
transport_path = 'quick'
2478
repo, client = self.setup_fake_client_and_repository(transport_path)
2479
client.add_error_response('nosuchrevision', 'unknown')
2480
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2483
[('call_expecting_body', 'Repository.get_revision_signature_text',
2484
('quick/', 'unknown'))],
2488
class TestRepositoryGetGraph(TestRemoteRepository):
2490
def test_get_graph(self):
2491
# get_graph returns a graph with a custom parents provider.
2492
transport_path = 'quack'
2493
repo, client = self.setup_fake_client_and_repository(transport_path)
2494
graph = repo.get_graph()
2495
self.assertNotEqual(graph._parents_provider, repo)
2498
class TestRepositoryAddSignatureText(TestRemoteRepository):
2500
def test_add_signature_text(self):
2501
transport_path = 'quack'
2502
repo, client = self.setup_fake_client_and_repository(transport_path)
2503
client.add_expected_call(
2504
'Repository.lock_write', ('quack/', ''),
2505
'success', ('ok', 'a token'))
2506
client.add_expected_call(
2507
'Repository.start_write_group', ('quack/', 'a token'),
2508
'success', ('ok', ('token1', )))
2509
client.add_expected_call(
2510
'Repository.add_signature_text', ('quack/', 'a token', 'rev1',
2512
'success', ('ok', ), None)
2514
repo.start_write_group()
2516
repo.add_signature_text("rev1", "every bloody emperor"))
2518
('call_with_body_bytes_expecting_body',
2519
'Repository.add_signature_text',
2520
('quack/', 'a token', 'rev1', 'token1'),
2521
'every bloody emperor'),
2525
class TestRepositoryGetParentMap(TestRemoteRepository):
2527
def test_get_parent_map_caching(self):
2528
# get_parent_map returns from cache until unlock()
2529
# setup a reponse with two revisions
2530
r1 = u'\u0e33'.encode('utf8')
2531
r2 = u'\u0dab'.encode('utf8')
2532
lines = [' '.join([r2, r1]), r1]
2533
encoded_body = bz2.compress('\n'.join(lines))
2535
transport_path = 'quack'
2536
repo, client = self.setup_fake_client_and_repository(transport_path)
2537
client.add_success_response_with_body(encoded_body, 'ok')
2538
client.add_success_response_with_body(encoded_body, 'ok')
2540
graph = repo.get_graph()
2541
parents = graph.get_parent_map([r2])
2542
self.assertEqual({r2: (r1,)}, parents)
2543
# locking and unlocking deeper should not reset
2546
parents = graph.get_parent_map([r1])
2547
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2549
[('call_with_body_bytes_expecting_body',
2550
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2554
# now we call again, and it should use the second response.
2556
graph = repo.get_graph()
2557
parents = graph.get_parent_map([r1])
2558
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2560
[('call_with_body_bytes_expecting_body',
2561
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2563
('call_with_body_bytes_expecting_body',
2564
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2570
def test_get_parent_map_reconnects_if_unknown_method(self):
2571
transport_path = 'quack'
2572
rev_id = 'revision-id'
2573
repo, client = self.setup_fake_client_and_repository(transport_path)
2574
client.add_unknown_method_response('Repository.get_parent_map')
2575
client.add_success_response_with_body(rev_id, 'ok')
2576
self.assertFalse(client._medium._is_remote_before((1, 2)))
2577
parents = repo.get_parent_map([rev_id])
2579
[('call_with_body_bytes_expecting_body',
2580
'Repository.get_parent_map',
2581
('quack/', 'include-missing:', rev_id), '\n\n0'),
2582
('disconnect medium',),
2583
('call_expecting_body', 'Repository.get_revision_graph',
2586
# The medium is now marked as being connected to an older server
2587
self.assertTrue(client._medium._is_remote_before((1, 2)))
2588
self.assertEqual({rev_id: ('null:',)}, parents)
2590
def test_get_parent_map_fallback_parentless_node(self):
2591
"""get_parent_map falls back to get_revision_graph on old servers. The
2592
results from get_revision_graph are tweaked to match the get_parent_map
2595
Specifically, a {key: ()} result from get_revision_graph means "no
2596
parents" for that key, which in get_parent_map results should be
2597
represented as {key: ('null:',)}.
2599
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2601
rev_id = 'revision-id'
2602
transport_path = 'quack'
2603
repo, client = self.setup_fake_client_and_repository(transport_path)
2604
client.add_success_response_with_body(rev_id, 'ok')
2605
client._medium._remember_remote_is_before((1, 2))
2606
parents = repo.get_parent_map([rev_id])
2608
[('call_expecting_body', 'Repository.get_revision_graph',
2611
self.assertEqual({rev_id: ('null:',)}, parents)
2613
def test_get_parent_map_unexpected_response(self):
2614
repo, client = self.setup_fake_client_and_repository('path')
2615
client.add_success_response('something unexpected!')
2617
errors.UnexpectedSmartServerResponse,
2618
repo.get_parent_map, ['a-revision-id'])
2620
def test_get_parent_map_negative_caches_missing_keys(self):
2621
self.setup_smart_server_with_call_log()
2622
repo = self.make_repository('foo')
2623
self.assertIsInstance(repo, RemoteRepository)
2625
self.addCleanup(repo.unlock)
2626
self.reset_smart_call_log()
2627
graph = repo.get_graph()
2628
self.assertEqual({},
2629
graph.get_parent_map(['some-missing', 'other-missing']))
2630
self.assertLength(1, self.hpss_calls)
2631
# No call if we repeat this
2632
self.reset_smart_call_log()
2633
graph = repo.get_graph()
2634
self.assertEqual({},
2635
graph.get_parent_map(['some-missing', 'other-missing']))
2636
self.assertLength(0, self.hpss_calls)
2637
# Asking for more unknown keys makes a request.
2638
self.reset_smart_call_log()
2639
graph = repo.get_graph()
2640
self.assertEqual({},
2641
graph.get_parent_map(['some-missing', 'other-missing',
2643
self.assertLength(1, self.hpss_calls)
2645
def disableExtraResults(self):
2646
self.overrideAttr(SmartServerRepositoryGetParentMap,
2647
'no_extra_results', True)
2649
def test_null_cached_missing_and_stop_key(self):
2650
self.setup_smart_server_with_call_log()
2651
# Make a branch with a single revision.
2652
builder = self.make_branch_builder('foo')
2653
builder.start_series()
2654
builder.build_snapshot('first', None, [
2655
('add', ('', 'root-id', 'directory', ''))])
2656
builder.finish_series()
2657
branch = builder.get_branch()
2658
repo = branch.repository
2659
self.assertIsInstance(repo, RemoteRepository)
2660
# Stop the server from sending extra results.
2661
self.disableExtraResults()
2663
self.addCleanup(repo.unlock)
2664
self.reset_smart_call_log()
2665
graph = repo.get_graph()
2666
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2667
# 'first' it will be a candidate for the stop_keys of subsequent
2668
# requests, and because 'null:' was queried but not returned it will be
2669
# cached as missing.
2670
self.assertEqual({'first': ('null:',)},
2671
graph.get_parent_map(['first', 'null:']))
2672
# Now query for another key. This request will pass along a recipe of
2673
# start and stop keys describing the already cached results, and this
2674
# recipe's revision count must be correct (or else it will trigger an
2675
# error from the server).
2676
self.assertEqual({}, graph.get_parent_map(['another-key']))
2677
# This assertion guards against disableExtraResults silently failing to
2678
# work, thus invalidating the test.
2679
self.assertLength(2, self.hpss_calls)
2681
def test_get_parent_map_gets_ghosts_from_result(self):
2682
# asking for a revision should negatively cache close ghosts in its
2684
self.setup_smart_server_with_call_log()
2685
tree = self.make_branch_and_memory_tree('foo')
2688
builder = treebuilder.TreeBuilder()
2689
builder.start_tree(tree)
2691
builder.finish_tree()
2692
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2693
rev_id = tree.commit('')
2697
self.addCleanup(tree.unlock)
2698
repo = tree.branch.repository
2699
self.assertIsInstance(repo, RemoteRepository)
2701
repo.get_parent_map([rev_id])
2702
self.reset_smart_call_log()
2703
# Now asking for rev_id's ghost parent should not make calls
2704
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2705
self.assertLength(0, self.hpss_calls)
2707
def test_exposes_get_cached_parent_map(self):
2708
"""RemoteRepository exposes get_cached_parent_map from
2711
r1 = u'\u0e33'.encode('utf8')
2712
r2 = u'\u0dab'.encode('utf8')
2713
lines = [' '.join([r2, r1]), r1]
2714
encoded_body = bz2.compress('\n'.join(lines))
2716
transport_path = 'quack'
2717
repo, client = self.setup_fake_client_and_repository(transport_path)
2718
client.add_success_response_with_body(encoded_body, 'ok')
2720
# get_cached_parent_map should *not* trigger an RPC
2721
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2722
self.assertEqual([], client._calls)
2723
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2724
self.assertEqual({r1: (NULL_REVISION,)},
2725
repo.get_cached_parent_map([r1]))
2727
[('call_with_body_bytes_expecting_body',
2728
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2734
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2736
def test_allows_new_revisions(self):
2737
"""get_parent_map's results can be updated by commit."""
2738
smart_server = test_server.SmartTCPServer_for_testing()
2739
self.start_server(smart_server)
2740
self.make_branch('branch')
2741
branch = Branch.open(smart_server.get_url() + '/branch')
2742
tree = branch.create_checkout('tree', lightweight=True)
2744
self.addCleanup(tree.unlock)
2745
graph = tree.branch.repository.get_graph()
2746
# This provides an opportunity for the missing rev-id to be cached.
2747
self.assertEqual({}, graph.get_parent_map(['rev1']))
2748
tree.commit('message', rev_id='rev1')
2749
graph = tree.branch.repository.get_graph()
2750
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2753
class TestRepositoryGetRevisions(TestRemoteRepository):
2755
def test_hpss_missing_revision(self):
2756
transport_path = 'quack'
2757
repo, client = self.setup_fake_client_and_repository(transport_path)
2758
client.add_success_response_with_body(
2760
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2761
['somerev1', 'anotherrev2'])
2763
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2764
('quack/', ), "somerev1\nanotherrev2")],
2767
def test_hpss_get_single_revision(self):
2768
transport_path = 'quack'
2769
repo, client = self.setup_fake_client_and_repository(transport_path)
2770
somerev1 = Revision("somerev1")
2771
somerev1.committer = "Joe Committer <joe@example.com>"
2772
somerev1.timestamp = 1321828927
2773
somerev1.timezone = -60
2774
somerev1.inventory_sha1 = "691b39be74c67b1212a75fcb19c433aaed903c2b"
2775
somerev1.message = "Message"
2776
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2778
# Split up body into two bits to make sure the zlib compression object
2779
# gets data fed twice.
2780
client.add_success_response_with_body(
2781
[body[:10], body[10:]], 'ok', '10')
2782
revs = repo.get_revisions(['somerev1'])
2783
self.assertEquals(revs, [somerev1])
2785
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2786
('quack/', ), "somerev1")],
531
2790
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
533
2792
def test_null_revision(self):
534
2793
# a null revision has the predictable result {}, we should have no wire
535
2794
# traffic when calling it with this argument
536
responses = [(('notused', ), '')]
537
2795
transport_path = 'empty'
538
repo, client = self.setup_fake_client_and_repository(
539
responses, transport_path)
540
result = repo.get_revision_graph(NULL_REVISION)
2796
repo, client = self.setup_fake_client_and_repository(transport_path)
2797
client.add_success_response('notused')
2798
# actual RemoteRepository.get_revision_graph is gone, but there's an
2799
# equivalent private method for testing
2800
result = repo._get_revision_graph(NULL_REVISION)
541
2801
self.assertEqual([], client._calls)
542
2802
self.assertEqual({}, result)
686
3216
def test_none(self):
687
3217
# repo.has_revision(None) should not cause any traffic.
688
3218
transport_path = 'quack'
690
repo, client = self.setup_fake_client_and_repository(
691
responses, transport_path)
3219
repo, client = self.setup_fake_client_and_repository(transport_path)
693
3221
# The null revision is always there, so has_revision(None) == True.
694
self.assertEqual(True, repo.has_revision(None))
3222
self.assertEqual(True, repo.has_revision(NULL_REVISION))
696
3224
# The remote repo shouldn't be accessed.
697
3225
self.assertEqual([], client._calls)
3228
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3229
"""Test Repository.iter_file_bytes."""
3231
def test_single(self):
3232
transport_path = 'quack'
3233
repo, client = self.setup_fake_client_and_repository(transport_path)
3234
client.add_expected_call(
3235
'Repository.iter_files_bytes', ('quack/', ),
3236
'success', ('ok',), iter(["ok\x000", "\n", zlib.compress("mydata" * 10)]))
3237
for (identifier, byte_stream) in repo.iter_files_bytes([("somefile",
3238
"somerev", "myid")]):
3239
self.assertEquals("myid", identifier)
3240
self.assertEquals("".join(byte_stream), "mydata" * 10)
3242
def test_missing(self):
3243
transport_path = 'quack'
3244
repo, client = self.setup_fake_client_and_repository(transport_path)
3245
client.add_expected_call(
3246
'Repository.iter_files_bytes',
3248
'error', ('RevisionNotPresent', 'somefile', 'somerev'),
3249
iter(["absent\0somefile\0somerev\n"]))
3250
self.assertRaises(errors.RevisionNotPresent, list,
3251
repo.iter_files_bytes(
3252
[("somefile", "somerev", "myid")]))
3255
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3256
"""Base class for Repository.insert_stream and .insert_stream_1.19
3260
def checkInsertEmptyStream(self, repo, client):
3261
"""Insert an empty stream, checking the result.
3263
This checks that there are no resume_tokens or missing_keys, and that
3264
the client is finished.
3266
sink = repo._get_sink()
3267
fmt = repository.format_registry.get_default()
3268
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3269
self.assertEqual([], resume_tokens)
3270
self.assertEqual(set(), missing_keys)
3271
self.assertFinished(client)
3274
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3275
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3278
This test case is very similar to TestRepositoryInsertStream_1_19.
3282
TestRemoteRepository.setUp(self)
3283
self.disable_verb('Repository.insert_stream_1.19')
3285
def test_unlocked_repo(self):
3286
transport_path = 'quack'
3287
repo, client = self.setup_fake_client_and_repository(transport_path)
3288
client.add_expected_call(
3289
'Repository.insert_stream_1.19', ('quack/', ''),
3290
'unknown', ('Repository.insert_stream_1.19',))
3291
client.add_expected_call(
3292
'Repository.insert_stream', ('quack/', ''),
3294
client.add_expected_call(
3295
'Repository.insert_stream', ('quack/', ''),
3297
self.checkInsertEmptyStream(repo, client)
3299
def test_locked_repo_with_no_lock_token(self):
3300
transport_path = 'quack'
3301
repo, client = self.setup_fake_client_and_repository(transport_path)
3302
client.add_expected_call(
3303
'Repository.lock_write', ('quack/', ''),
3304
'success', ('ok', ''))
3305
client.add_expected_call(
3306
'Repository.insert_stream_1.19', ('quack/', ''),
3307
'unknown', ('Repository.insert_stream_1.19',))
3308
client.add_expected_call(
3309
'Repository.insert_stream', ('quack/', ''),
3311
client.add_expected_call(
3312
'Repository.insert_stream', ('quack/', ''),
3315
self.checkInsertEmptyStream(repo, client)
3317
def test_locked_repo_with_lock_token(self):
3318
transport_path = 'quack'
3319
repo, client = self.setup_fake_client_and_repository(transport_path)
3320
client.add_expected_call(
3321
'Repository.lock_write', ('quack/', ''),
3322
'success', ('ok', 'a token'))
3323
client.add_expected_call(
3324
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3325
'unknown', ('Repository.insert_stream_1.19',))
3326
client.add_expected_call(
3327
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3329
client.add_expected_call(
3330
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3333
self.checkInsertEmptyStream(repo, client)
3335
def test_stream_with_inventory_deltas(self):
3336
"""'inventory-deltas' substreams cannot be sent to the
3337
Repository.insert_stream verb, because not all servers that implement
3338
that verb will accept them. So when one is encountered the RemoteSink
3339
immediately stops using that verb and falls back to VFS insert_stream.
3341
transport_path = 'quack'
3342
repo, client = self.setup_fake_client_and_repository(transport_path)
3343
client.add_expected_call(
3344
'Repository.insert_stream_1.19', ('quack/', ''),
3345
'unknown', ('Repository.insert_stream_1.19',))
3346
client.add_expected_call(
3347
'Repository.insert_stream', ('quack/', ''),
3349
client.add_expected_call(
3350
'Repository.insert_stream', ('quack/', ''),
3352
# Create a fake real repository for insert_stream to fall back on, so
3353
# that we can directly see the records the RemoteSink passes to the
3358
def insert_stream(self, stream, src_format, resume_tokens):
3359
for substream_kind, substream in stream:
3360
self.records.append(
3361
(substream_kind, [record.key for record in substream]))
3362
return ['fake tokens'], ['fake missing keys']
3363
fake_real_sink = FakeRealSink()
3364
class FakeRealRepository:
3365
def _get_sink(self):
3366
return fake_real_sink
3367
def is_in_write_group(self):
3369
def refresh_data(self):
3371
repo._real_repository = FakeRealRepository()
3372
sink = repo._get_sink()
3373
fmt = repository.format_registry.get_default()
3374
stream = self.make_stream_with_inv_deltas(fmt)
3375
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3376
# Every record from the first inventory delta should have been sent to
3378
expected_records = [
3379
('inventory-deltas', [('rev2',), ('rev3',)]),
3380
('texts', [('some-rev', 'some-file')])]
3381
self.assertEqual(expected_records, fake_real_sink.records)
3382
# The return values from the real sink's insert_stream are propagated
3383
# back to the original caller.
3384
self.assertEqual(['fake tokens'], resume_tokens)
3385
self.assertEqual(['fake missing keys'], missing_keys)
3386
self.assertFinished(client)
3388
def make_stream_with_inv_deltas(self, fmt):
3389
"""Make a simple stream with an inventory delta followed by more
3390
records and more substreams to test that all records and substreams
3391
from that point on are used.
3393
This sends, in order:
3394
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3396
* texts substream: (some-rev, some-file)
3398
# Define a stream using generators so that it isn't rewindable.
3399
inv = inventory.Inventory(revision_id='rev1')
3400
inv.root.revision = 'rev1'
3401
def stream_with_inv_delta():
3402
yield ('inventories', inventories_substream())
3403
yield ('inventory-deltas', inventory_delta_substream())
3405
versionedfile.FulltextContentFactory(
3406
('some-rev', 'some-file'), (), None, 'content')])
3407
def inventories_substream():
3408
# An empty inventory fulltext. This will be streamed normally.
3409
text = fmt._serializer.write_inventory_to_string(inv)
3410
yield versionedfile.FulltextContentFactory(
3411
('rev1',), (), None, text)
3412
def inventory_delta_substream():
3413
# An inventory delta. This can't be streamed via this verb, so it
3414
# will trigger a fallback to VFS insert_stream.
3415
entry = inv.make_entry(
3416
'directory', 'newdir', inv.root.file_id, 'newdir-id')
3417
entry.revision = 'ghost'
3418
delta = [(None, 'newdir', 'newdir-id', entry)]
3419
serializer = inventory_delta.InventoryDeltaSerializer(
3420
versioned_root=True, tree_references=False)
3421
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
3422
yield versionedfile.ChunkedContentFactory(
3423
('rev2',), (('rev1',)), None, lines)
3425
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
3426
yield versionedfile.ChunkedContentFactory(
3427
('rev3',), (('rev1',)), None, lines)
3428
return stream_with_inv_delta()
3431
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3433
def test_unlocked_repo(self):
3434
transport_path = 'quack'
3435
repo, client = self.setup_fake_client_and_repository(transport_path)
3436
client.add_expected_call(
3437
'Repository.insert_stream_1.19', ('quack/', ''),
3439
client.add_expected_call(
3440
'Repository.insert_stream_1.19', ('quack/', ''),
3442
self.checkInsertEmptyStream(repo, client)
3444
def test_locked_repo_with_no_lock_token(self):
3445
transport_path = 'quack'
3446
repo, client = self.setup_fake_client_and_repository(transport_path)
3447
client.add_expected_call(
3448
'Repository.lock_write', ('quack/', ''),
3449
'success', ('ok', ''))
3450
client.add_expected_call(
3451
'Repository.insert_stream_1.19', ('quack/', ''),
3453
client.add_expected_call(
3454
'Repository.insert_stream_1.19', ('quack/', ''),
3457
self.checkInsertEmptyStream(repo, client)
3459
def test_locked_repo_with_lock_token(self):
3460
transport_path = 'quack'
3461
repo, client = self.setup_fake_client_and_repository(transport_path)
3462
client.add_expected_call(
3463
'Repository.lock_write', ('quack/', ''),
3464
'success', ('ok', 'a token'))
3465
client.add_expected_call(
3466
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3468
client.add_expected_call(
3469
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3472
self.checkInsertEmptyStream(repo, client)
700
3475
class TestRepositoryTarball(TestRemoteRepository):
702
3477
# This is a canned tarball reponse we can validate against
769
3524
self.assertFalse(isinstance(dest_repo, RemoteRepository))
770
3525
self.assertTrue(isinstance(src_repo, RemoteRepository))
771
3526
src_repo.copy_content_into(dest_repo)
3529
class _StubRealPackRepository(object):
3531
def __init__(self, calls):
3533
self._pack_collection = _StubPackCollection(calls)
3535
def start_write_group(self):
3536
self.calls.append(('start_write_group',))
3538
def is_in_write_group(self):
3541
def refresh_data(self):
3542
self.calls.append(('pack collection reload_pack_names',))
3545
class _StubPackCollection(object):
3547
def __init__(self, calls):
3551
self.calls.append(('pack collection autopack',))
3554
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3555
"""Tests for RemoteRepository.autopack implementation."""
3558
"""When the server returns 'ok' and there's no _real_repository, then
3559
nothing else happens: the autopack method is done.
3561
transport_path = 'quack'
3562
repo, client = self.setup_fake_client_and_repository(transport_path)
3563
client.add_expected_call(
3564
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
3566
self.assertFinished(client)
3568
def test_ok_with_real_repo(self):
3569
"""When the server returns 'ok' and there is a _real_repository, then
3570
the _real_repository's reload_pack_name's method will be called.
3572
transport_path = 'quack'
3573
repo, client = self.setup_fake_client_and_repository(transport_path)
3574
client.add_expected_call(
3575
'PackRepository.autopack', ('quack/',),
3577
repo._real_repository = _StubRealPackRepository(client._calls)
3580
[('call', 'PackRepository.autopack', ('quack/',)),
3581
('pack collection reload_pack_names',)],
3584
def test_backwards_compatibility(self):
3585
"""If the server does not recognise the PackRepository.autopack verb,
3586
fallback to the real_repository's implementation.
3588
transport_path = 'quack'
3589
repo, client = self.setup_fake_client_and_repository(transport_path)
3590
client.add_unknown_method_response('PackRepository.autopack')
3591
def stub_ensure_real():
3592
client._calls.append(('_ensure_real',))
3593
repo._real_repository = _StubRealPackRepository(client._calls)
3594
repo._ensure_real = stub_ensure_real
3597
[('call', 'PackRepository.autopack', ('quack/',)),
3599
('pack collection autopack',)],
3602
def test_oom_error_reporting(self):
3603
"""An out-of-memory condition on the server is reported clearly"""
3604
transport_path = 'quack'
3605
repo, client = self.setup_fake_client_and_repository(transport_path)
3606
client.add_expected_call(
3607
'PackRepository.autopack', ('quack/',),
3608
'error', ('MemoryError',))
3609
err = self.assertRaises(errors.BzrError, repo.autopack)
3610
self.assertContainsRe(str(err), "^remote server out of mem")
3613
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3614
"""Base class for unit tests for bzrlib.remote._translate_error."""
3616
def translateTuple(self, error_tuple, **context):
3617
"""Call _translate_error with an ErrorFromSmartServer built from the
3620
:param error_tuple: A tuple of a smart server response, as would be
3621
passed to an ErrorFromSmartServer.
3622
:kwargs context: context items to call _translate_error with.
3624
:returns: The error raised by _translate_error.
3626
# Raise the ErrorFromSmartServer before passing it as an argument,
3627
# because _translate_error may need to re-raise it with a bare 'raise'
3629
server_error = errors.ErrorFromSmartServer(error_tuple)
3630
translated_error = self.translateErrorFromSmartServer(
3631
server_error, **context)
3632
return translated_error
3634
def translateErrorFromSmartServer(self, error_object, **context):
3635
"""Like translateTuple, but takes an already constructed
3636
ErrorFromSmartServer rather than a tuple.
3640
except errors.ErrorFromSmartServer, server_error:
3641
translated_error = self.assertRaises(
3642
errors.BzrError, remote._translate_error, server_error,
3644
return translated_error
3647
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3648
"""Unit tests for bzrlib.remote._translate_error.
3650
Given an ErrorFromSmartServer (which has an error tuple from a smart
3651
server) and some context, _translate_error raises more specific errors from
3654
This test case covers the cases where _translate_error succeeds in
3655
translating an ErrorFromSmartServer to something better. See
3656
TestErrorTranslationRobustness for other cases.
3659
def test_NoSuchRevision(self):
3660
branch = self.make_branch('')
3662
translated_error = self.translateTuple(
3663
('NoSuchRevision', revid), branch=branch)
3664
expected_error = errors.NoSuchRevision(branch, revid)
3665
self.assertEqual(expected_error, translated_error)
3667
def test_nosuchrevision(self):
3668
repository = self.make_repository('')
3670
translated_error = self.translateTuple(
3671
('nosuchrevision', revid), repository=repository)
3672
expected_error = errors.NoSuchRevision(repository, revid)
3673
self.assertEqual(expected_error, translated_error)
3675
def test_nobranch(self):
3676
bzrdir = self.make_bzrdir('')
3677
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3678
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3679
self.assertEqual(expected_error, translated_error)
3681
def test_nobranch_one_arg(self):
3682
bzrdir = self.make_bzrdir('')
3683
translated_error = self.translateTuple(
3684
('nobranch', 'extra detail'), bzrdir=bzrdir)
3685
expected_error = errors.NotBranchError(
3686
path=bzrdir.root_transport.base,
3687
detail='extra detail')
3688
self.assertEqual(expected_error, translated_error)
3690
def test_norepository(self):
3691
bzrdir = self.make_bzrdir('')
3692
translated_error = self.translateTuple(('norepository',),
3694
expected_error = errors.NoRepositoryPresent(bzrdir)
3695
self.assertEqual(expected_error, translated_error)
3697
def test_LockContention(self):
3698
translated_error = self.translateTuple(('LockContention',))
3699
expected_error = errors.LockContention('(remote lock)')
3700
self.assertEqual(expected_error, translated_error)
3702
def test_UnlockableTransport(self):
3703
bzrdir = self.make_bzrdir('')
3704
translated_error = self.translateTuple(
3705
('UnlockableTransport',), bzrdir=bzrdir)
3706
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3707
self.assertEqual(expected_error, translated_error)
3709
def test_LockFailed(self):
3710
lock = 'str() of a server lock'
3711
why = 'str() of why'
3712
translated_error = self.translateTuple(('LockFailed', lock, why))
3713
expected_error = errors.LockFailed(lock, why)
3714
self.assertEqual(expected_error, translated_error)
3716
def test_TokenMismatch(self):
3717
token = 'a lock token'
3718
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3719
expected_error = errors.TokenMismatch(token, '(remote token)')
3720
self.assertEqual(expected_error, translated_error)
3722
def test_Diverged(self):
3723
branch = self.make_branch('a')
3724
other_branch = self.make_branch('b')
3725
translated_error = self.translateTuple(
3726
('Diverged',), branch=branch, other_branch=other_branch)
3727
expected_error = errors.DivergedBranches(branch, other_branch)
3728
self.assertEqual(expected_error, translated_error)
3730
def test_NotStacked(self):
3731
branch = self.make_branch('')
3732
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3733
expected_error = errors.NotStacked(branch)
3734
self.assertEqual(expected_error, translated_error)
3736
def test_ReadError_no_args(self):
3738
translated_error = self.translateTuple(('ReadError',), path=path)
3739
expected_error = errors.ReadError(path)
3740
self.assertEqual(expected_error, translated_error)
3742
def test_ReadError(self):
3744
translated_error = self.translateTuple(('ReadError', path))
3745
expected_error = errors.ReadError(path)
3746
self.assertEqual(expected_error, translated_error)
3748
def test_IncompatibleRepositories(self):
3749
translated_error = self.translateTuple(('IncompatibleRepositories',
3750
"repo1", "repo2", "details here"))
3751
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3753
self.assertEqual(expected_error, translated_error)
3755
def test_PermissionDenied_no_args(self):
3757
translated_error = self.translateTuple(('PermissionDenied',),
3759
expected_error = errors.PermissionDenied(path)
3760
self.assertEqual(expected_error, translated_error)
3762
def test_PermissionDenied_one_arg(self):
3764
translated_error = self.translateTuple(('PermissionDenied', path))
3765
expected_error = errors.PermissionDenied(path)
3766
self.assertEqual(expected_error, translated_error)
3768
def test_PermissionDenied_one_arg_and_context(self):
3769
"""Given a choice between a path from the local context and a path on
3770
the wire, _translate_error prefers the path from the local context.
3772
local_path = 'local path'
3773
remote_path = 'remote path'
3774
translated_error = self.translateTuple(
3775
('PermissionDenied', remote_path), path=local_path)
3776
expected_error = errors.PermissionDenied(local_path)
3777
self.assertEqual(expected_error, translated_error)
3779
def test_PermissionDenied_two_args(self):
3781
extra = 'a string with extra info'
3782
translated_error = self.translateTuple(
3783
('PermissionDenied', path, extra))
3784
expected_error = errors.PermissionDenied(path, extra)
3785
self.assertEqual(expected_error, translated_error)
3787
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3789
def test_NoSuchFile_context_path(self):
3790
local_path = "local path"
3791
translated_error = self.translateTuple(('ReadError', "remote path"),
3793
expected_error = errors.ReadError(local_path)
3794
self.assertEqual(expected_error, translated_error)
3796
def test_NoSuchFile_without_context(self):
3797
remote_path = "remote path"
3798
translated_error = self.translateTuple(('ReadError', remote_path))
3799
expected_error = errors.ReadError(remote_path)
3800
self.assertEqual(expected_error, translated_error)
3802
def test_ReadOnlyError(self):
3803
translated_error = self.translateTuple(('ReadOnlyError',))
3804
expected_error = errors.TransportNotPossible("readonly transport")
3805
self.assertEqual(expected_error, translated_error)
3807
def test_MemoryError(self):
3808
translated_error = self.translateTuple(('MemoryError',))
3809
self.assertStartsWith(str(translated_error),
3810
"remote server out of memory")
3812
def test_generic_IndexError_no_classname(self):
3813
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3814
translated_error = self.translateErrorFromSmartServer(err)
3815
expected_error = errors.UnknownErrorFromSmartServer(err)
3816
self.assertEqual(expected_error, translated_error)
3818
# GZ 2011-03-02: TODO test generic non-ascii error string
3820
def test_generic_KeyError(self):
3821
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3822
translated_error = self.translateErrorFromSmartServer(err)
3823
expected_error = errors.UnknownErrorFromSmartServer(err)
3824
self.assertEqual(expected_error, translated_error)
3827
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3828
"""Unit tests for bzrlib.remote._translate_error's robustness.
3830
TestErrorTranslationSuccess is for cases where _translate_error can
3831
translate successfully. This class about how _translate_err behaves when
3832
it fails to translate: it re-raises the original error.
3835
def test_unrecognised_server_error(self):
3836
"""If the error code from the server is not recognised, the original
3837
ErrorFromSmartServer is propagated unmodified.
3839
error_tuple = ('An unknown error tuple',)
3840
server_error = errors.ErrorFromSmartServer(error_tuple)
3841
translated_error = self.translateErrorFromSmartServer(server_error)
3842
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3843
self.assertEqual(expected_error, translated_error)
3845
def test_context_missing_a_key(self):
3846
"""In case of a bug in the client, or perhaps an unexpected response
3847
from a server, _translate_error returns the original error tuple from
3848
the server and mutters a warning.
3850
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3851
# in the context dict. So let's give it an empty context dict instead
3852
# to exercise its error recovery.
3854
error_tuple = ('NoSuchRevision', 'revid')
3855
server_error = errors.ErrorFromSmartServer(error_tuple)
3856
translated_error = self.translateErrorFromSmartServer(server_error)
3857
self.assertEqual(server_error, translated_error)
3858
# In addition to re-raising ErrorFromSmartServer, some debug info has
3859
# been muttered to the log file for developer to look at.
3860
self.assertContainsRe(
3862
"Missing key 'branch' in context")
3864
def test_path_missing(self):
3865
"""Some translations (PermissionDenied, ReadError) can determine the
3866
'path' variable from either the wire or the local context. If neither
3867
has it, then an error is raised.
3869
error_tuple = ('ReadError',)
3870
server_error = errors.ErrorFromSmartServer(error_tuple)
3871
translated_error = self.translateErrorFromSmartServer(server_error)
3872
self.assertEqual(server_error, translated_error)
3873
# In addition to re-raising ErrorFromSmartServer, some debug info has
3874
# been muttered to the log file for developer to look at.
3875
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3878
class TestStacking(tests.TestCaseWithTransport):
3879
"""Tests for operations on stacked remote repositories.
3881
The underlying format type must support stacking.
3884
def test_access_stacked_remote(self):
3885
# based on <http://launchpad.net/bugs/261315>
3886
# make a branch stacked on another repository containing an empty
3887
# revision, then open it over hpss - we should be able to see that
3889
base_transport = self.get_transport()
3890
base_builder = self.make_branch_builder('base', format='1.9')
3891
base_builder.start_series()
3892
base_revid = base_builder.build_snapshot('rev-id', None,
3893
[('add', ('', None, 'directory', None))],
3895
base_builder.finish_series()
3896
stacked_branch = self.make_branch('stacked', format='1.9')
3897
stacked_branch.set_stacked_on_url('../base')
3898
# start a server looking at this
3899
smart_server = test_server.SmartTCPServer_for_testing()
3900
self.start_server(smart_server)
3901
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3902
# can get its branch and repository
3903
remote_branch = remote_bzrdir.open_branch()
3904
remote_repo = remote_branch.repository
3905
remote_repo.lock_read()
3907
# it should have an appropriate fallback repository, which should also
3908
# be a RemoteRepository
3909
self.assertLength(1, remote_repo._fallback_repositories)
3910
self.assertIsInstance(remote_repo._fallback_repositories[0],
3912
# and it has the revision committed to the underlying repository;
3913
# these have varying implementations so we try several of them
3914
self.assertTrue(remote_repo.has_revisions([base_revid]))
3915
self.assertTrue(remote_repo.has_revision(base_revid))
3916
self.assertEqual(remote_repo.get_revision(base_revid).message,
3919
remote_repo.unlock()
3921
def prepare_stacked_remote_branch(self):
3922
"""Get stacked_upon and stacked branches with content in each."""
3923
self.setup_smart_server_with_call_log()
3924
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3925
tree1.commit('rev1', rev_id='rev1')
3926
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3927
).open_workingtree()
3928
local_tree = tree2.branch.create_checkout('local')
3929
local_tree.commit('local changes make me feel good.')
3930
branch2 = Branch.open(self.get_url('tree2'))
3932
self.addCleanup(branch2.unlock)
3933
return tree1.branch, branch2
3935
def test_stacked_get_parent_map(self):
3936
# the public implementation of get_parent_map obeys stacking
3937
_, branch = self.prepare_stacked_remote_branch()
3938
repo = branch.repository
3939
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3941
def test_unstacked_get_parent_map(self):
3942
# _unstacked_provider.get_parent_map ignores stacking
3943
_, branch = self.prepare_stacked_remote_branch()
3944
provider = branch.repository._unstacked_provider
3945
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3947
def fetch_stream_to_rev_order(self, stream):
3949
for kind, substream in stream:
3950
if not kind == 'revisions':
3953
for content in substream:
3954
result.append(content.key[-1])
3957
def get_ordered_revs(self, format, order, branch_factory=None):
3958
"""Get a list of the revisions in a stream to format format.
3960
:param format: The format of the target.
3961
:param order: the order that target should have requested.
3962
:param branch_factory: A callable to create a trunk and stacked branch
3963
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3964
:result: The revision ids in the stream, in the order seen,
3965
the topological order of revisions in the source.
3967
unordered_format = bzrdir.format_registry.get(format)()
3968
target_repository_format = unordered_format.repository_format
3970
self.assertEqual(order, target_repository_format._fetch_order)
3971
if branch_factory is None:
3972
branch_factory = self.prepare_stacked_remote_branch
3973
_, stacked = branch_factory()
3974
source = stacked.repository._get_source(target_repository_format)
3975
tip = stacked.last_revision()
3976
stacked.repository._ensure_real()
3977
graph = stacked.repository.get_graph()
3978
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3979
if r != NULL_REVISION]
3981
search = vf_search.PendingAncestryResult([tip], stacked.repository)
3982
self.reset_smart_call_log()
3983
stream = source.get_stream(search)
3984
# We trust that if a revision is in the stream the rest of the new
3985
# content for it is too, as per our main fetch tests; here we are
3986
# checking that the revisions are actually included at all, and their
3988
return self.fetch_stream_to_rev_order(stream), revs
3990
def test_stacked_get_stream_unordered(self):
3991
# Repository._get_source.get_stream() from a stacked repository with
3992
# unordered yields the full data from both stacked and stacked upon
3994
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3995
self.assertEqual(set(expected_revs), set(rev_ord))
3996
# Getting unordered results should have made a streaming data request
3997
# from the server, then one from the backing branch.
3998
self.assertLength(2, self.hpss_calls)
4000
def test_stacked_on_stacked_get_stream_unordered(self):
4001
# Repository._get_source.get_stream() from a stacked repository which
4002
# is itself stacked yields the full data from all three sources.
4003
def make_stacked_stacked():
4004
_, stacked = self.prepare_stacked_remote_branch()
4005
tree = stacked.bzrdir.sprout('tree3', stacked=True
4006
).open_workingtree()
4007
local_tree = tree.branch.create_checkout('local-tree3')
4008
local_tree.commit('more local changes are better')
4009
branch = Branch.open(self.get_url('tree3'))
4011
self.addCleanup(branch.unlock)
4013
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
4014
branch_factory=make_stacked_stacked)
4015
self.assertEqual(set(expected_revs), set(rev_ord))
4016
# Getting unordered results should have made a streaming data request
4017
# from the server, and one from each backing repo
4018
self.assertLength(3, self.hpss_calls)
4020
def test_stacked_get_stream_topological(self):
4021
# Repository._get_source.get_stream() from a stacked repository with
4022
# topological sorting yields the full data from both stacked and
4023
# stacked upon sources in topological order.
4024
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4025
self.assertEqual(expected_revs, rev_ord)
4026
# Getting topological sort requires VFS calls still - one of which is
4027
# pushing up from the bound branch.
4028
self.assertLength(14, self.hpss_calls)
4030
def test_stacked_get_stream_groupcompress(self):
4031
# Repository._get_source.get_stream() from a stacked repository with
4032
# groupcompress sorting yields the full data from both stacked and
4033
# stacked upon sources in groupcompress order.
4034
raise tests.TestSkipped('No groupcompress ordered format available')
4035
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4036
self.assertEqual(expected_revs, reversed(rev_ord))
4037
# Getting unordered results should have made a streaming data request
4038
# from the backing branch, and one from the stacked on branch.
4039
self.assertLength(2, self.hpss_calls)
4041
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4042
# When pulling some fixed amount of content that is more than the
4043
# source has (because some is coming from a fallback branch, no error
4044
# should be received. This was reported as bug 360791.
4045
# Need three branches: a trunk, a stacked branch, and a preexisting
4046
# branch pulling content from stacked and trunk.
4047
self.setup_smart_server_with_call_log()
4048
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4049
r1 = trunk.commit('start')
4050
stacked_branch = trunk.branch.create_clone_on_transport(
4051
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4052
local = self.make_branch('local', format='1.9-rich-root')
4053
local.repository.fetch(stacked_branch.repository,
4054
stacked_branch.last_revision())
4057
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4060
super(TestRemoteBranchEffort, self).setUp()
4061
# Create a smart server that publishes whatever the backing VFS server
4063
self.smart_server = test_server.SmartTCPServer_for_testing()
4064
self.start_server(self.smart_server, self.get_server())
4065
# Log all HPSS calls into self.hpss_calls.
4066
_SmartClient.hooks.install_named_hook(
4067
'call', self.capture_hpss_call, None)
4068
self.hpss_calls = []
4070
def capture_hpss_call(self, params):
4071
self.hpss_calls.append(params.method)
4073
def test_copy_content_into_avoids_revision_history(self):
4074
local = self.make_branch('local')
4075
builder = self.make_branch_builder('remote')
4076
builder.build_commit(message="Commit.")
4077
remote_branch_url = self.smart_server.get_url() + 'remote'
4078
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4079
local.repository.fetch(remote_branch.repository)
4080
self.hpss_calls = []
4081
remote_branch.copy_content_into(local)
4082
self.assertFalse('Branch.revision_history' in self.hpss_calls)
4084
def test_fetch_everything_needs_just_one_call(self):
4085
local = self.make_branch('local')
4086
builder = self.make_branch_builder('remote')
4087
builder.build_commit(message="Commit.")
4088
remote_branch_url = self.smart_server.get_url() + 'remote'
4089
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4090
self.hpss_calls = []
4091
local.repository.fetch(
4092
remote_branch.repository,
4093
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4094
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
4096
def override_verb(self, verb_name, verb):
4097
request_handlers = request.request_handlers
4098
orig_verb = request_handlers.get(verb_name)
4099
orig_info = request_handlers.get_info(verb_name)
4100
request_handlers.register(verb_name, verb, override_existing=True)
4101
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4102
override_existing=True, info=orig_info)
4104
def test_fetch_everything_backwards_compat(self):
4105
"""Can fetch with EverythingResult even with pre 2.4 servers.
4107
Pre-2.4 do not support 'everything' searches with the
4108
Repository.get_stream_1.19 verb.
4111
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4112
"""A version of the Repository.get_stream_1.19 verb patched to
4113
reject 'everything' searches the way 2.3 and earlier do.
4115
def recreate_search(self, repository, search_bytes,
4116
discard_excess=False):
4117
verb_log.append(search_bytes.split('\n', 1)[0])
4118
if search_bytes == 'everything':
4120
request.FailedSmartServerResponse(('BadSearch',)))
4121
return super(OldGetStreamVerb,
4122
self).recreate_search(repository, search_bytes,
4123
discard_excess=discard_excess)
4124
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
4125
local = self.make_branch('local')
4126
builder = self.make_branch_builder('remote')
4127
builder.build_commit(message="Commit.")
4128
remote_branch_url = self.smart_server.get_url() + 'remote'
4129
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4130
self.hpss_calls = []
4131
local.repository.fetch(
4132
remote_branch.repository,
4133
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4134
# make sure the overridden verb was used
4135
self.assertLength(1, verb_log)
4136
# more than one HPSS call is needed, but because it's a VFS callback
4137
# its hard to predict exactly how many.
4138
self.assertTrue(len(self.hpss_calls) > 1)
4141
class TestUpdateBoundBranchWithModifiedBoundLocation(
4142
tests.TestCaseWithTransport):
4143
"""Ensure correct handling of bound_location modifications.
4145
This is tested against a smart server as http://pad.lv/786980 was about a
4146
ReadOnlyError (write attempt during a read-only transaction) which can only
4147
happen in this context.
4151
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4152
self.transport_server = test_server.SmartTCPServer_for_testing
4154
def make_master_and_checkout(self, master_name, checkout_name):
4155
# Create the master branch and its associated checkout
4156
self.master = self.make_branch_and_tree(master_name)
4157
self.checkout = self.master.branch.create_checkout(checkout_name)
4158
# Modify the master branch so there is something to update
4159
self.master.commit('add stuff')
4160
self.last_revid = self.master.commit('even more stuff')
4161
self.bound_location = self.checkout.branch.get_bound_location()
4163
def assertUpdateSucceeds(self, new_location):
4164
self.checkout.branch.set_bound_location(new_location)
4165
self.checkout.update()
4166
self.assertEquals(self.last_revid, self.checkout.last_revision())
4168
def test_without_final_slash(self):
4169
self.make_master_and_checkout('master', 'checkout')
4170
# For unclear reasons some users have a bound_location without a final
4171
# '/', simulate that by forcing such a value
4172
self.assertEndsWith(self.bound_location, '/')
4173
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4175
def test_plus_sign(self):
4176
self.make_master_and_checkout('+master', 'checkout')
4177
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4179
def test_tilda(self):
4180
# Embed ~ in the middle of the path just to avoid any $HOME
4182
self.make_master_and_checkout('mas~ter', 'checkout')
4183
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4186
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4188
def test_no_context(self):
4189
class OutOfCoffee(errors.BzrError):
4190
"""A dummy exception for testing."""
4192
def __init__(self, urgency):
4193
self.urgency = urgency
4194
remote.no_context_error_translators.register("OutOfCoffee",
4195
lambda err: OutOfCoffee(err.error_args[0]))
4196
transport = MemoryTransport()
4197
client = FakeClient(transport.base)
4198
client.add_expected_call(
4199
'Branch.get_stacked_on_url', ('quack/',),
4200
'error', ('NotStacked',))
4201
client.add_expected_call(
4202
'Branch.last_revision_info',
4204
'error', ('OutOfCoffee', 'low'))
4205
transport.mkdir('quack')
4206
transport = transport.clone('quack')
4207
branch = self.make_remote_branch(transport, client)
4208
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4209
self.assertFinished(client)
4211
def test_with_context(self):
4212
class OutOfTea(errors.BzrError):
4213
def __init__(self, branch, urgency):
4214
self.branch = branch
4215
self.urgency = urgency
4216
remote.error_translators.register("OutOfTea",
4217
lambda err, find, path: OutOfTea(err.error_args[0],
4219
transport = MemoryTransport()
4220
client = FakeClient(transport.base)
4221
client.add_expected_call(
4222
'Branch.get_stacked_on_url', ('quack/',),
4223
'error', ('NotStacked',))
4224
client.add_expected_call(
4225
'Branch.last_revision_info',
4227
'error', ('OutOfTea', 'low'))
4228
transport.mkdir('quack')
4229
transport = transport.clone('quack')
4230
branch = self.make_remote_branch(transport, client)
4231
self.assertRaises(OutOfTea, branch.last_revision_info)
4232
self.assertFinished(client)
4235
class TestRepositoryPack(TestRemoteRepository):
4237
def test_pack(self):
4238
transport_path = 'quack'
4239
repo, client = self.setup_fake_client_and_repository(transport_path)
4240
client.add_expected_call(
4241
'Repository.lock_write', ('quack/', ''),
4242
'success', ('ok', 'token'))
4243
client.add_expected_call(
4244
'Repository.pack', ('quack/', 'token', 'False'),
4245
'success', ('ok',), )
4246
client.add_expected_call(
4247
'Repository.unlock', ('quack/', 'token'),
4248
'success', ('ok', ))
4251
def test_pack_with_hint(self):
4252
transport_path = 'quack'
4253
repo, client = self.setup_fake_client_and_repository(transport_path)
4254
client.add_expected_call(
4255
'Repository.lock_write', ('quack/', ''),
4256
'success', ('ok', 'token'))
4257
client.add_expected_call(
4258
'Repository.pack', ('quack/', 'token', 'False'),
4259
'success', ('ok',), )
4260
client.add_expected_call(
4261
'Repository.unlock', ('quack/', 'token', 'False'),
4262
'success', ('ok', ))
4263
repo.pack(['hinta', 'hintb'])
4266
class TestRepositoryIterInventories(TestRemoteRepository):
4267
"""Test Repository.iter_inventories."""
4269
def _serialize_inv_delta(self, old_name, new_name, delta):
4270
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4271
return "".join(serializer.delta_to_lines(old_name, new_name, delta))
4273
def test_single_empty(self):
4274
transport_path = 'quack'
4275
repo, client = self.setup_fake_client_and_repository(transport_path)
4276
fmt = bzrdir.format_registry.get('2a')().repository_format
4278
stream = [('inventory-deltas', [
4279
versionedfile.FulltextContentFactory('somerevid', None, None,
4280
self._serialize_inv_delta('null:', 'somerevid', []))])]
4281
client.add_expected_call(
4282
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4283
'success', ('ok', ),
4284
_stream_to_byte_stream(stream, fmt))
4285
ret = list(repo.iter_inventories(["somerevid"]))
4286
self.assertLength(1, ret)
4288
self.assertEquals("somerevid", inv.revision_id)
4290
def test_empty(self):
4291
transport_path = 'quack'
4292
repo, client = self.setup_fake_client_and_repository(transport_path)
4293
ret = list(repo.iter_inventories([]))
4294
self.assertEquals(ret, [])
4296
def test_missing(self):
4297
transport_path = 'quack'
4298
repo, client = self.setup_fake_client_and_repository(transport_path)
4299
client.add_expected_call(
4300
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4301
'success', ('ok', ), iter([]))
4302
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(