137
102
b = BzrDir.open_from_transport(self.transport).open_branch()
138
103
self.assertStartsWith(str(b), 'RemoteBranch(')
140
def test_remote_bzrdir_repr(self):
141
b = BzrDir.open_from_transport(self.transport)
142
self.assertStartsWith(str(b), 'RemoteBzrDir(')
144
def test_remote_branch_format_supports_stacking(self):
146
self.make_branch('unstackable', format='pack-0.92')
147
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
148
self.assertFalse(b._format.supports_stacking())
149
self.make_branch('stackable', format='1.9')
150
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
151
self.assertTrue(b._format.supports_stacking())
153
def test_remote_repo_format_supports_external_references(self):
155
bd = self.make_bzrdir('unstackable', format='pack-0.92')
156
r = bd.create_repository()
157
self.assertFalse(r._format.supports_external_lookups)
158
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
159
self.assertFalse(r._format.supports_external_lookups)
160
bd = self.make_bzrdir('stackable', format='1.9')
161
r = bd.create_repository()
162
self.assertTrue(r._format.supports_external_lookups)
163
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
164
self.assertTrue(r._format.supports_external_lookups)
166
def test_remote_branch_set_append_revisions_only(self):
167
# Make a format 1.9 branch, which supports append_revisions_only
168
branch = self.make_branch('branch', format='1.9')
169
branch.set_append_revisions_only(True)
170
config = branch.get_config_stack()
172
True, config.get('append_revisions_only'))
173
branch.set_append_revisions_only(False)
174
config = branch.get_config_stack()
176
False, config.get('append_revisions_only'))
178
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
179
branch = self.make_branch('branch', format='knit')
181
errors.UpgradeRequired, branch.set_append_revisions_only, True)
184
106
class FakeProtocol(object):
185
107
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
187
def __init__(self, body, fake_client):
189
self._body_buffer = None
190
self._fake_client = fake_client
109
def __init__(self, body):
110
self._body_buffer = StringIO(body)
192
112
def read_body_bytes(self, count=-1):
193
if self._body_buffer is None:
194
self._body_buffer = StringIO(self.body)
195
bytes = self._body_buffer.read(count)
196
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
197
self._fake_client.expecting_body = False
200
def cancel_read_body(self):
201
self._fake_client.expecting_body = False
203
def read_streamed_body(self):
113
return self._body_buffer.read(count)
207
116
class FakeClient(_SmartClient):
208
117
"""Lookalike for _SmartClient allowing testing."""
119
def __init__(self, responses):
120
# We don't call the super init because there is no medium.
121
"""create a FakeClient.
210
def __init__(self, fake_medium_base='fake base'):
211
"""Create a FakeClient."""
123
:param respones: A list of response-tuple, body-data pairs to be sent
126
self.responses = responses
214
self.expecting_body = False
215
# if non-None, this is the list of expected calls, with only the
216
# method name and arguments included. the body might be hard to
217
# compute so is not included. If a call is None, that call can
219
self._expected_calls = None
220
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
222
def add_expected_call(self, call_name, call_args, response_type,
223
response_args, response_body=None):
224
if self._expected_calls is None:
225
self._expected_calls = []
226
self._expected_calls.append((call_name, call_args))
227
self.responses.append((response_type, response_args, response_body))
229
def add_success_response(self, *args):
230
self.responses.append(('success', args, None))
232
def add_success_response_with_body(self, body, *args):
233
self.responses.append(('success', args, body))
234
if self._expected_calls is not None:
235
self._expected_calls.append(None)
237
def add_error_response(self, *args):
238
self.responses.append(('error', args))
240
def add_unknown_method_response(self, verb):
241
self.responses.append(('unknown', verb))
243
def finished_test(self):
244
if self._expected_calls:
245
raise AssertionError("%r finished but was still expecting %r"
246
% (self, self._expected_calls[0]))
248
def _get_next_response(self):
250
response_tuple = self.responses.pop(0)
251
except IndexError, e:
252
raise AssertionError("%r didn't expect any more calls"
254
if response_tuple[0] == 'unknown':
255
raise errors.UnknownSmartMethod(response_tuple[1])
256
elif response_tuple[0] == 'error':
257
raise errors.ErrorFromSmartServer(response_tuple[1])
258
return response_tuple
260
def _check_call(self, method, args):
261
if self._expected_calls is None:
262
# the test should be updated to say what it expects
265
next_call = self._expected_calls.pop(0)
267
raise AssertionError("%r didn't expect any more calls "
269
% (self, method, args,))
270
if next_call is None:
272
if method != next_call[0] or args != next_call[1]:
273
raise AssertionError("%r expected %r%r "
275
% (self, next_call[0], next_call[1], method, args,))
277
129
def call(self, method, *args):
278
self._check_call(method, args)
279
130
self._calls.append(('call', method, args))
280
return self._get_next_response()[1]
131
return self.responses.pop(0)[0]
282
133
def call_expecting_body(self, method, *args):
283
self._check_call(method, args)
284
134
self._calls.append(('call_expecting_body', method, args))
285
result = self._get_next_response()
286
self.expecting_body = True
287
return result[1], FakeProtocol(result[2], self)
289
def call_with_body_bytes(self, method, args, body):
290
self._check_call(method, args)
291
self._calls.append(('call_with_body_bytes', method, args, body))
292
result = self._get_next_response()
293
return result[1], FakeProtocol(result[2], self)
295
def call_with_body_bytes_expecting_body(self, method, args, body):
296
self._check_call(method, args)
297
self._calls.append(('call_with_body_bytes_expecting_body', method,
299
result = self._get_next_response()
300
self.expecting_body = True
301
return result[1], FakeProtocol(result[2], self)
303
def call_with_body_stream(self, args, stream):
304
# Explicitly consume the stream before checking for an error, because
305
# that's what happens a real medium.
306
stream = list(stream)
307
self._check_call(args[0], args[1:])
308
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
309
result = self._get_next_response()
310
# The second value returned from call_with_body_stream is supposed to
311
# be a response_handler object, but so far no tests depend on that.
312
response_handler = None
313
return result[1], response_handler
316
class FakeMedium(medium.SmartClientMedium):
318
def __init__(self, client_calls, base):
319
medium.SmartClientMedium.__init__(self, base)
320
self._client_calls = client_calls
322
def disconnect(self):
323
self._client_calls.append(('disconnect medium',))
326
class TestVfsHas(tests.TestCase):
328
def test_unicode_path(self):
329
client = FakeClient('/')
330
client.add_success_response('yes',)
331
transport = RemoteTransport('bzr://localhost/', _client=client)
332
filename = u'/hell\u00d8'.encode('utf8')
333
result = transport.has(filename)
335
[('call', 'has', (filename,))],
337
self.assertTrue(result)
340
class TestRemote(tests.TestCaseWithMemoryTransport):
342
def get_branch_format(self):
343
reference_bzrdir_format = controldir.format_registry.get('default')()
344
return reference_bzrdir_format.get_branch_format()
346
def get_repo_format(self):
347
reference_bzrdir_format = controldir.format_registry.get('default')()
348
return reference_bzrdir_format.repository_format
350
def assertFinished(self, fake_client):
351
"""Assert that all of a FakeClient's expected calls have occurred."""
352
fake_client.finished_test()
355
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
356
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
358
def assertRemotePath(self, expected, client_base, transport_base):
359
"""Assert that the result of
360
SmartClientMedium.remote_path_from_transport is the expected value for
361
a given client_base and transport_base.
363
client_medium = medium.SmartClientMedium(client_base)
364
t = transport.get_transport(transport_base)
365
result = client_medium.remote_path_from_transport(t)
366
self.assertEqual(expected, result)
368
def test_remote_path_from_transport(self):
369
"""SmartClientMedium.remote_path_from_transport calculates a URL for
370
the given transport relative to the root of the client base URL.
372
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
373
self.assertRemotePath(
374
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
376
def assertRemotePathHTTP(self, expected, transport_base, relpath):
377
"""Assert that the result of
378
HttpTransportBase.remote_path_from_transport is the expected value for
379
a given transport_base and relpath of that transport. (Note that
380
HttpTransportBase is a subclass of SmartClientMedium)
382
base_transport = transport.get_transport(transport_base)
383
client_medium = base_transport.get_smart_medium()
384
cloned_transport = base_transport.clone(relpath)
385
result = client_medium.remote_path_from_transport(cloned_transport)
386
self.assertEqual(expected, result)
388
def test_remote_path_from_transport_http(self):
389
"""Remote paths for HTTP transports are calculated differently to other
390
transports. They are just relative to the client base, not the root
391
directory of the host.
393
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
394
self.assertRemotePathHTTP(
395
'../xyz/', scheme + '//host/path', '../xyz/')
396
self.assertRemotePathHTTP(
397
'xyz/', scheme + '//host/path', 'xyz/')
400
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
401
"""Tests for the behaviour of client_medium.remote_is_at_least."""
403
def test_initially_unlimited(self):
404
"""A fresh medium assumes that the remote side supports all
407
client_medium = medium.SmartClientMedium('dummy base')
408
self.assertFalse(client_medium._is_remote_before((99, 99)))
410
def test__remember_remote_is_before(self):
411
"""Calling _remember_remote_is_before ratchets down the known remote
414
client_medium = medium.SmartClientMedium('dummy base')
415
# Mark the remote side as being less than 1.6. The remote side may
417
client_medium._remember_remote_is_before((1, 6))
418
self.assertTrue(client_medium._is_remote_before((1, 6)))
419
self.assertFalse(client_medium._is_remote_before((1, 5)))
420
# Calling _remember_remote_is_before again with a lower value works.
421
client_medium._remember_remote_is_before((1, 5))
422
self.assertTrue(client_medium._is_remote_before((1, 5)))
423
# If you call _remember_remote_is_before with a higher value it logs a
424
# warning, and continues to remember the lower value.
425
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
426
client_medium._remember_remote_is_before((1, 9))
427
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
428
self.assertTrue(client_medium._is_remote_before((1, 5)))
431
class TestBzrDirCloningMetaDir(TestRemote):
433
def test_backwards_compat(self):
434
self.setup_smart_server_with_call_log()
435
a_dir = self.make_bzrdir('.')
436
self.reset_smart_call_log()
437
verb = 'BzrDir.cloning_metadir'
438
self.disable_verb(verb)
439
format = a_dir.cloning_metadir()
440
call_count = len([call for call in self.hpss_calls if
441
call.call.method == verb])
442
self.assertEqual(1, call_count)
444
def test_branch_reference(self):
445
transport = self.get_transport('quack')
446
referenced = self.make_branch('referenced')
447
expected = referenced.bzrdir.cloning_metadir()
448
client = FakeClient(transport.base)
449
client.add_expected_call(
450
'BzrDir.cloning_metadir', ('quack/', 'False'),
451
'error', ('BranchReference',)),
452
client.add_expected_call(
453
'BzrDir.open_branchV3', ('quack/',),
454
'success', ('ref', self.get_url('referenced'))),
455
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
457
result = a_bzrdir.cloning_metadir()
458
# We should have got a control dir matching the referenced branch.
459
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
460
self.assertEqual(expected._repository_format, result._repository_format)
461
self.assertEqual(expected._branch_format, result._branch_format)
462
self.assertFinished(client)
464
def test_current_server(self):
465
transport = self.get_transport('.')
466
transport = transport.clone('quack')
467
self.make_bzrdir('quack')
468
client = FakeClient(transport.base)
469
reference_bzrdir_format = controldir.format_registry.get('default')()
470
control_name = reference_bzrdir_format.network_name()
471
client.add_expected_call(
472
'BzrDir.cloning_metadir', ('quack/', 'False'),
473
'success', (control_name, '', ('branch', ''))),
474
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
476
result = a_bzrdir.cloning_metadir()
477
# We should have got a reference control dir with default branch and
478
# repository formats.
479
# This pokes a little, just to be sure.
480
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
481
self.assertEqual(None, result._repository_format)
482
self.assertEqual(None, result._branch_format)
483
self.assertFinished(client)
485
def test_unknown(self):
486
transport = self.get_transport('quack')
487
referenced = self.make_branch('referenced')
488
expected = referenced.bzrdir.cloning_metadir()
489
client = FakeClient(transport.base)
490
client.add_expected_call(
491
'BzrDir.cloning_metadir', ('quack/', 'False'),
492
'success', ('unknown', 'unknown', ('branch', ''))),
493
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
495
self.assertRaises(errors.UnknownFormatError, a_bzrdir.cloning_metadir)
498
class TestBzrDirCheckoutMetaDir(TestRemote):
500
def test__get_checkout_format(self):
501
transport = MemoryTransport()
502
client = FakeClient(transport.base)
503
reference_bzrdir_format = controldir.format_registry.get('default')()
504
control_name = reference_bzrdir_format.network_name()
505
client.add_expected_call(
506
'BzrDir.checkout_metadir', ('quack/', ),
507
'success', (control_name, '', ''))
508
transport.mkdir('quack')
509
transport = transport.clone('quack')
510
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
512
result = a_bzrdir.checkout_metadir()
513
# We should have got a reference control dir with default branch and
514
# repository formats.
515
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
516
self.assertEqual(None, result._repository_format)
517
self.assertEqual(None, result._branch_format)
518
self.assertFinished(client)
520
def test_unknown_format(self):
521
transport = MemoryTransport()
522
client = FakeClient(transport.base)
523
client.add_expected_call(
524
'BzrDir.checkout_metadir', ('quack/',),
525
'success', ('dontknow', '', ''))
526
transport.mkdir('quack')
527
transport = transport.clone('quack')
528
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
530
self.assertRaises(errors.UnknownFormatError,
531
a_bzrdir.checkout_metadir)
532
self.assertFinished(client)
535
class TestBzrDirGetBranches(TestRemote):
537
def test_get_branches(self):
538
transport = MemoryTransport()
539
client = FakeClient(transport.base)
540
reference_bzrdir_format = controldir.format_registry.get('default')()
541
branch_name = reference_bzrdir_format.get_branch_format().network_name()
542
client.add_success_response_with_body(
544
"foo": ("branch", branch_name),
545
"": ("branch", branch_name)}), "success")
546
client.add_success_response(
547
'ok', '', 'no', 'no', 'no',
548
reference_bzrdir_format.repository_format.network_name())
549
client.add_error_response('NotStacked')
550
client.add_success_response(
551
'ok', '', 'no', 'no', 'no',
552
reference_bzrdir_format.repository_format.network_name())
553
client.add_error_response('NotStacked')
554
transport.mkdir('quack')
555
transport = transport.clone('quack')
556
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
558
result = a_bzrdir.get_branches()
559
self.assertEquals(set(["", "foo"]), set(result.keys()))
561
[('call_expecting_body', 'BzrDir.get_branches', ('quack/',)),
562
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
563
('call', 'Branch.get_stacked_on_url', ('quack/', )),
564
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
565
('call', 'Branch.get_stacked_on_url', ('quack/', ))],
569
class TestBzrDirDestroyBranch(TestRemote):
571
def test_destroy_default(self):
572
transport = self.get_transport('quack')
573
referenced = self.make_branch('referenced')
574
client = FakeClient(transport.base)
575
client.add_expected_call(
576
'BzrDir.destroy_branch', ('quack/', ),
578
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
580
a_bzrdir.destroy_branch()
581
self.assertFinished(client)
584
class TestBzrDirHasWorkingTree(TestRemote):
586
def test_has_workingtree(self):
587
transport = self.get_transport('quack')
588
client = FakeClient(transport.base)
589
client.add_expected_call(
590
'BzrDir.has_workingtree', ('quack/',),
591
'success', ('yes',)),
592
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
594
self.assertTrue(a_bzrdir.has_workingtree())
595
self.assertFinished(client)
597
def test_no_workingtree(self):
598
transport = self.get_transport('quack')
599
client = FakeClient(transport.base)
600
client.add_expected_call(
601
'BzrDir.has_workingtree', ('quack/',),
603
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
605
self.assertFalse(a_bzrdir.has_workingtree())
606
self.assertFinished(client)
609
class TestBzrDirDestroyRepository(TestRemote):
611
def test_destroy_repository(self):
612
transport = self.get_transport('quack')
613
client = FakeClient(transport.base)
614
client.add_expected_call(
615
'BzrDir.destroy_repository', ('quack/',),
617
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
619
a_bzrdir.destroy_repository()
620
self.assertFinished(client)
623
class TestBzrDirOpen(TestRemote):
625
def make_fake_client_and_transport(self, path='quack'):
626
transport = MemoryTransport()
627
transport.mkdir(path)
628
transport = transport.clone(path)
629
client = FakeClient(transport.base)
630
return client, transport
632
def test_absent(self):
633
client, transport = self.make_fake_client_and_transport()
634
client.add_expected_call(
635
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
636
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
637
RemoteBzrDirFormat(), _client=client, _force_probe=True)
638
self.assertFinished(client)
640
def test_present_without_workingtree(self):
641
client, transport = self.make_fake_client_and_transport()
642
client.add_expected_call(
643
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
644
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
645
_client=client, _force_probe=True)
646
self.assertIsInstance(bd, RemoteBzrDir)
647
self.assertFalse(bd.has_workingtree())
648
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
649
self.assertFinished(client)
651
def test_present_with_workingtree(self):
652
client, transport = self.make_fake_client_and_transport()
653
client.add_expected_call(
654
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
655
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
656
_client=client, _force_probe=True)
657
self.assertIsInstance(bd, RemoteBzrDir)
658
self.assertTrue(bd.has_workingtree())
659
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
660
self.assertFinished(client)
662
def test_backwards_compat(self):
663
client, transport = self.make_fake_client_and_transport()
664
client.add_expected_call(
665
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
666
client.add_expected_call(
667
'BzrDir.open', ('quack/',), 'success', ('yes',))
668
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
669
_client=client, _force_probe=True)
670
self.assertIsInstance(bd, RemoteBzrDir)
671
self.assertFinished(client)
673
def test_backwards_compat_hpss_v2(self):
674
client, transport = self.make_fake_client_and_transport()
675
# Monkey-patch fake client to simulate real-world behaviour with v2
676
# server: upon first RPC call detect the protocol version, and because
677
# the version is 2 also do _remember_remote_is_before((1, 6)) before
678
# continuing with the RPC.
679
orig_check_call = client._check_call
680
def check_call(method, args):
681
client._medium._protocol_version = 2
682
client._medium._remember_remote_is_before((1, 6))
683
client._check_call = orig_check_call
684
client._check_call(method, args)
685
client._check_call = check_call
686
client.add_expected_call(
687
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
688
client.add_expected_call(
689
'BzrDir.open', ('quack/',), 'success', ('yes',))
690
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
691
_client=client, _force_probe=True)
692
self.assertIsInstance(bd, RemoteBzrDir)
693
self.assertFinished(client)
696
class TestBzrDirOpenBranch(TestRemote):
698
def test_backwards_compat(self):
699
self.setup_smart_server_with_call_log()
700
self.make_branch('.')
701
a_dir = BzrDir.open(self.get_url('.'))
702
self.reset_smart_call_log()
703
verb = 'BzrDir.open_branchV3'
704
self.disable_verb(verb)
705
format = a_dir.open_branch()
706
call_count = len([call for call in self.hpss_calls if
707
call.call.method == verb])
708
self.assertEqual(1, call_count)
135
result = self.responses.pop(0)
136
return result[0], FakeProtocol(result[1])
139
class TestBzrDirOpenBranch(tests.TestCase):
710
141
def test_branch_present(self):
711
reference_format = self.get_repo_format()
712
network_name = reference_format.network_name()
713
branch_network_name = self.get_branch_format().network_name()
142
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )])
714
143
transport = MemoryTransport()
715
144
transport.mkdir('quack')
716
145
transport = transport.clone('quack')
717
client = FakeClient(transport.base)
718
client.add_expected_call(
719
'BzrDir.open_branchV3', ('quack/',),
720
'success', ('branch', branch_network_name))
721
client.add_expected_call(
722
'BzrDir.find_repositoryV3', ('quack/',),
723
'success', ('ok', '', 'no', 'no', 'no', network_name))
724
client.add_expected_call(
725
'Branch.get_stacked_on_url', ('quack/',),
726
'error', ('NotStacked',))
727
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
146
bzrdir = RemoteBzrDir(transport, _client=client)
729
147
result = bzrdir.open_branch()
149
[('call', 'BzrDir.open_branch', ('///quack/',)),
150
('call', 'BzrDir.find_repository', ('///quack/',))],
730
152
self.assertIsInstance(result, RemoteBranch)
731
153
self.assertEqual(bzrdir, result.bzrdir)
732
self.assertFinished(client)
734
155
def test_branch_missing(self):
156
client = FakeClient([(('nobranch',), )])
735
157
transport = MemoryTransport()
736
158
transport.mkdir('quack')
737
159
transport = transport.clone('quack')
738
client = FakeClient(transport.base)
739
client.add_error_response('nobranch')
740
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
160
bzrdir = RemoteBzrDir(transport, _client=client)
742
161
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
743
162
self.assertEqual(
744
[('call', 'BzrDir.open_branchV3', ('quack/',))],
163
[('call', 'BzrDir.open_branch', ('///quack/',))],
747
def test__get_tree_branch(self):
748
# _get_tree_branch is a form of open_branch, but it should only ask for
749
# branch opening, not any other network requests.
751
def open_branch(name=None, possible_transports=None):
752
calls.append("Called")
754
transport = MemoryTransport()
755
# no requests on the network - catches other api calls being made.
756
client = FakeClient(transport.base)
757
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
759
# patch the open_branch call to record that it was called.
760
bzrdir.open_branch = open_branch
761
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
762
self.assertEqual(["Called"], calls)
763
self.assertEqual([], client._calls)
765
def test_url_quoting_of_path(self):
766
# Relpaths on the wire should not be URL-escaped. So "~" should be
767
# transmitted as "~", not "%7E".
768
transport = RemoteTCPTransport('bzr://localhost/~hello/')
769
client = FakeClient(transport.base)
770
reference_format = self.get_repo_format()
771
network_name = reference_format.network_name()
772
branch_network_name = self.get_branch_format().network_name()
773
client.add_expected_call(
774
'BzrDir.open_branchV3', ('~hello/',),
775
'success', ('branch', branch_network_name))
776
client.add_expected_call(
777
'BzrDir.find_repositoryV3', ('~hello/',),
778
'success', ('ok', '', 'no', 'no', 'no', network_name))
779
client.add_expected_call(
780
'Branch.get_stacked_on_url', ('~hello/',),
781
'error', ('NotStacked',))
782
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
784
result = bzrdir.open_branch()
785
self.assertFinished(client)
787
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
788
reference_format = self.get_repo_format()
789
network_name = reference_format.network_name()
790
transport = MemoryTransport()
791
transport.mkdir('quack')
792
transport = transport.clone('quack')
166
def check_open_repository(self, rich_root, subtrees):
794
168
rich_response = 'yes'
818
191
self.check_open_repository(False, True)
819
192
self.check_open_repository(True, False)
820
193
self.check_open_repository(False, False)
821
self.check_open_repository(False, False, 'yes')
823
195
def test_old_server(self):
824
196
"""RemoteBzrDirFormat should fail to probe if the server version is too
827
199
self.assertRaises(errors.NotBranchError,
828
RemoteBzrProber.probe_transport, OldServerTransport())
831
class TestBzrDirCreateBranch(TestRemote):
833
def test_backwards_compat(self):
834
self.setup_smart_server_with_call_log()
835
repo = self.make_repository('.')
836
self.reset_smart_call_log()
837
self.disable_verb('BzrDir.create_branch')
838
branch = repo.bzrdir.create_branch()
839
create_branch_call_count = len([call for call in self.hpss_calls if
840
call.call.method == 'BzrDir.create_branch'])
841
self.assertEqual(1, create_branch_call_count)
843
def test_current_server(self):
844
transport = self.get_transport('.')
845
transport = transport.clone('quack')
846
self.make_repository('quack')
847
client = FakeClient(transport.base)
848
reference_bzrdir_format = controldir.format_registry.get('default')()
849
reference_format = reference_bzrdir_format.get_branch_format()
850
network_name = reference_format.network_name()
851
reference_repo_fmt = reference_bzrdir_format.repository_format
852
reference_repo_name = reference_repo_fmt.network_name()
853
client.add_expected_call(
854
'BzrDir.create_branch', ('quack/', network_name),
855
'success', ('ok', network_name, '', 'no', 'no', 'yes',
856
reference_repo_name))
857
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
859
branch = a_bzrdir.create_branch()
860
# We should have got a remote branch
861
self.assertIsInstance(branch, remote.RemoteBranch)
862
# its format should have the settings from the response
863
format = branch._format
864
self.assertEqual(network_name, format.network_name())
866
def test_already_open_repo_and_reused_medium(self):
867
"""Bug 726584: create_branch(..., repository=repo) should work
868
regardless of what the smart medium's base URL is.
870
self.transport_server = test_server.SmartTCPServer_for_testing
871
transport = self.get_transport('.')
872
repo = self.make_repository('quack')
873
# Client's medium rooted a transport root (not at the bzrdir)
874
client = FakeClient(transport.base)
875
transport = transport.clone('quack')
876
reference_bzrdir_format = controldir.format_registry.get('default')()
877
reference_format = reference_bzrdir_format.get_branch_format()
878
network_name = reference_format.network_name()
879
reference_repo_fmt = reference_bzrdir_format.repository_format
880
reference_repo_name = reference_repo_fmt.network_name()
881
client.add_expected_call(
882
'BzrDir.create_branch', ('extra/quack/', network_name),
883
'success', ('ok', network_name, '', 'no', 'no', 'yes',
884
reference_repo_name))
885
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
887
branch = a_bzrdir.create_branch(repository=repo)
888
# We should have got a remote branch
889
self.assertIsInstance(branch, remote.RemoteBranch)
890
# its format should have the settings from the response
891
format = branch._format
892
self.assertEqual(network_name, format.network_name())
895
class TestBzrDirCreateRepository(TestRemote):
897
def test_backwards_compat(self):
898
self.setup_smart_server_with_call_log()
899
bzrdir = self.make_bzrdir('.')
900
self.reset_smart_call_log()
901
self.disable_verb('BzrDir.create_repository')
902
repo = bzrdir.create_repository()
903
create_repo_call_count = len([call for call in self.hpss_calls if
904
call.call.method == 'BzrDir.create_repository'])
905
self.assertEqual(1, create_repo_call_count)
907
def test_current_server(self):
908
transport = self.get_transport('.')
909
transport = transport.clone('quack')
910
self.make_bzrdir('quack')
911
client = FakeClient(transport.base)
912
reference_bzrdir_format = controldir.format_registry.get('default')()
913
reference_format = reference_bzrdir_format.repository_format
914
network_name = reference_format.network_name()
915
client.add_expected_call(
916
'BzrDir.create_repository', ('quack/',
917
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
919
'success', ('ok', 'yes', 'yes', 'yes', network_name))
920
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
922
repo = a_bzrdir.create_repository()
923
# We should have got a remote repository
924
self.assertIsInstance(repo, remote.RemoteRepository)
925
# its format should have the settings from the response
926
format = repo._format
927
self.assertTrue(format.rich_root_data)
928
self.assertTrue(format.supports_tree_reference)
929
self.assertTrue(format.supports_external_lookups)
930
self.assertEqual(network_name, format.network_name())
933
class TestBzrDirOpenRepository(TestRemote):
935
def test_backwards_compat_1_2_3(self):
936
# fallback all the way to the first version.
937
reference_format = self.get_repo_format()
938
network_name = reference_format.network_name()
939
server_url = 'bzr://example.com/'
940
self.permit_url(server_url)
941
client = FakeClient(server_url)
942
client.add_unknown_method_response('BzrDir.find_repositoryV3')
943
client.add_unknown_method_response('BzrDir.find_repositoryV2')
944
client.add_success_response('ok', '', 'no', 'no')
945
# A real repository instance will be created to determine the network
947
client.add_success_response_with_body(
948
"Bazaar-NG meta directory, format 1\n", 'ok')
949
client.add_success_response('stat', '0', '65535')
950
client.add_success_response_with_body(
951
reference_format.get_format_string(), 'ok')
952
# PackRepository wants to do a stat
953
client.add_success_response('stat', '0', '65535')
954
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
956
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
958
repo = bzrdir.open_repository()
960
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
961
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
962
('call', 'BzrDir.find_repository', ('quack/',)),
963
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
964
('call', 'stat', ('/quack/.bzr',)),
965
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
966
('call', 'stat', ('/quack/.bzr/repository',)),
969
self.assertEqual(network_name, repo._format.network_name())
971
def test_backwards_compat_2(self):
972
# fallback to find_repositoryV2
973
reference_format = self.get_repo_format()
974
network_name = reference_format.network_name()
975
server_url = 'bzr://example.com/'
976
self.permit_url(server_url)
977
client = FakeClient(server_url)
978
client.add_unknown_method_response('BzrDir.find_repositoryV3')
979
client.add_success_response('ok', '', 'no', 'no', 'no')
980
# A real repository instance will be created to determine the network
982
client.add_success_response_with_body(
983
"Bazaar-NG meta directory, format 1\n", 'ok')
984
client.add_success_response('stat', '0', '65535')
985
client.add_success_response_with_body(
986
reference_format.get_format_string(), 'ok')
987
# PackRepository wants to do a stat
988
client.add_success_response('stat', '0', '65535')
989
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
991
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
993
repo = bzrdir.open_repository()
995
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
996
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
997
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
998
('call', 'stat', ('/quack/.bzr',)),
999
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
1000
('call', 'stat', ('/quack/.bzr/repository',)),
1003
self.assertEqual(network_name, repo._format.network_name())
1005
def test_current_server(self):
1006
reference_format = self.get_repo_format()
1007
network_name = reference_format.network_name()
1008
transport = MemoryTransport()
1009
transport.mkdir('quack')
1010
transport = transport.clone('quack')
1011
client = FakeClient(transport.base)
1012
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
1013
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1015
repo = bzrdir.open_repository()
1017
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
1019
self.assertEqual(network_name, repo._format.network_name())
1022
class TestBzrDirFormatInitializeEx(TestRemote):
1024
def test_success(self):
1025
"""Simple test for typical successful call."""
1026
fmt = RemoteBzrDirFormat()
1027
default_format_name = BzrDirFormat.get_default_format().network_name()
1028
transport = self.get_transport()
1029
client = FakeClient(transport.base)
1030
client.add_expected_call(
1031
'BzrDirFormat.initialize_ex_1.16',
1032
(default_format_name, 'path', 'False', 'False', 'False', '',
1033
'', '', '', 'False'),
1035
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
1036
'bzrdir fmt', 'False', '', '', 'repo lock token'))
1037
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1038
# it's currently hard to test that without supplying a real remote
1039
# transport connected to a real server.
1040
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
1041
transport, False, False, False, None, None, None, None, False)
1042
self.assertFinished(client)
1044
def test_error(self):
1045
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1046
corresponding error from the client.
1048
fmt = RemoteBzrDirFormat()
1049
default_format_name = BzrDirFormat.get_default_format().network_name()
1050
transport = self.get_transport()
1051
client = FakeClient(transport.base)
1052
client.add_expected_call(
1053
'BzrDirFormat.initialize_ex_1.16',
1054
(default_format_name, 'path', 'False', 'False', 'False', '',
1055
'', '', '', 'False'),
1057
('PermissionDenied', 'path', 'extra info'))
1058
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1059
# it's currently hard to test that without supplying a real remote
1060
# transport connected to a real server.
1061
err = self.assertRaises(errors.PermissionDenied,
1062
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1063
False, False, False, None, None, None, None, False)
1064
self.assertEqual('path', err.path)
1065
self.assertEqual(': extra info', err.extra)
1066
self.assertFinished(client)
1068
def test_error_from_real_server(self):
1069
"""Integration test for error translation."""
1070
transport = self.make_smart_server('foo')
1071
transport = transport.clone('no-such-path')
1072
fmt = RemoteBzrDirFormat()
1073
err = self.assertRaises(errors.NoSuchFile,
1074
fmt.initialize_on_transport_ex, transport, create_prefix=False)
200
RemoteBzrDirFormat.probe_transport, OldServerTransport())
1077
203
class OldSmartClient(object):
1102
225
return OldSmartClient()
1105
class RemoteBzrDirTestCase(TestRemote):
1107
def make_remote_bzrdir(self, transport, client):
1108
"""Make a RemotebzrDir using 'client' as the _client."""
1109
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1113
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1115
def lock_remote_branch(self, branch):
1116
"""Trick a RemoteBranch into thinking it is locked."""
1117
branch._lock_mode = 'w'
1118
branch._lock_count = 2
1119
branch._lock_token = 'branch token'
1120
branch._repo_lock_token = 'repo token'
1121
branch.repository._lock_mode = 'w'
1122
branch.repository._lock_count = 2
1123
branch.repository._lock_token = 'repo token'
1125
def make_remote_branch(self, transport, client):
1126
"""Make a RemoteBranch using 'client' as its _SmartClient.
1128
A RemoteBzrDir and RemoteRepository will also be created to fill out
1129
the RemoteBranch, albeit with stub values for some of their attributes.
1131
# we do not want bzrdir to make any remote calls, so use False as its
1132
# _client. If it tries to make a remote call, this will fail
1134
bzrdir = self.make_remote_bzrdir(transport, False)
1135
repo = RemoteRepository(bzrdir, None, _client=client)
1136
branch_format = self.get_branch_format()
1137
format = RemoteBranchFormat(network_name=branch_format.network_name())
1138
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1141
class TestBranchBreakLock(RemoteBranchTestCase):
1143
def test_break_lock(self):
1144
transport_path = 'quack'
1145
transport = MemoryTransport()
1146
client = FakeClient(transport.base)
1147
client.add_expected_call(
1148
'Branch.get_stacked_on_url', ('quack/',),
1149
'error', ('NotStacked',))
1150
client.add_expected_call(
1151
'Branch.break_lock', ('quack/',),
1153
transport.mkdir('quack')
1154
transport = transport.clone('quack')
1155
branch = self.make_remote_branch(transport, client)
1157
self.assertFinished(client)
1160
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1162
def test_get_physical_lock_status_yes(self):
1163
transport = MemoryTransport()
1164
client = FakeClient(transport.base)
1165
client.add_expected_call(
1166
'Branch.get_stacked_on_url', ('quack/',),
1167
'error', ('NotStacked',))
1168
client.add_expected_call(
1169
'Branch.get_physical_lock_status', ('quack/',),
1170
'success', ('yes',))
1171
transport.mkdir('quack')
1172
transport = transport.clone('quack')
1173
branch = self.make_remote_branch(transport, client)
1174
result = branch.get_physical_lock_status()
1175
self.assertFinished(client)
1176
self.assertEqual(True, result)
1178
def test_get_physical_lock_status_no(self):
1179
transport = MemoryTransport()
1180
client = FakeClient(transport.base)
1181
client.add_expected_call(
1182
'Branch.get_stacked_on_url', ('quack/',),
1183
'error', ('NotStacked',))
1184
client.add_expected_call(
1185
'Branch.get_physical_lock_status', ('quack/',),
1187
transport.mkdir('quack')
1188
transport = transport.clone('quack')
1189
branch = self.make_remote_branch(transport, client)
1190
result = branch.get_physical_lock_status()
1191
self.assertFinished(client)
1192
self.assertEqual(False, result)
1195
class TestBranchGetParent(RemoteBranchTestCase):
1197
def test_no_parent(self):
1198
# in an empty branch we decode the response properly
1199
transport = MemoryTransport()
1200
client = FakeClient(transport.base)
1201
client.add_expected_call(
1202
'Branch.get_stacked_on_url', ('quack/',),
1203
'error', ('NotStacked',))
1204
client.add_expected_call(
1205
'Branch.get_parent', ('quack/',),
1207
transport.mkdir('quack')
1208
transport = transport.clone('quack')
1209
branch = self.make_remote_branch(transport, client)
1210
result = branch.get_parent()
1211
self.assertFinished(client)
1212
self.assertEqual(None, result)
1214
def test_parent_relative(self):
1215
transport = MemoryTransport()
1216
client = FakeClient(transport.base)
1217
client.add_expected_call(
1218
'Branch.get_stacked_on_url', ('kwaak/',),
1219
'error', ('NotStacked',))
1220
client.add_expected_call(
1221
'Branch.get_parent', ('kwaak/',),
1222
'success', ('../foo/',))
1223
transport.mkdir('kwaak')
1224
transport = transport.clone('kwaak')
1225
branch = self.make_remote_branch(transport, client)
1226
result = branch.get_parent()
1227
self.assertEqual(transport.clone('../foo').base, result)
1229
def test_parent_absolute(self):
1230
transport = MemoryTransport()
1231
client = FakeClient(transport.base)
1232
client.add_expected_call(
1233
'Branch.get_stacked_on_url', ('kwaak/',),
1234
'error', ('NotStacked',))
1235
client.add_expected_call(
1236
'Branch.get_parent', ('kwaak/',),
1237
'success', ('http://foo/',))
1238
transport.mkdir('kwaak')
1239
transport = transport.clone('kwaak')
1240
branch = self.make_remote_branch(transport, client)
1241
result = branch.get_parent()
1242
self.assertEqual('http://foo/', result)
1243
self.assertFinished(client)
1246
class TestBranchSetParentLocation(RemoteBranchTestCase):
1248
def test_no_parent(self):
1249
# We call the verb when setting parent to None
1250
transport = MemoryTransport()
1251
client = FakeClient(transport.base)
1252
client.add_expected_call(
1253
'Branch.get_stacked_on_url', ('quack/',),
1254
'error', ('NotStacked',))
1255
client.add_expected_call(
1256
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1258
transport.mkdir('quack')
1259
transport = transport.clone('quack')
1260
branch = self.make_remote_branch(transport, client)
1261
branch._lock_token = 'b'
1262
branch._repo_lock_token = 'r'
1263
branch._set_parent_location(None)
1264
self.assertFinished(client)
1266
def test_parent(self):
1267
transport = MemoryTransport()
1268
client = FakeClient(transport.base)
1269
client.add_expected_call(
1270
'Branch.get_stacked_on_url', ('kwaak/',),
1271
'error', ('NotStacked',))
1272
client.add_expected_call(
1273
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1275
transport.mkdir('kwaak')
1276
transport = transport.clone('kwaak')
1277
branch = self.make_remote_branch(transport, client)
1278
branch._lock_token = 'b'
1279
branch._repo_lock_token = 'r'
1280
branch._set_parent_location('foo')
1281
self.assertFinished(client)
1283
def test_backwards_compat(self):
1284
self.setup_smart_server_with_call_log()
1285
branch = self.make_branch('.')
1286
self.reset_smart_call_log()
1287
verb = 'Branch.set_parent_location'
1288
self.disable_verb(verb)
1289
branch.set_parent('http://foo/')
1290
self.assertLength(14, self.hpss_calls)
1293
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1295
def test_backwards_compat(self):
1296
self.setup_smart_server_with_call_log()
1297
branch = self.make_branch('.')
1298
self.reset_smart_call_log()
1299
verb = 'Branch.get_tags_bytes'
1300
self.disable_verb(verb)
1301
branch.tags.get_tag_dict()
1302
call_count = len([call for call in self.hpss_calls if
1303
call.call.method == verb])
1304
self.assertEqual(1, call_count)
1306
def test_trivial(self):
1307
transport = MemoryTransport()
1308
client = FakeClient(transport.base)
1309
client.add_expected_call(
1310
'Branch.get_stacked_on_url', ('quack/',),
1311
'error', ('NotStacked',))
1312
client.add_expected_call(
1313
'Branch.get_tags_bytes', ('quack/',),
1315
transport.mkdir('quack')
1316
transport = transport.clone('quack')
1317
branch = self.make_remote_branch(transport, client)
1318
result = branch.tags.get_tag_dict()
1319
self.assertFinished(client)
1320
self.assertEqual({}, result)
1323
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1325
def test_trivial(self):
1326
transport = MemoryTransport()
1327
client = FakeClient(transport.base)
1328
client.add_expected_call(
1329
'Branch.get_stacked_on_url', ('quack/',),
1330
'error', ('NotStacked',))
1331
client.add_expected_call(
1332
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1334
transport.mkdir('quack')
1335
transport = transport.clone('quack')
1336
branch = self.make_remote_branch(transport, client)
1337
self.lock_remote_branch(branch)
1338
branch._set_tags_bytes('tags bytes')
1339
self.assertFinished(client)
1340
self.assertEqual('tags bytes', client._calls[-1][-1])
1342
def test_backwards_compatible(self):
1343
transport = MemoryTransport()
1344
client = FakeClient(transport.base)
1345
client.add_expected_call(
1346
'Branch.get_stacked_on_url', ('quack/',),
1347
'error', ('NotStacked',))
1348
client.add_expected_call(
1349
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1350
'unknown', ('Branch.set_tags_bytes',))
1351
transport.mkdir('quack')
1352
transport = transport.clone('quack')
1353
branch = self.make_remote_branch(transport, client)
1354
self.lock_remote_branch(branch)
1355
class StubRealBranch(object):
1358
def _set_tags_bytes(self, bytes):
1359
self.calls.append(('set_tags_bytes', bytes))
1360
real_branch = StubRealBranch()
1361
branch._real_branch = real_branch
1362
branch._set_tags_bytes('tags bytes')
1363
# Call a second time, to exercise the 'remote version already inferred'
1365
branch._set_tags_bytes('tags bytes')
1366
self.assertFinished(client)
1368
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1371
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1373
def test_uses_last_revision_info_and_tags_by_default(self):
1374
transport = MemoryTransport()
1375
client = FakeClient(transport.base)
1376
client.add_expected_call(
1377
'Branch.get_stacked_on_url', ('quack/',),
1378
'error', ('NotStacked',))
1379
client.add_expected_call(
1380
'Branch.last_revision_info', ('quack/',),
1381
'success', ('ok', '1', 'rev-tip'))
1382
client.add_expected_call(
1383
'Branch.get_config_file', ('quack/',),
1384
'success', ('ok',), '')
1385
transport.mkdir('quack')
1386
transport = transport.clone('quack')
1387
branch = self.make_remote_branch(transport, client)
1388
result = branch.heads_to_fetch()
1389
self.assertFinished(client)
1390
self.assertEqual((set(['rev-tip']), set()), result)
1392
def test_uses_last_revision_info_and_tags_when_set(self):
1393
transport = MemoryTransport()
1394
client = FakeClient(transport.base)
1395
client.add_expected_call(
1396
'Branch.get_stacked_on_url', ('quack/',),
1397
'error', ('NotStacked',))
1398
client.add_expected_call(
1399
'Branch.last_revision_info', ('quack/',),
1400
'success', ('ok', '1', 'rev-tip'))
1401
client.add_expected_call(
1402
'Branch.get_config_file', ('quack/',),
1403
'success', ('ok',), 'branch.fetch_tags = True')
1404
# XXX: this will break if the default format's serialization of tags
1405
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1406
client.add_expected_call(
1407
'Branch.get_tags_bytes', ('quack/',),
1408
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1409
transport.mkdir('quack')
1410
transport = transport.clone('quack')
1411
branch = self.make_remote_branch(transport, client)
1412
result = branch.heads_to_fetch()
1413
self.assertFinished(client)
1415
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1417
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1418
transport = MemoryTransport()
1419
client = FakeClient(transport.base)
1420
client.add_expected_call(
1421
'Branch.get_stacked_on_url', ('quack/',),
1422
'error', ('NotStacked',))
1423
client.add_expected_call(
1424
'Branch.heads_to_fetch', ('quack/',),
1425
'success', (['tip'], ['tagged-1', 'tagged-2']))
1426
transport.mkdir('quack')
1427
transport = transport.clone('quack')
1428
branch = self.make_remote_branch(transport, client)
1429
branch._format._use_default_local_heads_to_fetch = lambda: False
1430
result = branch.heads_to_fetch()
1431
self.assertFinished(client)
1432
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1434
def make_branch_with_tags(self):
1435
self.setup_smart_server_with_call_log()
1436
# Make a branch with a single revision.
1437
builder = self.make_branch_builder('foo')
1438
builder.start_series()
1439
builder.build_snapshot('tip', None, [
1440
('add', ('', 'root-id', 'directory', ''))])
1441
builder.finish_series()
1442
branch = builder.get_branch()
1443
# Add two tags to that branch
1444
branch.tags.set_tag('tag-1', 'rev-1')
1445
branch.tags.set_tag('tag-2', 'rev-2')
1448
def test_backwards_compatible(self):
1449
br = self.make_branch_with_tags()
1450
br.get_config_stack().set('branch.fetch_tags', True)
1451
self.addCleanup(br.lock_read().unlock)
1452
# Disable the heads_to_fetch verb
1453
verb = 'Branch.heads_to_fetch'
1454
self.disable_verb(verb)
1455
self.reset_smart_call_log()
1456
result = br.heads_to_fetch()
1457
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1459
['Branch.last_revision_info', 'Branch.get_tags_bytes'],
1460
[call.call.method for call in self.hpss_calls])
1462
def test_backwards_compatible_no_tags(self):
1463
br = self.make_branch_with_tags()
1464
br.get_config_stack().set('branch.fetch_tags', False)
1465
self.addCleanup(br.lock_read().unlock)
1466
# Disable the heads_to_fetch verb
1467
verb = 'Branch.heads_to_fetch'
1468
self.disable_verb(verb)
1469
self.reset_smart_call_log()
1470
result = br.heads_to_fetch()
1471
self.assertEqual((set(['tip']), set()), result)
1473
['Branch.last_revision_info'],
1474
[call.call.method for call in self.hpss_calls])
1477
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
228
class TestBranchLastRevisionInfo(tests.TestCase):
1479
230
def test_empty_branch(self):
1480
231
# in an empty branch we decode the response properly
232
client = FakeClient([(('ok', '0', 'null:'), )])
1481
233
transport = MemoryTransport()
1482
client = FakeClient(transport.base)
1483
client.add_expected_call(
1484
'Branch.get_stacked_on_url', ('quack/',),
1485
'error', ('NotStacked',))
1486
client.add_expected_call(
1487
'Branch.last_revision_info', ('quack/',),
1488
'success', ('ok', '0', 'null:'))
1489
234
transport.mkdir('quack')
1490
235
transport = transport.clone('quack')
1491
branch = self.make_remote_branch(transport, client)
236
# we do not want bzrdir to make any remote calls
237
bzrdir = RemoteBzrDir(transport, _client=False)
238
branch = RemoteBranch(bzrdir, None, _client=client)
1492
239
result = branch.last_revision_info()
1493
self.assertFinished(client)
242
[('call', 'Branch.last_revision_info', ('///quack/',))],
1494
244
self.assertEqual((0, NULL_REVISION), result)
1496
246
def test_non_empty_branch(self):
1497
247
# in a non-empty branch we also decode the response properly
1498
248
revid = u'\xc8'.encode('utf8')
249
client = FakeClient([(('ok', '2', revid), )])
1499
250
transport = MemoryTransport()
1500
client = FakeClient(transport.base)
1501
client.add_expected_call(
1502
'Branch.get_stacked_on_url', ('kwaak/',),
1503
'error', ('NotStacked',))
1504
client.add_expected_call(
1505
'Branch.last_revision_info', ('kwaak/',),
1506
'success', ('ok', '2', revid))
1507
251
transport.mkdir('kwaak')
1508
252
transport = transport.clone('kwaak')
1509
branch = self.make_remote_branch(transport, client)
253
# we do not want bzrdir to make any remote calls
254
bzrdir = RemoteBzrDir(transport, _client=False)
255
branch = RemoteBranch(bzrdir, None, _client=client)
1510
256
result = branch.last_revision_info()
259
[('call', 'Branch.last_revision_info', ('///kwaak/',))],
1511
261
self.assertEqual((2, revid), result)
1514
class TestBranch_get_stacked_on_url(TestRemote):
1515
"""Test Branch._get_stacked_on_url rpc"""
1517
def test_get_stacked_on_invalid_url(self):
1518
# test that asking for a stacked on url the server can't access works.
1519
# This isn't perfect, but then as we're in the same process there
1520
# really isn't anything we can do to be 100% sure that the server
1521
# doesn't just open in - this test probably needs to be rewritten using
1522
# a spawn()ed server.
1523
stacked_branch = self.make_branch('stacked', format='1.9')
1524
memory_branch = self.make_branch('base', format='1.9')
1525
vfs_url = self.get_vfs_only_url('base')
1526
stacked_branch.set_stacked_on_url(vfs_url)
1527
transport = stacked_branch.bzrdir.root_transport
1528
client = FakeClient(transport.base)
1529
client.add_expected_call(
1530
'Branch.get_stacked_on_url', ('stacked/',),
1531
'success', ('ok', vfs_url))
1532
# XXX: Multiple calls are bad, this second call documents what is
1534
client.add_expected_call(
1535
'Branch.get_stacked_on_url', ('stacked/',),
1536
'success', ('ok', vfs_url))
1537
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1539
repo_fmt = remote.RemoteRepositoryFormat()
1540
repo_fmt._custom_format = stacked_branch.repository._format
1541
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1543
result = branch.get_stacked_on_url()
1544
self.assertEqual(vfs_url, result)
1546
def test_backwards_compatible(self):
1547
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1548
base_branch = self.make_branch('base', format='1.6')
1549
stacked_branch = self.make_branch('stacked', format='1.6')
1550
stacked_branch.set_stacked_on_url('../base')
1551
client = FakeClient(self.get_url())
1552
branch_network_name = self.get_branch_format().network_name()
1553
client.add_expected_call(
1554
'BzrDir.open_branchV3', ('stacked/',),
1555
'success', ('branch', branch_network_name))
1556
client.add_expected_call(
1557
'BzrDir.find_repositoryV3', ('stacked/',),
1558
'success', ('ok', '', 'no', 'no', 'yes',
1559
stacked_branch.repository._format.network_name()))
1560
# called twice, once from constructor and then again by us
1561
client.add_expected_call(
1562
'Branch.get_stacked_on_url', ('stacked/',),
1563
'unknown', ('Branch.get_stacked_on_url',))
1564
client.add_expected_call(
1565
'Branch.get_stacked_on_url', ('stacked/',),
1566
'unknown', ('Branch.get_stacked_on_url',))
1567
# this will also do vfs access, but that goes direct to the transport
1568
# and isn't seen by the FakeClient.
1569
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1570
RemoteBzrDirFormat(), _client=client)
1571
branch = bzrdir.open_branch()
1572
result = branch.get_stacked_on_url()
1573
self.assertEqual('../base', result)
1574
self.assertFinished(client)
1575
# it's in the fallback list both for the RemoteRepository and its vfs
1577
self.assertEqual(1, len(branch.repository._fallback_repositories))
1579
len(branch.repository._real_repository._fallback_repositories))
1581
def test_get_stacked_on_real_branch(self):
1582
base_branch = self.make_branch('base')
1583
stacked_branch = self.make_branch('stacked')
1584
stacked_branch.set_stacked_on_url('../base')
1585
reference_format = self.get_repo_format()
1586
network_name = reference_format.network_name()
1587
client = FakeClient(self.get_url())
1588
branch_network_name = self.get_branch_format().network_name()
1589
client.add_expected_call(
1590
'BzrDir.open_branchV3', ('stacked/',),
1591
'success', ('branch', branch_network_name))
1592
client.add_expected_call(
1593
'BzrDir.find_repositoryV3', ('stacked/',),
1594
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1595
# called twice, once from constructor and then again by us
1596
client.add_expected_call(
1597
'Branch.get_stacked_on_url', ('stacked/',),
1598
'success', ('ok', '../base'))
1599
client.add_expected_call(
1600
'Branch.get_stacked_on_url', ('stacked/',),
1601
'success', ('ok', '../base'))
1602
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1603
RemoteBzrDirFormat(), _client=client)
1604
branch = bzrdir.open_branch()
1605
result = branch.get_stacked_on_url()
1606
self.assertEqual('../base', result)
1607
self.assertFinished(client)
1608
# it's in the fallback list both for the RemoteRepository.
1609
self.assertEqual(1, len(branch.repository._fallback_repositories))
1610
# And we haven't had to construct a real repository.
1611
self.assertEqual(None, branch.repository._real_repository)
1614
class TestBranchSetLastRevision(RemoteBranchTestCase):
264
class TestBranchSetLastRevision(tests.TestCase):
1616
266
def test_set_empty(self):
1617
# _set_last_revision_info('null:') is translated to calling
267
# set_revision_history([]) is translated to calling
1618
268
# Branch.set_last_revision(path, '') on the wire.
269
client = FakeClient([
271
(('ok', 'branch token', 'repo token'), ),
1619
276
transport = MemoryTransport()
1620
277
transport.mkdir('branch')
1621
278
transport = transport.clone('branch')
1623
client = FakeClient(transport.base)
1624
client.add_expected_call(
1625
'Branch.get_stacked_on_url', ('branch/',),
1626
'error', ('NotStacked',))
1627
client.add_expected_call(
1628
'Branch.lock_write', ('branch/', '', ''),
1629
'success', ('ok', 'branch token', 'repo token'))
1630
client.add_expected_call(
1631
'Branch.last_revision_info',
1633
'success', ('ok', '0', 'null:'))
1634
client.add_expected_call(
1635
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1637
client.add_expected_call(
1638
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1640
branch = self.make_remote_branch(transport, client)
280
bzrdir = RemoteBzrDir(transport, _client=False)
281
branch = RemoteBranch(bzrdir, None, _client=client)
282
# This is a hack to work around the problem that RemoteBranch currently
283
# unnecessarily invokes _ensure_real upon a call to lock_write.
284
branch._ensure_real = lambda: None
1641
285
branch.lock_write()
1642
result = branch._set_last_revision(NULL_REVISION)
287
result = branch.set_revision_history([])
289
[('call', 'Branch.set_last_revision',
290
('///branch/', 'branch token', 'repo token', 'null:'))],
1644
293
self.assertEqual(None, result)
1645
self.assertFinished(client)
1647
295
def test_set_nonempty(self):
1648
# set_last_revision_info(N, rev-idN) is translated to calling
296
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1649
297
# Branch.set_last_revision(path, rev-idN) on the wire.
1650
transport = MemoryTransport()
1651
transport.mkdir('branch')
1652
transport = transport.clone('branch')
1654
client = FakeClient(transport.base)
1655
client.add_expected_call(
1656
'Branch.get_stacked_on_url', ('branch/',),
1657
'error', ('NotStacked',))
1658
client.add_expected_call(
1659
'Branch.lock_write', ('branch/', '', ''),
1660
'success', ('ok', 'branch token', 'repo token'))
1661
client.add_expected_call(
1662
'Branch.last_revision_info',
1664
'success', ('ok', '0', 'null:'))
1666
encoded_body = bz2.compress('\n'.join(lines))
1667
client.add_success_response_with_body(encoded_body, 'ok')
1668
client.add_expected_call(
1669
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1671
client.add_expected_call(
1672
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1674
branch = self.make_remote_branch(transport, client)
1675
# Lock the branch, reset the record of remote calls.
1677
result = branch._set_last_revision('rev-id2')
1679
self.assertEqual(None, result)
1680
self.assertFinished(client)
1682
def test_no_such_revision(self):
1683
transport = MemoryTransport()
1684
transport.mkdir('branch')
1685
transport = transport.clone('branch')
1686
# A response of 'NoSuchRevision' is translated into an exception.
1687
client = FakeClient(transport.base)
1688
client.add_expected_call(
1689
'Branch.get_stacked_on_url', ('branch/',),
1690
'error', ('NotStacked',))
1691
client.add_expected_call(
1692
'Branch.lock_write', ('branch/', '', ''),
1693
'success', ('ok', 'branch token', 'repo token'))
1694
client.add_expected_call(
1695
'Branch.last_revision_info',
1697
'success', ('ok', '0', 'null:'))
1698
# get_graph calls to construct the revision history, for the set_rh
1701
encoded_body = bz2.compress('\n'.join(lines))
1702
client.add_success_response_with_body(encoded_body, 'ok')
1703
client.add_expected_call(
1704
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1705
'error', ('NoSuchRevision', 'rev-id'))
1706
client.add_expected_call(
1707
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1710
branch = self.make_remote_branch(transport, client)
1713
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1715
self.assertFinished(client)
1717
def test_tip_change_rejected(self):
1718
"""TipChangeRejected responses cause a TipChangeRejected exception to
1721
transport = MemoryTransport()
1722
transport.mkdir('branch')
1723
transport = transport.clone('branch')
1724
client = FakeClient(transport.base)
1725
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1726
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1727
client.add_expected_call(
1728
'Branch.get_stacked_on_url', ('branch/',),
1729
'error', ('NotStacked',))
1730
client.add_expected_call(
1731
'Branch.lock_write', ('branch/', '', ''),
1732
'success', ('ok', 'branch token', 'repo token'))
1733
client.add_expected_call(
1734
'Branch.last_revision_info',
1736
'success', ('ok', '0', 'null:'))
1738
encoded_body = bz2.compress('\n'.join(lines))
1739
client.add_success_response_with_body(encoded_body, 'ok')
1740
client.add_expected_call(
1741
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1742
'error', ('TipChangeRejected', rejection_msg_utf8))
1743
client.add_expected_call(
1744
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1746
branch = self.make_remote_branch(transport, client)
1748
# The 'TipChangeRejected' error response triggered by calling
1749
# set_last_revision_info causes a TipChangeRejected exception.
1750
err = self.assertRaises(
1751
errors.TipChangeRejected,
1752
branch._set_last_revision, 'rev-id')
1753
# The UTF-8 message from the response has been decoded into a unicode
1755
self.assertIsInstance(err.msg, unicode)
1756
self.assertEqual(rejection_msg_unicode, err.msg)
1758
self.assertFinished(client)
1761
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1763
def test_set_last_revision_info(self):
1764
# set_last_revision_info(num, 'rev-id') is translated to calling
1765
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1766
transport = MemoryTransport()
1767
transport.mkdir('branch')
1768
transport = transport.clone('branch')
1769
client = FakeClient(transport.base)
1770
# get_stacked_on_url
1771
client.add_error_response('NotStacked')
1773
client.add_success_response('ok', 'branch token', 'repo token')
1774
# query the current revision
1775
client.add_success_response('ok', '0', 'null:')
1777
client.add_success_response('ok')
1779
client.add_success_response('ok')
1781
branch = self.make_remote_branch(transport, client)
1782
# Lock the branch, reset the record of remote calls.
1785
result = branch.set_last_revision_info(1234, 'a-revision-id')
1787
[('call', 'Branch.last_revision_info', ('branch/',)),
1788
('call', 'Branch.set_last_revision_info',
1789
('branch/', 'branch token', 'repo token',
1790
'1234', 'a-revision-id'))],
1792
self.assertEqual(None, result)
1794
def test_no_such_revision(self):
1795
# A response of 'NoSuchRevision' is translated into an exception.
1796
transport = MemoryTransport()
1797
transport.mkdir('branch')
1798
transport = transport.clone('branch')
1799
client = FakeClient(transport.base)
1800
# get_stacked_on_url
1801
client.add_error_response('NotStacked')
1803
client.add_success_response('ok', 'branch token', 'repo token')
1805
client.add_error_response('NoSuchRevision', 'revid')
1807
client.add_success_response('ok')
1809
branch = self.make_remote_branch(transport, client)
1810
# Lock the branch, reset the record of remote calls.
1815
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1818
def test_backwards_compatibility(self):
1819
"""If the server does not support the Branch.set_last_revision_info
1820
verb (which is new in 1.4), then the client falls back to VFS methods.
1822
# This test is a little messy. Unlike most tests in this file, it
1823
# doesn't purely test what a Remote* object sends over the wire, and
1824
# how it reacts to responses from the wire. It instead relies partly
1825
# on asserting that the RemoteBranch will call
1826
# self._real_branch.set_last_revision_info(...).
1828
# First, set up our RemoteBranch with a FakeClient that raises
1829
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1830
transport = MemoryTransport()
1831
transport.mkdir('branch')
1832
transport = transport.clone('branch')
1833
client = FakeClient(transport.base)
1834
client.add_expected_call(
1835
'Branch.get_stacked_on_url', ('branch/',),
1836
'error', ('NotStacked',))
1837
client.add_expected_call(
1838
'Branch.last_revision_info',
1840
'success', ('ok', '0', 'null:'))
1841
client.add_expected_call(
1842
'Branch.set_last_revision_info',
1843
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1844
'unknown', 'Branch.set_last_revision_info')
1846
branch = self.make_remote_branch(transport, client)
1847
class StubRealBranch(object):
1850
def set_last_revision_info(self, revno, revision_id):
1852
('set_last_revision_info', revno, revision_id))
1853
def _clear_cached_state(self):
1855
real_branch = StubRealBranch()
1856
branch._real_branch = real_branch
1857
self.lock_remote_branch(branch)
1859
# Call set_last_revision_info, and verify it behaved as expected.
1860
result = branch.set_last_revision_info(1234, 'a-revision-id')
1862
[('set_last_revision_info', 1234, 'a-revision-id')],
1864
self.assertFinished(client)
1866
def test_unexpected_error(self):
1867
# If the server sends an error the client doesn't understand, it gets
1868
# turned into an UnknownErrorFromSmartServer, which is presented as a
1869
# non-internal error to the user.
1870
transport = MemoryTransport()
1871
transport.mkdir('branch')
1872
transport = transport.clone('branch')
1873
client = FakeClient(transport.base)
1874
# get_stacked_on_url
1875
client.add_error_response('NotStacked')
1877
client.add_success_response('ok', 'branch token', 'repo token')
1879
client.add_error_response('UnexpectedError')
1881
client.add_success_response('ok')
1883
branch = self.make_remote_branch(transport, client)
1884
# Lock the branch, reset the record of remote calls.
1888
err = self.assertRaises(
1889
errors.UnknownErrorFromSmartServer,
1890
branch.set_last_revision_info, 123, 'revid')
1891
self.assertEqual(('UnexpectedError',), err.error_tuple)
1894
def test_tip_change_rejected(self):
1895
"""TipChangeRejected responses cause a TipChangeRejected exception to
1898
transport = MemoryTransport()
1899
transport.mkdir('branch')
1900
transport = transport.clone('branch')
1901
client = FakeClient(transport.base)
1902
# get_stacked_on_url
1903
client.add_error_response('NotStacked')
1905
client.add_success_response('ok', 'branch token', 'repo token')
1907
client.add_error_response('TipChangeRejected', 'rejection message')
1909
client.add_success_response('ok')
1911
branch = self.make_remote_branch(transport, client)
1912
# Lock the branch, reset the record of remote calls.
1914
self.addCleanup(branch.unlock)
1917
# The 'TipChangeRejected' error response triggered by calling
1918
# set_last_revision_info causes a TipChangeRejected exception.
1919
err = self.assertRaises(
1920
errors.TipChangeRejected,
1921
branch.set_last_revision_info, 123, 'revid')
1922
self.assertEqual('rejection message', err.msg)
1925
class TestBranchGetSetConfig(RemoteBranchTestCase):
1927
def test_get_branch_conf(self):
1928
# in an empty branch we decode the response properly
1929
client = FakeClient()
1930
client.add_expected_call(
1931
'Branch.get_stacked_on_url', ('memory:///',),
1932
'error', ('NotStacked',),)
1933
client.add_success_response_with_body('# config file body', 'ok')
1934
transport = MemoryTransport()
1935
branch = self.make_remote_branch(transport, client)
1936
config = branch.get_config()
1937
config.has_explicit_nickname()
1939
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1940
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1943
def test_get_multi_line_branch_conf(self):
1944
# Make sure that multiple-line branch.conf files are supported
1946
# https://bugs.launchpad.net/bzr/+bug/354075
1947
client = FakeClient()
1948
client.add_expected_call(
1949
'Branch.get_stacked_on_url', ('memory:///',),
1950
'error', ('NotStacked',),)
1951
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1952
transport = MemoryTransport()
1953
branch = self.make_remote_branch(transport, client)
1954
config = branch.get_config()
1955
self.assertEqual(u'2', config.get_user_option('b'))
1957
def test_set_option(self):
1958
client = FakeClient()
1959
client.add_expected_call(
1960
'Branch.get_stacked_on_url', ('memory:///',),
1961
'error', ('NotStacked',),)
1962
client.add_expected_call(
1963
'Branch.lock_write', ('memory:///', '', ''),
1964
'success', ('ok', 'branch token', 'repo token'))
1965
client.add_expected_call(
1966
'Branch.set_config_option', ('memory:///', 'branch token',
1967
'repo token', 'foo', 'bar', ''),
1969
client.add_expected_call(
1970
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1972
transport = MemoryTransport()
1973
branch = self.make_remote_branch(transport, client)
1975
config = branch._get_config()
1976
config.set_option('foo', 'bar')
1978
self.assertFinished(client)
1980
def test_set_option_with_dict(self):
1981
client = FakeClient()
1982
client.add_expected_call(
1983
'Branch.get_stacked_on_url', ('memory:///',),
1984
'error', ('NotStacked',),)
1985
client.add_expected_call(
1986
'Branch.lock_write', ('memory:///', '', ''),
1987
'success', ('ok', 'branch token', 'repo token'))
1988
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1989
client.add_expected_call(
1990
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1991
'repo token', encoded_dict_value, 'foo', ''),
1993
client.add_expected_call(
1994
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1996
transport = MemoryTransport()
1997
branch = self.make_remote_branch(transport, client)
1999
config = branch._get_config()
2001
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
2004
self.assertFinished(client)
2006
def test_backwards_compat_set_option(self):
2007
self.setup_smart_server_with_call_log()
2008
branch = self.make_branch('.')
2009
verb = 'Branch.set_config_option'
2010
self.disable_verb(verb)
2012
self.addCleanup(branch.unlock)
2013
self.reset_smart_call_log()
2014
branch._get_config().set_option('value', 'name')
2015
self.assertLength(11, self.hpss_calls)
2016
self.assertEqual('value', branch._get_config().get_option('name'))
2018
def test_backwards_compat_set_option_with_dict(self):
2019
self.setup_smart_server_with_call_log()
2020
branch = self.make_branch('.')
2021
verb = 'Branch.set_config_option_dict'
2022
self.disable_verb(verb)
2024
self.addCleanup(branch.unlock)
2025
self.reset_smart_call_log()
2026
config = branch._get_config()
2027
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2028
config.set_option(value_dict, 'name')
2029
self.assertLength(11, self.hpss_calls)
2030
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2033
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2035
def test_get_branch_conf(self):
2036
# in an empty branch we decode the response properly
2037
client = FakeClient()
2038
client.add_expected_call(
2039
'Branch.get_stacked_on_url', ('memory:///',),
2040
'error', ('NotStacked',),)
2041
client.add_success_response_with_body('# config file body', 'ok')
2042
transport = MemoryTransport()
2043
branch = self.make_remote_branch(transport, client)
2044
config = branch.get_config_stack()
2046
config.get("log_format")
2048
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2049
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
2052
def test_set_branch_conf(self):
2053
client = FakeClient()
2054
client.add_expected_call(
2055
'Branch.get_stacked_on_url', ('memory:///',),
2056
'error', ('NotStacked',),)
2057
client.add_expected_call(
2058
'Branch.lock_write', ('memory:///', '', ''),
2059
'success', ('ok', 'branch token', 'repo token'))
2060
client.add_expected_call(
2061
'Branch.get_config_file', ('memory:///', ),
2062
'success', ('ok', ), "# line 1\n")
2063
client.add_expected_call(
2064
'Branch.get_config_file', ('memory:///', ),
2065
'success', ('ok', ), "# line 1\n")
2066
client.add_expected_call(
2067
'Branch.put_config_file', ('memory:///', 'branch token',
2070
client.add_expected_call(
2071
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2073
transport = MemoryTransport()
2074
branch = self.make_remote_branch(transport, client)
2076
config = branch.get_config_stack()
2077
config.set('email', 'The Dude <lebowski@example.com>')
2079
self.assertFinished(client)
2081
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2082
('call', 'Branch.lock_write', ('memory:///', '', '')),
2083
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2084
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2085
('call_with_body_bytes_expecting_body', 'Branch.put_config_file',
2086
('memory:///', 'branch token', 'repo token'),
2087
'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2088
('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
2092
class TestBranchLockWrite(RemoteBranchTestCase):
298
client = FakeClient([
300
(('ok', 'branch token', 'repo token'), ),
305
transport = MemoryTransport()
306
transport.mkdir('branch')
307
transport = transport.clone('branch')
309
bzrdir = RemoteBzrDir(transport, _client=False)
310
branch = RemoteBranch(bzrdir, None, _client=client)
311
# This is a hack to work around the problem that RemoteBranch currently
312
# unnecessarily invokes _ensure_real upon a call to lock_write.
313
branch._ensure_real = lambda: None
314
# Lock the branch, reset the record of remote calls.
318
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
320
[('call', 'Branch.set_last_revision',
321
('///branch/', 'branch token', 'repo token', 'rev-id2'))],
324
self.assertEqual(None, result)
326
def test_no_such_revision(self):
327
# A response of 'NoSuchRevision' is translated into an exception.
328
client = FakeClient([
330
(('ok', 'branch token', 'repo token'), ),
332
(('NoSuchRevision', 'rev-id'), ),
335
transport = MemoryTransport()
336
transport.mkdir('branch')
337
transport = transport.clone('branch')
339
bzrdir = RemoteBzrDir(transport, _client=False)
340
branch = RemoteBranch(bzrdir, None, _client=client)
341
branch._ensure_real = lambda: None
346
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
350
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
351
"""Test branch.control_files api munging...
353
We special case RemoteBranch.control_files.get('branch.conf') to
354
call a specific API so that RemoteBranch's can intercept configuration
355
file reading, allowing them to signal to the client about things like
356
'email is configured for commits'.
359
def test_get_branch_conf(self):
360
# in an empty branch we decode the response properly
361
client = FakeClient([(('ok', ), 'config file body')])
362
# we need to make a real branch because the remote_branch.control_files
363
# will trigger _ensure_real.
364
branch = self.make_branch('quack')
365
transport = branch.bzrdir.root_transport
366
# we do not want bzrdir to make any remote calls
367
bzrdir = RemoteBzrDir(transport, _client=False)
368
branch = RemoteBranch(bzrdir, None, _client=client)
369
result = branch.control_files.get('branch.conf')
371
[('call_expecting_body', 'Branch.get_config_file', ('///quack/',))],
373
self.assertEqual('config file body', result.read())
376
class TestBranchLockWrite(tests.TestCase):
2094
378
def test_lock_write_unlockable(self):
379
client = FakeClient([(('UnlockableTransport', ), '')])
2095
380
transport = MemoryTransport()
2096
client = FakeClient(transport.base)
2097
client.add_expected_call(
2098
'Branch.get_stacked_on_url', ('quack/',),
2099
'error', ('NotStacked',),)
2100
client.add_expected_call(
2101
'Branch.lock_write', ('quack/', '', ''),
2102
'error', ('UnlockableTransport',))
2103
381
transport.mkdir('quack')
2104
382
transport = transport.clone('quack')
2105
branch = self.make_remote_branch(transport, client)
383
# we do not want bzrdir to make any remote calls
384
bzrdir = RemoteBzrDir(transport, _client=False)
385
branch = RemoteBranch(bzrdir, None, _client=client)
2106
386
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2107
self.assertFinished(client)
2110
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2112
def test_simple(self):
2113
transport = MemoryTransport()
2114
client = FakeClient(transport.base)
2115
client.add_expected_call(
2116
'Branch.get_stacked_on_url', ('quack/',),
2117
'error', ('NotStacked',),)
2118
client.add_expected_call(
2119
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2120
'success', ('ok', '0',),)
2121
client.add_expected_call(
2122
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2123
'error', ('NoSuchRevision', 'unknown',),)
2124
transport.mkdir('quack')
2125
transport = transport.clone('quack')
2126
branch = self.make_remote_branch(transport, client)
2127
self.assertEquals(0, branch.revision_id_to_revno('null:'))
2128
self.assertRaises(errors.NoSuchRevision,
2129
branch.revision_id_to_revno, 'unknown')
2130
self.assertFinished(client)
2132
def test_dotted(self):
2133
transport = MemoryTransport()
2134
client = FakeClient(transport.base)
2135
client.add_expected_call(
2136
'Branch.get_stacked_on_url', ('quack/',),
2137
'error', ('NotStacked',),)
2138
client.add_expected_call(
2139
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2140
'success', ('ok', '0',),)
2141
client.add_expected_call(
2142
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2143
'error', ('NoSuchRevision', 'unknown',),)
2144
transport.mkdir('quack')
2145
transport = transport.clone('quack')
2146
branch = self.make_remote_branch(transport, client)
2147
self.assertEquals((0, ), branch.revision_id_to_dotted_revno('null:'))
2148
self.assertRaises(errors.NoSuchRevision,
2149
branch.revision_id_to_dotted_revno, 'unknown')
2150
self.assertFinished(client)
2152
def test_dotted_no_smart_verb(self):
2153
self.setup_smart_server_with_call_log()
2154
branch = self.make_branch('.')
2155
self.disable_verb('Branch.revision_id_to_revno')
2156
self.reset_smart_call_log()
2157
self.assertEquals((0, ),
2158
branch.revision_id_to_dotted_revno('null:'))
2159
self.assertLength(8, self.hpss_calls)
2162
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2164
def test__get_config(self):
2165
client = FakeClient()
2166
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
2167
transport = MemoryTransport()
2168
bzrdir = self.make_remote_bzrdir(transport, client)
2169
config = bzrdir.get_config()
2170
self.assertEqual('/', config.get_default_stack_on())
2171
387
self.assertEqual(
2172
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
388
[('call', 'Branch.lock_write', ('///quack/', '', ''))],
2175
def test_set_option_uses_vfs(self):
2176
self.setup_smart_server_with_call_log()
2177
bzrdir = self.make_bzrdir('.')
2178
self.reset_smart_call_log()
2179
config = bzrdir.get_config()
2180
config.set_default_stack_on('/')
2181
self.assertLength(4, self.hpss_calls)
2183
def test_backwards_compat_get_option(self):
2184
self.setup_smart_server_with_call_log()
2185
bzrdir = self.make_bzrdir('.')
2186
verb = 'BzrDir.get_config_file'
2187
self.disable_verb(verb)
2188
self.reset_smart_call_log()
2189
self.assertEqual(None,
2190
bzrdir._get_config().get_option('default_stack_on'))
2191
self.assertLength(4, self.hpss_calls)
2194
392
class TestTransportIsReadonly(tests.TestCase):
2196
394
def test_true(self):
2197
client = FakeClient()
2198
client.add_success_response('yes')
395
client = FakeClient([(('yes',), '')])
2199
396
transport = RemoteTransport('bzr://example.com/', medium=False,
2201
398
self.assertEqual(True, transport.is_readonly())
2407
class TestRepositoryBreakLock(TestRemoteRepository):
2409
def test_break_lock(self):
2410
transport_path = 'quack'
2411
repo, client = self.setup_fake_client_and_repository(transport_path)
2412
client.add_success_response('ok')
2415
[('call', 'Repository.break_lock', ('quack/',))],
2419
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2421
def test_get_serializer_format(self):
2422
transport_path = 'hill'
2423
repo, client = self.setup_fake_client_and_repository(transport_path)
2424
client.add_success_response('ok', '7')
2425
self.assertEquals('7', repo.get_serializer_format())
2427
[('call', 'VersionedFileRepository.get_serializer_format',
2432
class TestRepositoryReconcile(TestRemoteRepository):
2434
def test_reconcile(self):
2435
transport_path = 'hill'
2436
repo, client = self.setup_fake_client_and_repository(transport_path)
2437
body = ("garbage_inventories: 2\n"
2438
"inconsistent_parents: 3\n")
2439
client.add_expected_call(
2440
'Repository.lock_write', ('hill/', ''),
2441
'success', ('ok', 'a token'))
2442
client.add_success_response_with_body(body, 'ok')
2443
reconciler = repo.reconcile()
2445
[('call', 'Repository.lock_write', ('hill/', '')),
2446
('call_expecting_body', 'Repository.reconcile',
2447
('hill/', 'a token'))],
2449
self.assertEquals(2, reconciler.garbage_inventories)
2450
self.assertEquals(3, reconciler.inconsistent_parents)
2453
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2455
def test_text(self):
2456
# ('ok',), body with signature text
2457
transport_path = 'quack'
2458
repo, client = self.setup_fake_client_and_repository(transport_path)
2459
client.add_success_response_with_body(
2461
self.assertEquals("THETEXT", repo.get_signature_text("revid"))
2463
[('call_expecting_body', 'Repository.get_revision_signature_text',
2464
('quack/', 'revid'))],
2467
def test_no_signature(self):
2468
transport_path = 'quick'
2469
repo, client = self.setup_fake_client_and_repository(transport_path)
2470
client.add_error_response('nosuchrevision', 'unknown')
2471
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2474
[('call_expecting_body', 'Repository.get_revision_signature_text',
2475
('quick/', 'unknown'))],
2479
class TestRepositoryGetGraph(TestRemoteRepository):
2481
def test_get_graph(self):
2482
# get_graph returns a graph with a custom parents provider.
2483
transport_path = 'quack'
2484
repo, client = self.setup_fake_client_and_repository(transport_path)
2485
graph = repo.get_graph()
2486
self.assertNotEqual(graph._parents_provider, repo)
2489
class TestRepositoryAddSignatureText(TestRemoteRepository):
2491
def test_add_signature_text(self):
2492
transport_path = 'quack'
2493
repo, client = self.setup_fake_client_and_repository(transport_path)
2494
client.add_expected_call(
2495
'Repository.lock_write', ('quack/', ''),
2496
'success', ('ok', 'a token'))
2497
client.add_expected_call(
2498
'Repository.start_write_group', ('quack/', 'a token'),
2499
'success', ('ok', ('token1', )))
2500
client.add_expected_call(
2501
'Repository.add_signature_text', ('quack/', 'a token', 'rev1',
2503
'success', ('ok', ), None)
2505
repo.start_write_group()
2507
repo.add_signature_text("rev1", "every bloody emperor"))
2509
('call_with_body_bytes_expecting_body',
2510
'Repository.add_signature_text',
2511
('quack/', 'a token', 'rev1', 'token1'),
2512
'every bloody emperor'),
2516
class TestRepositoryGetParentMap(TestRemoteRepository):
2518
def test_get_parent_map_caching(self):
2519
# get_parent_map returns from cache until unlock()
2520
# setup a reponse with two revisions
2521
r1 = u'\u0e33'.encode('utf8')
2522
r2 = u'\u0dab'.encode('utf8')
2523
lines = [' '.join([r2, r1]), r1]
2524
encoded_body = bz2.compress('\n'.join(lines))
2526
transport_path = 'quack'
2527
repo, client = self.setup_fake_client_and_repository(transport_path)
2528
client.add_success_response_with_body(encoded_body, 'ok')
2529
client.add_success_response_with_body(encoded_body, 'ok')
2531
graph = repo.get_graph()
2532
parents = graph.get_parent_map([r2])
2533
self.assertEqual({r2: (r1,)}, parents)
2534
# locking and unlocking deeper should not reset
2537
parents = graph.get_parent_map([r1])
2538
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2540
[('call_with_body_bytes_expecting_body',
2541
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2545
# now we call again, and it should use the second response.
2547
graph = repo.get_graph()
2548
parents = graph.get_parent_map([r1])
2549
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2551
[('call_with_body_bytes_expecting_body',
2552
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2554
('call_with_body_bytes_expecting_body',
2555
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2561
def test_get_parent_map_reconnects_if_unknown_method(self):
2562
transport_path = 'quack'
2563
rev_id = 'revision-id'
2564
repo, client = self.setup_fake_client_and_repository(transport_path)
2565
client.add_unknown_method_response('Repository.get_parent_map')
2566
client.add_success_response_with_body(rev_id, 'ok')
2567
self.assertFalse(client._medium._is_remote_before((1, 2)))
2568
parents = repo.get_parent_map([rev_id])
2570
[('call_with_body_bytes_expecting_body',
2571
'Repository.get_parent_map',
2572
('quack/', 'include-missing:', rev_id), '\n\n0'),
2573
('disconnect medium',),
2574
('call_expecting_body', 'Repository.get_revision_graph',
2577
# The medium is now marked as being connected to an older server
2578
self.assertTrue(client._medium._is_remote_before((1, 2)))
2579
self.assertEqual({rev_id: ('null:',)}, parents)
2581
def test_get_parent_map_fallback_parentless_node(self):
2582
"""get_parent_map falls back to get_revision_graph on old servers. The
2583
results from get_revision_graph are tweaked to match the get_parent_map
2586
Specifically, a {key: ()} result from get_revision_graph means "no
2587
parents" for that key, which in get_parent_map results should be
2588
represented as {key: ('null:',)}.
2590
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2592
rev_id = 'revision-id'
2593
transport_path = 'quack'
2594
repo, client = self.setup_fake_client_and_repository(transport_path)
2595
client.add_success_response_with_body(rev_id, 'ok')
2596
client._medium._remember_remote_is_before((1, 2))
2597
parents = repo.get_parent_map([rev_id])
2599
[('call_expecting_body', 'Repository.get_revision_graph',
2602
self.assertEqual({rev_id: ('null:',)}, parents)
2604
def test_get_parent_map_unexpected_response(self):
2605
repo, client = self.setup_fake_client_and_repository('path')
2606
client.add_success_response('something unexpected!')
2608
errors.UnexpectedSmartServerResponse,
2609
repo.get_parent_map, ['a-revision-id'])
2611
def test_get_parent_map_negative_caches_missing_keys(self):
2612
self.setup_smart_server_with_call_log()
2613
repo = self.make_repository('foo')
2614
self.assertIsInstance(repo, RemoteRepository)
2616
self.addCleanup(repo.unlock)
2617
self.reset_smart_call_log()
2618
graph = repo.get_graph()
2619
self.assertEqual({},
2620
graph.get_parent_map(['some-missing', 'other-missing']))
2621
self.assertLength(1, self.hpss_calls)
2622
# No call if we repeat this
2623
self.reset_smart_call_log()
2624
graph = repo.get_graph()
2625
self.assertEqual({},
2626
graph.get_parent_map(['some-missing', 'other-missing']))
2627
self.assertLength(0, self.hpss_calls)
2628
# Asking for more unknown keys makes a request.
2629
self.reset_smart_call_log()
2630
graph = repo.get_graph()
2631
self.assertEqual({},
2632
graph.get_parent_map(['some-missing', 'other-missing',
2634
self.assertLength(1, self.hpss_calls)
2636
def disableExtraResults(self):
2637
self.overrideAttr(SmartServerRepositoryGetParentMap,
2638
'no_extra_results', True)
2640
def test_null_cached_missing_and_stop_key(self):
2641
self.setup_smart_server_with_call_log()
2642
# Make a branch with a single revision.
2643
builder = self.make_branch_builder('foo')
2644
builder.start_series()
2645
builder.build_snapshot('first', None, [
2646
('add', ('', 'root-id', 'directory', ''))])
2647
builder.finish_series()
2648
branch = builder.get_branch()
2649
repo = branch.repository
2650
self.assertIsInstance(repo, RemoteRepository)
2651
# Stop the server from sending extra results.
2652
self.disableExtraResults()
2654
self.addCleanup(repo.unlock)
2655
self.reset_smart_call_log()
2656
graph = repo.get_graph()
2657
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2658
# 'first' it will be a candidate for the stop_keys of subsequent
2659
# requests, and because 'null:' was queried but not returned it will be
2660
# cached as missing.
2661
self.assertEqual({'first': ('null:',)},
2662
graph.get_parent_map(['first', 'null:']))
2663
# Now query for another key. This request will pass along a recipe of
2664
# start and stop keys describing the already cached results, and this
2665
# recipe's revision count must be correct (or else it will trigger an
2666
# error from the server).
2667
self.assertEqual({}, graph.get_parent_map(['another-key']))
2668
# This assertion guards against disableExtraResults silently failing to
2669
# work, thus invalidating the test.
2670
self.assertLength(2, self.hpss_calls)
2672
def test_get_parent_map_gets_ghosts_from_result(self):
2673
# asking for a revision should negatively cache close ghosts in its
2675
self.setup_smart_server_with_call_log()
2676
tree = self.make_branch_and_memory_tree('foo')
2679
builder = treebuilder.TreeBuilder()
2680
builder.start_tree(tree)
2682
builder.finish_tree()
2683
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2684
rev_id = tree.commit('')
2688
self.addCleanup(tree.unlock)
2689
repo = tree.branch.repository
2690
self.assertIsInstance(repo, RemoteRepository)
2692
repo.get_parent_map([rev_id])
2693
self.reset_smart_call_log()
2694
# Now asking for rev_id's ghost parent should not make calls
2695
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2696
self.assertLength(0, self.hpss_calls)
2698
def test_exposes_get_cached_parent_map(self):
2699
"""RemoteRepository exposes get_cached_parent_map from
2702
r1 = u'\u0e33'.encode('utf8')
2703
r2 = u'\u0dab'.encode('utf8')
2704
lines = [' '.join([r2, r1]), r1]
2705
encoded_body = bz2.compress('\n'.join(lines))
2707
transport_path = 'quack'
2708
repo, client = self.setup_fake_client_and_repository(transport_path)
2709
client.add_success_response_with_body(encoded_body, 'ok')
2711
# get_cached_parent_map should *not* trigger an RPC
2712
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2713
self.assertEqual([], client._calls)
2714
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2715
self.assertEqual({r1: (NULL_REVISION,)},
2716
repo.get_cached_parent_map([r1]))
2718
[('call_with_body_bytes_expecting_body',
2719
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2725
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2727
def test_allows_new_revisions(self):
2728
"""get_parent_map's results can be updated by commit."""
2729
smart_server = test_server.SmartTCPServer_for_testing()
2730
self.start_server(smart_server)
2731
self.make_branch('branch')
2732
branch = Branch.open(smart_server.get_url() + '/branch')
2733
tree = branch.create_checkout('tree', lightweight=True)
2735
self.addCleanup(tree.unlock)
2736
graph = tree.branch.repository.get_graph()
2737
# This provides an opportunity for the missing rev-id to be cached.
2738
self.assertEqual({}, graph.get_parent_map(['rev1']))
2739
tree.commit('message', rev_id='rev1')
2740
graph = tree.branch.repository.get_graph()
2741
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2744
class TestRepositoryGetRevisions(TestRemoteRepository):
2746
def test_hpss_missing_revision(self):
2747
transport_path = 'quack'
2748
repo, client = self.setup_fake_client_and_repository(transport_path)
2749
client.add_success_response_with_body(
2751
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2752
['somerev1', 'anotherrev2'])
2754
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2755
('quack/', ), "somerev1\nanotherrev2")],
2758
def test_hpss_get_single_revision(self):
2759
transport_path = 'quack'
2760
repo, client = self.setup_fake_client_and_repository(transport_path)
2761
somerev1 = Revision("somerev1")
2762
somerev1.committer = "Joe Committer <joe@example.com>"
2763
somerev1.timestamp = 1321828927
2764
somerev1.timezone = -60
2765
somerev1.inventory_sha1 = "691b39be74c67b1212a75fcb19c433aaed903c2b"
2766
somerev1.message = "Message"
2767
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2769
# Split up body into two bits to make sure the zlib compression object
2770
# gets data fed twice.
2771
client.add_success_response_with_body(
2772
[body[:10], body[10:]], 'ok', '10')
2773
revs = repo.get_revisions(['somerev1'])
2774
self.assertEquals(revs, [somerev1])
2776
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2777
('quack/', ), "somerev1")],
2781
531
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2783
533
def test_null_revision(self):
2784
534
# a null revision has the predictable result {}, we should have no wire
2785
535
# traffic when calling it with this argument
536
responses = [(('notused', ), '')]
2786
537
transport_path = 'empty'
2787
repo, client = self.setup_fake_client_and_repository(transport_path)
2788
client.add_success_response('notused')
2789
# actual RemoteRepository.get_revision_graph is gone, but there's an
2790
# equivalent private method for testing
2791
result = repo._get_revision_graph(NULL_REVISION)
538
repo, client = self.setup_fake_client_and_repository(
539
responses, transport_path)
540
result = repo.get_revision_graph(NULL_REVISION)
2792
541
self.assertEqual([], client._calls)
2793
542
self.assertEqual({}, result)
3207
686
def test_none(self):
3208
687
# repo.has_revision(None) should not cause any traffic.
3209
688
transport_path = 'quack'
3210
repo, client = self.setup_fake_client_and_repository(transport_path)
690
repo, client = self.setup_fake_client_and_repository(
691
responses, transport_path)
3212
693
# The null revision is always there, so has_revision(None) == True.
3213
self.assertEqual(True, repo.has_revision(NULL_REVISION))
694
self.assertEqual(True, repo.has_revision(None))
3215
696
# The remote repo shouldn't be accessed.
3216
697
self.assertEqual([], client._calls)
3219
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3220
"""Test Repository.iter_file_bytes."""
3222
def test_single(self):
3223
transport_path = 'quack'
3224
repo, client = self.setup_fake_client_and_repository(transport_path)
3225
client.add_expected_call(
3226
'Repository.iter_files_bytes', ('quack/', ),
3227
'success', ('ok',), iter(["ok\x000", "\n", zlib.compress("mydata" * 10)]))
3228
for (identifier, byte_stream) in repo.iter_files_bytes([("somefile",
3229
"somerev", "myid")]):
3230
self.assertEquals("myid", identifier)
3231
self.assertEquals("".join(byte_stream), "mydata" * 10)
3233
def test_missing(self):
3234
transport_path = 'quack'
3235
repo, client = self.setup_fake_client_and_repository(transport_path)
3236
client.add_expected_call(
3237
'Repository.iter_files_bytes',
3239
'error', ('RevisionNotPresent', 'somefile', 'somerev'),
3240
iter(["absent\0somefile\0somerev\n"]))
3241
self.assertRaises(errors.RevisionNotPresent, list,
3242
repo.iter_files_bytes(
3243
[("somefile", "somerev", "myid")]))
3246
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3247
"""Base class for Repository.insert_stream and .insert_stream_1.19
3251
def checkInsertEmptyStream(self, repo, client):
3252
"""Insert an empty stream, checking the result.
3254
This checks that there are no resume_tokens or missing_keys, and that
3255
the client is finished.
3257
sink = repo._get_sink()
3258
fmt = repository.format_registry.get_default()
3259
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3260
self.assertEqual([], resume_tokens)
3261
self.assertEqual(set(), missing_keys)
3262
self.assertFinished(client)
3265
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3266
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3269
This test case is very similar to TestRepositoryInsertStream_1_19.
3273
TestRemoteRepository.setUp(self)
3274
self.disable_verb('Repository.insert_stream_1.19')
3276
def test_unlocked_repo(self):
3277
transport_path = 'quack'
3278
repo, client = self.setup_fake_client_and_repository(transport_path)
3279
client.add_expected_call(
3280
'Repository.insert_stream_1.19', ('quack/', ''),
3281
'unknown', ('Repository.insert_stream_1.19',))
3282
client.add_expected_call(
3283
'Repository.insert_stream', ('quack/', ''),
3285
client.add_expected_call(
3286
'Repository.insert_stream', ('quack/', ''),
3288
self.checkInsertEmptyStream(repo, client)
3290
def test_locked_repo_with_no_lock_token(self):
3291
transport_path = 'quack'
3292
repo, client = self.setup_fake_client_and_repository(transport_path)
3293
client.add_expected_call(
3294
'Repository.lock_write', ('quack/', ''),
3295
'success', ('ok', ''))
3296
client.add_expected_call(
3297
'Repository.insert_stream_1.19', ('quack/', ''),
3298
'unknown', ('Repository.insert_stream_1.19',))
3299
client.add_expected_call(
3300
'Repository.insert_stream', ('quack/', ''),
3302
client.add_expected_call(
3303
'Repository.insert_stream', ('quack/', ''),
3306
self.checkInsertEmptyStream(repo, client)
3308
def test_locked_repo_with_lock_token(self):
3309
transport_path = 'quack'
3310
repo, client = self.setup_fake_client_and_repository(transport_path)
3311
client.add_expected_call(
3312
'Repository.lock_write', ('quack/', ''),
3313
'success', ('ok', 'a token'))
3314
client.add_expected_call(
3315
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3316
'unknown', ('Repository.insert_stream_1.19',))
3317
client.add_expected_call(
3318
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3320
client.add_expected_call(
3321
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3324
self.checkInsertEmptyStream(repo, client)
3326
def test_stream_with_inventory_deltas(self):
3327
"""'inventory-deltas' substreams cannot be sent to the
3328
Repository.insert_stream verb, because not all servers that implement
3329
that verb will accept them. So when one is encountered the RemoteSink
3330
immediately stops using that verb and falls back to VFS insert_stream.
3332
transport_path = 'quack'
3333
repo, client = self.setup_fake_client_and_repository(transport_path)
3334
client.add_expected_call(
3335
'Repository.insert_stream_1.19', ('quack/', ''),
3336
'unknown', ('Repository.insert_stream_1.19',))
3337
client.add_expected_call(
3338
'Repository.insert_stream', ('quack/', ''),
3340
client.add_expected_call(
3341
'Repository.insert_stream', ('quack/', ''),
3343
# Create a fake real repository for insert_stream to fall back on, so
3344
# that we can directly see the records the RemoteSink passes to the
3349
def insert_stream(self, stream, src_format, resume_tokens):
3350
for substream_kind, substream in stream:
3351
self.records.append(
3352
(substream_kind, [record.key for record in substream]))
3353
return ['fake tokens'], ['fake missing keys']
3354
fake_real_sink = FakeRealSink()
3355
class FakeRealRepository:
3356
def _get_sink(self):
3357
return fake_real_sink
3358
def is_in_write_group(self):
3360
def refresh_data(self):
3362
repo._real_repository = FakeRealRepository()
3363
sink = repo._get_sink()
3364
fmt = repository.format_registry.get_default()
3365
stream = self.make_stream_with_inv_deltas(fmt)
3366
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3367
# Every record from the first inventory delta should have been sent to
3369
expected_records = [
3370
('inventory-deltas', [('rev2',), ('rev3',)]),
3371
('texts', [('some-rev', 'some-file')])]
3372
self.assertEqual(expected_records, fake_real_sink.records)
3373
# The return values from the real sink's insert_stream are propagated
3374
# back to the original caller.
3375
self.assertEqual(['fake tokens'], resume_tokens)
3376
self.assertEqual(['fake missing keys'], missing_keys)
3377
self.assertFinished(client)
3379
def make_stream_with_inv_deltas(self, fmt):
3380
"""Make a simple stream with an inventory delta followed by more
3381
records and more substreams to test that all records and substreams
3382
from that point on are used.
3384
This sends, in order:
3385
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3387
* texts substream: (some-rev, some-file)
3389
# Define a stream using generators so that it isn't rewindable.
3390
inv = inventory.Inventory(revision_id='rev1')
3391
inv.root.revision = 'rev1'
3392
def stream_with_inv_delta():
3393
yield ('inventories', inventories_substream())
3394
yield ('inventory-deltas', inventory_delta_substream())
3396
versionedfile.FulltextContentFactory(
3397
('some-rev', 'some-file'), (), None, 'content')])
3398
def inventories_substream():
3399
# An empty inventory fulltext. This will be streamed normally.
3400
text = fmt._serializer.write_inventory_to_string(inv)
3401
yield versionedfile.FulltextContentFactory(
3402
('rev1',), (), None, text)
3403
def inventory_delta_substream():
3404
# An inventory delta. This can't be streamed via this verb, so it
3405
# will trigger a fallback to VFS insert_stream.
3406
entry = inv.make_entry(
3407
'directory', 'newdir', inv.root.file_id, 'newdir-id')
3408
entry.revision = 'ghost'
3409
delta = [(None, 'newdir', 'newdir-id', entry)]
3410
serializer = inventory_delta.InventoryDeltaSerializer(
3411
versioned_root=True, tree_references=False)
3412
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
3413
yield versionedfile.ChunkedContentFactory(
3414
('rev2',), (('rev1',)), None, lines)
3416
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
3417
yield versionedfile.ChunkedContentFactory(
3418
('rev3',), (('rev1',)), None, lines)
3419
return stream_with_inv_delta()
3422
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3424
def test_unlocked_repo(self):
3425
transport_path = 'quack'
3426
repo, client = self.setup_fake_client_and_repository(transport_path)
3427
client.add_expected_call(
3428
'Repository.insert_stream_1.19', ('quack/', ''),
3430
client.add_expected_call(
3431
'Repository.insert_stream_1.19', ('quack/', ''),
3433
self.checkInsertEmptyStream(repo, client)
3435
def test_locked_repo_with_no_lock_token(self):
3436
transport_path = 'quack'
3437
repo, client = self.setup_fake_client_and_repository(transport_path)
3438
client.add_expected_call(
3439
'Repository.lock_write', ('quack/', ''),
3440
'success', ('ok', ''))
3441
client.add_expected_call(
3442
'Repository.insert_stream_1.19', ('quack/', ''),
3444
client.add_expected_call(
3445
'Repository.insert_stream_1.19', ('quack/', ''),
3448
self.checkInsertEmptyStream(repo, client)
3450
def test_locked_repo_with_lock_token(self):
3451
transport_path = 'quack'
3452
repo, client = self.setup_fake_client_and_repository(transport_path)
3453
client.add_expected_call(
3454
'Repository.lock_write', ('quack/', ''),
3455
'success', ('ok', 'a token'))
3456
client.add_expected_call(
3457
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3459
client.add_expected_call(
3460
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3463
self.checkInsertEmptyStream(repo, client)
3466
700
class TestRepositoryTarball(TestRemoteRepository):
3468
702
# This is a canned tarball reponse we can validate against
3515
769
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3516
770
self.assertTrue(isinstance(src_repo, RemoteRepository))
3517
771
src_repo.copy_content_into(dest_repo)
3520
class _StubRealPackRepository(object):
3522
def __init__(self, calls):
3524
self._pack_collection = _StubPackCollection(calls)
3526
def start_write_group(self):
3527
self.calls.append(('start_write_group',))
3529
def is_in_write_group(self):
3532
def refresh_data(self):
3533
self.calls.append(('pack collection reload_pack_names',))
3536
class _StubPackCollection(object):
3538
def __init__(self, calls):
3542
self.calls.append(('pack collection autopack',))
3545
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3546
"""Tests for RemoteRepository.autopack implementation."""
3549
"""When the server returns 'ok' and there's no _real_repository, then
3550
nothing else happens: the autopack method is done.
3552
transport_path = 'quack'
3553
repo, client = self.setup_fake_client_and_repository(transport_path)
3554
client.add_expected_call(
3555
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
3557
self.assertFinished(client)
3559
def test_ok_with_real_repo(self):
3560
"""When the server returns 'ok' and there is a _real_repository, then
3561
the _real_repository's reload_pack_name's method will be called.
3563
transport_path = 'quack'
3564
repo, client = self.setup_fake_client_and_repository(transport_path)
3565
client.add_expected_call(
3566
'PackRepository.autopack', ('quack/',),
3568
repo._real_repository = _StubRealPackRepository(client._calls)
3571
[('call', 'PackRepository.autopack', ('quack/',)),
3572
('pack collection reload_pack_names',)],
3575
def test_backwards_compatibility(self):
3576
"""If the server does not recognise the PackRepository.autopack verb,
3577
fallback to the real_repository's implementation.
3579
transport_path = 'quack'
3580
repo, client = self.setup_fake_client_and_repository(transport_path)
3581
client.add_unknown_method_response('PackRepository.autopack')
3582
def stub_ensure_real():
3583
client._calls.append(('_ensure_real',))
3584
repo._real_repository = _StubRealPackRepository(client._calls)
3585
repo._ensure_real = stub_ensure_real
3588
[('call', 'PackRepository.autopack', ('quack/',)),
3590
('pack collection autopack',)],
3593
def test_oom_error_reporting(self):
3594
"""An out-of-memory condition on the server is reported clearly"""
3595
transport_path = 'quack'
3596
repo, client = self.setup_fake_client_and_repository(transport_path)
3597
client.add_expected_call(
3598
'PackRepository.autopack', ('quack/',),
3599
'error', ('MemoryError',))
3600
err = self.assertRaises(errors.BzrError, repo.autopack)
3601
self.assertContainsRe(str(err), "^remote server out of mem")
3604
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3605
"""Base class for unit tests for bzrlib.remote._translate_error."""
3607
def translateTuple(self, error_tuple, **context):
3608
"""Call _translate_error with an ErrorFromSmartServer built from the
3611
:param error_tuple: A tuple of a smart server response, as would be
3612
passed to an ErrorFromSmartServer.
3613
:kwargs context: context items to call _translate_error with.
3615
:returns: The error raised by _translate_error.
3617
# Raise the ErrorFromSmartServer before passing it as an argument,
3618
# because _translate_error may need to re-raise it with a bare 'raise'
3620
server_error = errors.ErrorFromSmartServer(error_tuple)
3621
translated_error = self.translateErrorFromSmartServer(
3622
server_error, **context)
3623
return translated_error
3625
def translateErrorFromSmartServer(self, error_object, **context):
3626
"""Like translateTuple, but takes an already constructed
3627
ErrorFromSmartServer rather than a tuple.
3631
except errors.ErrorFromSmartServer, server_error:
3632
translated_error = self.assertRaises(
3633
errors.BzrError, remote._translate_error, server_error,
3635
return translated_error
3638
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3639
"""Unit tests for bzrlib.remote._translate_error.
3641
Given an ErrorFromSmartServer (which has an error tuple from a smart
3642
server) and some context, _translate_error raises more specific errors from
3645
This test case covers the cases where _translate_error succeeds in
3646
translating an ErrorFromSmartServer to something better. See
3647
TestErrorTranslationRobustness for other cases.
3650
def test_NoSuchRevision(self):
3651
branch = self.make_branch('')
3653
translated_error = self.translateTuple(
3654
('NoSuchRevision', revid), branch=branch)
3655
expected_error = errors.NoSuchRevision(branch, revid)
3656
self.assertEqual(expected_error, translated_error)
3658
def test_nosuchrevision(self):
3659
repository = self.make_repository('')
3661
translated_error = self.translateTuple(
3662
('nosuchrevision', revid), repository=repository)
3663
expected_error = errors.NoSuchRevision(repository, revid)
3664
self.assertEqual(expected_error, translated_error)
3666
def test_nobranch(self):
3667
bzrdir = self.make_bzrdir('')
3668
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3669
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3670
self.assertEqual(expected_error, translated_error)
3672
def test_nobranch_one_arg(self):
3673
bzrdir = self.make_bzrdir('')
3674
translated_error = self.translateTuple(
3675
('nobranch', 'extra detail'), bzrdir=bzrdir)
3676
expected_error = errors.NotBranchError(
3677
path=bzrdir.root_transport.base,
3678
detail='extra detail')
3679
self.assertEqual(expected_error, translated_error)
3681
def test_norepository(self):
3682
bzrdir = self.make_bzrdir('')
3683
translated_error = self.translateTuple(('norepository',),
3685
expected_error = errors.NoRepositoryPresent(bzrdir)
3686
self.assertEqual(expected_error, translated_error)
3688
def test_LockContention(self):
3689
translated_error = self.translateTuple(('LockContention',))
3690
expected_error = errors.LockContention('(remote lock)')
3691
self.assertEqual(expected_error, translated_error)
3693
def test_UnlockableTransport(self):
3694
bzrdir = self.make_bzrdir('')
3695
translated_error = self.translateTuple(
3696
('UnlockableTransport',), bzrdir=bzrdir)
3697
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3698
self.assertEqual(expected_error, translated_error)
3700
def test_LockFailed(self):
3701
lock = 'str() of a server lock'
3702
why = 'str() of why'
3703
translated_error = self.translateTuple(('LockFailed', lock, why))
3704
expected_error = errors.LockFailed(lock, why)
3705
self.assertEqual(expected_error, translated_error)
3707
def test_TokenMismatch(self):
3708
token = 'a lock token'
3709
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3710
expected_error = errors.TokenMismatch(token, '(remote token)')
3711
self.assertEqual(expected_error, translated_error)
3713
def test_Diverged(self):
3714
branch = self.make_branch('a')
3715
other_branch = self.make_branch('b')
3716
translated_error = self.translateTuple(
3717
('Diverged',), branch=branch, other_branch=other_branch)
3718
expected_error = errors.DivergedBranches(branch, other_branch)
3719
self.assertEqual(expected_error, translated_error)
3721
def test_NotStacked(self):
3722
branch = self.make_branch('')
3723
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3724
expected_error = errors.NotStacked(branch)
3725
self.assertEqual(expected_error, translated_error)
3727
def test_ReadError_no_args(self):
3729
translated_error = self.translateTuple(('ReadError',), path=path)
3730
expected_error = errors.ReadError(path)
3731
self.assertEqual(expected_error, translated_error)
3733
def test_ReadError(self):
3735
translated_error = self.translateTuple(('ReadError', path))
3736
expected_error = errors.ReadError(path)
3737
self.assertEqual(expected_error, translated_error)
3739
def test_IncompatibleRepositories(self):
3740
translated_error = self.translateTuple(('IncompatibleRepositories',
3741
"repo1", "repo2", "details here"))
3742
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3744
self.assertEqual(expected_error, translated_error)
3746
def test_PermissionDenied_no_args(self):
3748
translated_error = self.translateTuple(('PermissionDenied',),
3750
expected_error = errors.PermissionDenied(path)
3751
self.assertEqual(expected_error, translated_error)
3753
def test_PermissionDenied_one_arg(self):
3755
translated_error = self.translateTuple(('PermissionDenied', path))
3756
expected_error = errors.PermissionDenied(path)
3757
self.assertEqual(expected_error, translated_error)
3759
def test_PermissionDenied_one_arg_and_context(self):
3760
"""Given a choice between a path from the local context and a path on
3761
the wire, _translate_error prefers the path from the local context.
3763
local_path = 'local path'
3764
remote_path = 'remote path'
3765
translated_error = self.translateTuple(
3766
('PermissionDenied', remote_path), path=local_path)
3767
expected_error = errors.PermissionDenied(local_path)
3768
self.assertEqual(expected_error, translated_error)
3770
def test_PermissionDenied_two_args(self):
3772
extra = 'a string with extra info'
3773
translated_error = self.translateTuple(
3774
('PermissionDenied', path, extra))
3775
expected_error = errors.PermissionDenied(path, extra)
3776
self.assertEqual(expected_error, translated_error)
3778
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3780
def test_NoSuchFile_context_path(self):
3781
local_path = "local path"
3782
translated_error = self.translateTuple(('ReadError', "remote path"),
3784
expected_error = errors.ReadError(local_path)
3785
self.assertEqual(expected_error, translated_error)
3787
def test_NoSuchFile_without_context(self):
3788
remote_path = "remote path"
3789
translated_error = self.translateTuple(('ReadError', remote_path))
3790
expected_error = errors.ReadError(remote_path)
3791
self.assertEqual(expected_error, translated_error)
3793
def test_ReadOnlyError(self):
3794
translated_error = self.translateTuple(('ReadOnlyError',))
3795
expected_error = errors.TransportNotPossible("readonly transport")
3796
self.assertEqual(expected_error, translated_error)
3798
def test_MemoryError(self):
3799
translated_error = self.translateTuple(('MemoryError',))
3800
self.assertStartsWith(str(translated_error),
3801
"remote server out of memory")
3803
def test_generic_IndexError_no_classname(self):
3804
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3805
translated_error = self.translateErrorFromSmartServer(err)
3806
expected_error = errors.UnknownErrorFromSmartServer(err)
3807
self.assertEqual(expected_error, translated_error)
3809
# GZ 2011-03-02: TODO test generic non-ascii error string
3811
def test_generic_KeyError(self):
3812
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3813
translated_error = self.translateErrorFromSmartServer(err)
3814
expected_error = errors.UnknownErrorFromSmartServer(err)
3815
self.assertEqual(expected_error, translated_error)
3818
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3819
"""Unit tests for bzrlib.remote._translate_error's robustness.
3821
TestErrorTranslationSuccess is for cases where _translate_error can
3822
translate successfully. This class about how _translate_err behaves when
3823
it fails to translate: it re-raises the original error.
3826
def test_unrecognised_server_error(self):
3827
"""If the error code from the server is not recognised, the original
3828
ErrorFromSmartServer is propagated unmodified.
3830
error_tuple = ('An unknown error tuple',)
3831
server_error = errors.ErrorFromSmartServer(error_tuple)
3832
translated_error = self.translateErrorFromSmartServer(server_error)
3833
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3834
self.assertEqual(expected_error, translated_error)
3836
def test_context_missing_a_key(self):
3837
"""In case of a bug in the client, or perhaps an unexpected response
3838
from a server, _translate_error returns the original error tuple from
3839
the server and mutters a warning.
3841
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3842
# in the context dict. So let's give it an empty context dict instead
3843
# to exercise its error recovery.
3845
error_tuple = ('NoSuchRevision', 'revid')
3846
server_error = errors.ErrorFromSmartServer(error_tuple)
3847
translated_error = self.translateErrorFromSmartServer(server_error)
3848
self.assertEqual(server_error, translated_error)
3849
# In addition to re-raising ErrorFromSmartServer, some debug info has
3850
# been muttered to the log file for developer to look at.
3851
self.assertContainsRe(
3853
"Missing key 'branch' in context")
3855
def test_path_missing(self):
3856
"""Some translations (PermissionDenied, ReadError) can determine the
3857
'path' variable from either the wire or the local context. If neither
3858
has it, then an error is raised.
3860
error_tuple = ('ReadError',)
3861
server_error = errors.ErrorFromSmartServer(error_tuple)
3862
translated_error = self.translateErrorFromSmartServer(server_error)
3863
self.assertEqual(server_error, translated_error)
3864
# In addition to re-raising ErrorFromSmartServer, some debug info has
3865
# been muttered to the log file for developer to look at.
3866
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3869
class TestStacking(tests.TestCaseWithTransport):
3870
"""Tests for operations on stacked remote repositories.
3872
The underlying format type must support stacking.
3875
def test_access_stacked_remote(self):
3876
# based on <http://launchpad.net/bugs/261315>
3877
# make a branch stacked on another repository containing an empty
3878
# revision, then open it over hpss - we should be able to see that
3880
base_transport = self.get_transport()
3881
base_builder = self.make_branch_builder('base', format='1.9')
3882
base_builder.start_series()
3883
base_revid = base_builder.build_snapshot('rev-id', None,
3884
[('add', ('', None, 'directory', None))],
3886
base_builder.finish_series()
3887
stacked_branch = self.make_branch('stacked', format='1.9')
3888
stacked_branch.set_stacked_on_url('../base')
3889
# start a server looking at this
3890
smart_server = test_server.SmartTCPServer_for_testing()
3891
self.start_server(smart_server)
3892
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3893
# can get its branch and repository
3894
remote_branch = remote_bzrdir.open_branch()
3895
remote_repo = remote_branch.repository
3896
remote_repo.lock_read()
3898
# it should have an appropriate fallback repository, which should also
3899
# be a RemoteRepository
3900
self.assertLength(1, remote_repo._fallback_repositories)
3901
self.assertIsInstance(remote_repo._fallback_repositories[0],
3903
# and it has the revision committed to the underlying repository;
3904
# these have varying implementations so we try several of them
3905
self.assertTrue(remote_repo.has_revisions([base_revid]))
3906
self.assertTrue(remote_repo.has_revision(base_revid))
3907
self.assertEqual(remote_repo.get_revision(base_revid).message,
3910
remote_repo.unlock()
3912
def prepare_stacked_remote_branch(self):
3913
"""Get stacked_upon and stacked branches with content in each."""
3914
self.setup_smart_server_with_call_log()
3915
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3916
tree1.commit('rev1', rev_id='rev1')
3917
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3918
).open_workingtree()
3919
local_tree = tree2.branch.create_checkout('local')
3920
local_tree.commit('local changes make me feel good.')
3921
branch2 = Branch.open(self.get_url('tree2'))
3923
self.addCleanup(branch2.unlock)
3924
return tree1.branch, branch2
3926
def test_stacked_get_parent_map(self):
3927
# the public implementation of get_parent_map obeys stacking
3928
_, branch = self.prepare_stacked_remote_branch()
3929
repo = branch.repository
3930
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3932
def test_unstacked_get_parent_map(self):
3933
# _unstacked_provider.get_parent_map ignores stacking
3934
_, branch = self.prepare_stacked_remote_branch()
3935
provider = branch.repository._unstacked_provider
3936
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3938
def fetch_stream_to_rev_order(self, stream):
3940
for kind, substream in stream:
3941
if not kind == 'revisions':
3944
for content in substream:
3945
result.append(content.key[-1])
3948
def get_ordered_revs(self, format, order, branch_factory=None):
3949
"""Get a list of the revisions in a stream to format format.
3951
:param format: The format of the target.
3952
:param order: the order that target should have requested.
3953
:param branch_factory: A callable to create a trunk and stacked branch
3954
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3955
:result: The revision ids in the stream, in the order seen,
3956
the topological order of revisions in the source.
3958
unordered_format = controldir.format_registry.get(format)()
3959
target_repository_format = unordered_format.repository_format
3961
self.assertEqual(order, target_repository_format._fetch_order)
3962
if branch_factory is None:
3963
branch_factory = self.prepare_stacked_remote_branch
3964
_, stacked = branch_factory()
3965
source = stacked.repository._get_source(target_repository_format)
3966
tip = stacked.last_revision()
3967
stacked.repository._ensure_real()
3968
graph = stacked.repository.get_graph()
3969
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3970
if r != NULL_REVISION]
3972
search = vf_search.PendingAncestryResult([tip], stacked.repository)
3973
self.reset_smart_call_log()
3974
stream = source.get_stream(search)
3975
# We trust that if a revision is in the stream the rest of the new
3976
# content for it is too, as per our main fetch tests; here we are
3977
# checking that the revisions are actually included at all, and their
3979
return self.fetch_stream_to_rev_order(stream), revs
3981
def test_stacked_get_stream_unordered(self):
3982
# Repository._get_source.get_stream() from a stacked repository with
3983
# unordered yields the full data from both stacked and stacked upon
3985
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3986
self.assertEqual(set(expected_revs), set(rev_ord))
3987
# Getting unordered results should have made a streaming data request
3988
# from the server, then one from the backing branch.
3989
self.assertLength(2, self.hpss_calls)
3991
def test_stacked_on_stacked_get_stream_unordered(self):
3992
# Repository._get_source.get_stream() from a stacked repository which
3993
# is itself stacked yields the full data from all three sources.
3994
def make_stacked_stacked():
3995
_, stacked = self.prepare_stacked_remote_branch()
3996
tree = stacked.bzrdir.sprout('tree3', stacked=True
3997
).open_workingtree()
3998
local_tree = tree.branch.create_checkout('local-tree3')
3999
local_tree.commit('more local changes are better')
4000
branch = Branch.open(self.get_url('tree3'))
4002
self.addCleanup(branch.unlock)
4004
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
4005
branch_factory=make_stacked_stacked)
4006
self.assertEqual(set(expected_revs), set(rev_ord))
4007
# Getting unordered results should have made a streaming data request
4008
# from the server, and one from each backing repo
4009
self.assertLength(3, self.hpss_calls)
4011
def test_stacked_get_stream_topological(self):
4012
# Repository._get_source.get_stream() from a stacked repository with
4013
# topological sorting yields the full data from both stacked and
4014
# stacked upon sources in topological order.
4015
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4016
self.assertEqual(expected_revs, rev_ord)
4017
# Getting topological sort requires VFS calls still - one of which is
4018
# pushing up from the bound branch.
4019
self.assertLength(14, self.hpss_calls)
4021
def test_stacked_get_stream_groupcompress(self):
4022
# Repository._get_source.get_stream() from a stacked repository with
4023
# groupcompress sorting yields the full data from both stacked and
4024
# stacked upon sources in groupcompress order.
4025
raise tests.TestSkipped('No groupcompress ordered format available')
4026
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4027
self.assertEqual(expected_revs, reversed(rev_ord))
4028
# Getting unordered results should have made a streaming data request
4029
# from the backing branch, and one from the stacked on branch.
4030
self.assertLength(2, self.hpss_calls)
4032
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4033
# When pulling some fixed amount of content that is more than the
4034
# source has (because some is coming from a fallback branch, no error
4035
# should be received. This was reported as bug 360791.
4036
# Need three branches: a trunk, a stacked branch, and a preexisting
4037
# branch pulling content from stacked and trunk.
4038
self.setup_smart_server_with_call_log()
4039
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4040
r1 = trunk.commit('start')
4041
stacked_branch = trunk.branch.create_clone_on_transport(
4042
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4043
local = self.make_branch('local', format='1.9-rich-root')
4044
local.repository.fetch(stacked_branch.repository,
4045
stacked_branch.last_revision())
4048
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4051
super(TestRemoteBranchEffort, self).setUp()
4052
# Create a smart server that publishes whatever the backing VFS server
4054
self.smart_server = test_server.SmartTCPServer_for_testing()
4055
self.start_server(self.smart_server, self.get_server())
4056
# Log all HPSS calls into self.hpss_calls.
4057
_SmartClient.hooks.install_named_hook(
4058
'call', self.capture_hpss_call, None)
4059
self.hpss_calls = []
4061
def capture_hpss_call(self, params):
4062
self.hpss_calls.append(params.method)
4064
def test_copy_content_into_avoids_revision_history(self):
4065
local = self.make_branch('local')
4066
builder = self.make_branch_builder('remote')
4067
builder.build_commit(message="Commit.")
4068
remote_branch_url = self.smart_server.get_url() + 'remote'
4069
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4070
local.repository.fetch(remote_branch.repository)
4071
self.hpss_calls = []
4072
remote_branch.copy_content_into(local)
4073
self.assertFalse('Branch.revision_history' in self.hpss_calls)
4075
def test_fetch_everything_needs_just_one_call(self):
4076
local = self.make_branch('local')
4077
builder = self.make_branch_builder('remote')
4078
builder.build_commit(message="Commit.")
4079
remote_branch_url = self.smart_server.get_url() + 'remote'
4080
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4081
self.hpss_calls = []
4082
local.repository.fetch(
4083
remote_branch.repository,
4084
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4085
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
4087
def override_verb(self, verb_name, verb):
4088
request_handlers = request.request_handlers
4089
orig_verb = request_handlers.get(verb_name)
4090
orig_info = request_handlers.get_info(verb_name)
4091
request_handlers.register(verb_name, verb, override_existing=True)
4092
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4093
override_existing=True, info=orig_info)
4095
def test_fetch_everything_backwards_compat(self):
4096
"""Can fetch with EverythingResult even with pre 2.4 servers.
4098
Pre-2.4 do not support 'everything' searches with the
4099
Repository.get_stream_1.19 verb.
4102
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4103
"""A version of the Repository.get_stream_1.19 verb patched to
4104
reject 'everything' searches the way 2.3 and earlier do.
4106
def recreate_search(self, repository, search_bytes,
4107
discard_excess=False):
4108
verb_log.append(search_bytes.split('\n', 1)[0])
4109
if search_bytes == 'everything':
4111
request.FailedSmartServerResponse(('BadSearch',)))
4112
return super(OldGetStreamVerb,
4113
self).recreate_search(repository, search_bytes,
4114
discard_excess=discard_excess)
4115
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
4116
local = self.make_branch('local')
4117
builder = self.make_branch_builder('remote')
4118
builder.build_commit(message="Commit.")
4119
remote_branch_url = self.smart_server.get_url() + 'remote'
4120
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4121
self.hpss_calls = []
4122
local.repository.fetch(
4123
remote_branch.repository,
4124
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4125
# make sure the overridden verb was used
4126
self.assertLength(1, verb_log)
4127
# more than one HPSS call is needed, but because it's a VFS callback
4128
# its hard to predict exactly how many.
4129
self.assertTrue(len(self.hpss_calls) > 1)
4132
class TestUpdateBoundBranchWithModifiedBoundLocation(
4133
tests.TestCaseWithTransport):
4134
"""Ensure correct handling of bound_location modifications.
4136
This is tested against a smart server as http://pad.lv/786980 was about a
4137
ReadOnlyError (write attempt during a read-only transaction) which can only
4138
happen in this context.
4142
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4143
self.transport_server = test_server.SmartTCPServer_for_testing
4145
def make_master_and_checkout(self, master_name, checkout_name):
4146
# Create the master branch and its associated checkout
4147
self.master = self.make_branch_and_tree(master_name)
4148
self.checkout = self.master.branch.create_checkout(checkout_name)
4149
# Modify the master branch so there is something to update
4150
self.master.commit('add stuff')
4151
self.last_revid = self.master.commit('even more stuff')
4152
self.bound_location = self.checkout.branch.get_bound_location()
4154
def assertUpdateSucceeds(self, new_location):
4155
self.checkout.branch.set_bound_location(new_location)
4156
self.checkout.update()
4157
self.assertEquals(self.last_revid, self.checkout.last_revision())
4159
def test_without_final_slash(self):
4160
self.make_master_and_checkout('master', 'checkout')
4161
# For unclear reasons some users have a bound_location without a final
4162
# '/', simulate that by forcing such a value
4163
self.assertEndsWith(self.bound_location, '/')
4164
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4166
def test_plus_sign(self):
4167
self.make_master_and_checkout('+master', 'checkout')
4168
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4170
def test_tilda(self):
4171
# Embed ~ in the middle of the path just to avoid any $HOME
4173
self.make_master_and_checkout('mas~ter', 'checkout')
4174
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4177
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4179
def test_no_context(self):
4180
class OutOfCoffee(errors.BzrError):
4181
"""A dummy exception for testing."""
4183
def __init__(self, urgency):
4184
self.urgency = urgency
4185
remote.no_context_error_translators.register("OutOfCoffee",
4186
lambda err: OutOfCoffee(err.error_args[0]))
4187
transport = MemoryTransport()
4188
client = FakeClient(transport.base)
4189
client.add_expected_call(
4190
'Branch.get_stacked_on_url', ('quack/',),
4191
'error', ('NotStacked',))
4192
client.add_expected_call(
4193
'Branch.last_revision_info',
4195
'error', ('OutOfCoffee', 'low'))
4196
transport.mkdir('quack')
4197
transport = transport.clone('quack')
4198
branch = self.make_remote_branch(transport, client)
4199
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4200
self.assertFinished(client)
4202
def test_with_context(self):
4203
class OutOfTea(errors.BzrError):
4204
def __init__(self, branch, urgency):
4205
self.branch = branch
4206
self.urgency = urgency
4207
remote.error_translators.register("OutOfTea",
4208
lambda err, find, path: OutOfTea(err.error_args[0],
4210
transport = MemoryTransport()
4211
client = FakeClient(transport.base)
4212
client.add_expected_call(
4213
'Branch.get_stacked_on_url', ('quack/',),
4214
'error', ('NotStacked',))
4215
client.add_expected_call(
4216
'Branch.last_revision_info',
4218
'error', ('OutOfTea', 'low'))
4219
transport.mkdir('quack')
4220
transport = transport.clone('quack')
4221
branch = self.make_remote_branch(transport, client)
4222
self.assertRaises(OutOfTea, branch.last_revision_info)
4223
self.assertFinished(client)
4226
class TestRepositoryPack(TestRemoteRepository):
4228
def test_pack(self):
4229
transport_path = 'quack'
4230
repo, client = self.setup_fake_client_and_repository(transport_path)
4231
client.add_expected_call(
4232
'Repository.lock_write', ('quack/', ''),
4233
'success', ('ok', 'token'))
4234
client.add_expected_call(
4235
'Repository.pack', ('quack/', 'token', 'False'),
4236
'success', ('ok',), )
4237
client.add_expected_call(
4238
'Repository.unlock', ('quack/', 'token'),
4239
'success', ('ok', ))
4242
def test_pack_with_hint(self):
4243
transport_path = 'quack'
4244
repo, client = self.setup_fake_client_and_repository(transport_path)
4245
client.add_expected_call(
4246
'Repository.lock_write', ('quack/', ''),
4247
'success', ('ok', 'token'))
4248
client.add_expected_call(
4249
'Repository.pack', ('quack/', 'token', 'False'),
4250
'success', ('ok',), )
4251
client.add_expected_call(
4252
'Repository.unlock', ('quack/', 'token', 'False'),
4253
'success', ('ok', ))
4254
repo.pack(['hinta', 'hintb'])
4257
class TestRepositoryIterInventories(TestRemoteRepository):
4258
"""Test Repository.iter_inventories."""
4260
def _serialize_inv_delta(self, old_name, new_name, delta):
4261
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4262
return "".join(serializer.delta_to_lines(old_name, new_name, delta))
4264
def test_single_empty(self):
4265
transport_path = 'quack'
4266
repo, client = self.setup_fake_client_and_repository(transport_path)
4267
fmt = controldir.format_registry.get('2a')().repository_format
4269
stream = [('inventory-deltas', [
4270
versionedfile.FulltextContentFactory('somerevid', None, None,
4271
self._serialize_inv_delta('null:', 'somerevid', []))])]
4272
client.add_expected_call(
4273
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4274
'success', ('ok', ),
4275
_stream_to_byte_stream(stream, fmt))
4276
ret = list(repo.iter_inventories(["somerevid"]))
4277
self.assertLength(1, ret)
4279
self.assertEquals("somerevid", inv.revision_id)
4281
def test_empty(self):
4282
transport_path = 'quack'
4283
repo, client = self.setup_fake_client_and_repository(transport_path)
4284
ret = list(repo.iter_inventories([]))
4285
self.assertEquals(ret, [])
4287
def test_missing(self):
4288
transport_path = 'quack'
4289
repo, client = self.setup_fake_client_and_repository(transport_path)
4290
client.add_expected_call(
4291
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4292
'success', ('ok', ), iter([]))
4293
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(