102
132
b = BzrDir.open_from_transport(self.transport).open_branch()
103
133
self.assertStartsWith(str(b), 'RemoteBranch(')
135
def test_remote_branch_format_supports_stacking(self):
137
self.make_branch('unstackable', format='pack-0.92')
138
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
139
self.assertFalse(b._format.supports_stacking())
140
self.make_branch('stackable', format='1.9')
141
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
142
self.assertTrue(b._format.supports_stacking())
144
def test_remote_repo_format_supports_external_references(self):
146
bd = self.make_bzrdir('unstackable', format='pack-0.92')
147
r = bd.create_repository()
148
self.assertFalse(r._format.supports_external_lookups)
149
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
150
self.assertFalse(r._format.supports_external_lookups)
151
bd = self.make_bzrdir('stackable', format='1.9')
152
r = bd.create_repository()
153
self.assertTrue(r._format.supports_external_lookups)
154
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
155
self.assertTrue(r._format.supports_external_lookups)
157
def test_remote_branch_set_append_revisions_only(self):
158
# Make a format 1.9 branch, which supports append_revisions_only
159
branch = self.make_branch('branch', format='1.9')
160
config = branch.get_config()
161
branch.set_append_revisions_only(True)
163
'True', config.get_user_option('append_revisions_only'))
164
branch.set_append_revisions_only(False)
166
'False', config.get_user_option('append_revisions_only'))
168
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
169
branch = self.make_branch('branch', format='knit')
170
config = branch.get_config()
172
errors.UpgradeRequired, branch.set_append_revisions_only, True)
106
175
class FakeProtocol(object):
107
176
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
109
def __init__(self, body):
110
self._body_buffer = StringIO(body)
178
def __init__(self, body, fake_client):
180
self._body_buffer = None
181
self._fake_client = fake_client
112
183
def read_body_bytes(self, count=-1):
113
return self._body_buffer.read(count)
184
if self._body_buffer is None:
185
self._body_buffer = StringIO(self.body)
186
bytes = self._body_buffer.read(count)
187
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
188
self._fake_client.expecting_body = False
191
def cancel_read_body(self):
192
self._fake_client.expecting_body = False
194
def read_streamed_body(self):
116
198
class FakeClient(_SmartClient):
117
199
"""Lookalike for _SmartClient allowing testing."""
119
def __init__(self, responses):
120
# We don't call the super init because there is no medium.
121
"""create a FakeClient.
123
:param respones: A list of response-tuple, body-data pairs to be sent
126
self.responses = responses
201
def __init__(self, fake_medium_base='fake base'):
202
"""Create a FakeClient."""
205
self.expecting_body = False
206
# if non-None, this is the list of expected calls, with only the
207
# method name and arguments included. the body might be hard to
208
# compute so is not included. If a call is None, that call can
210
self._expected_calls = None
211
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
213
def add_expected_call(self, call_name, call_args, response_type,
214
response_args, response_body=None):
215
if self._expected_calls is None:
216
self._expected_calls = []
217
self._expected_calls.append((call_name, call_args))
218
self.responses.append((response_type, response_args, response_body))
220
def add_success_response(self, *args):
221
self.responses.append(('success', args, None))
223
def add_success_response_with_body(self, body, *args):
224
self.responses.append(('success', args, body))
225
if self._expected_calls is not None:
226
self._expected_calls.append(None)
228
def add_error_response(self, *args):
229
self.responses.append(('error', args))
231
def add_unknown_method_response(self, verb):
232
self.responses.append(('unknown', verb))
234
def finished_test(self):
235
if self._expected_calls:
236
raise AssertionError("%r finished but was still expecting %r"
237
% (self, self._expected_calls[0]))
239
def _get_next_response(self):
241
response_tuple = self.responses.pop(0)
242
except IndexError, e:
243
raise AssertionError("%r didn't expect any more calls"
245
if response_tuple[0] == 'unknown':
246
raise errors.UnknownSmartMethod(response_tuple[1])
247
elif response_tuple[0] == 'error':
248
raise errors.ErrorFromSmartServer(response_tuple[1])
249
return response_tuple
251
def _check_call(self, method, args):
252
if self._expected_calls is None:
253
# the test should be updated to say what it expects
256
next_call = self._expected_calls.pop(0)
258
raise AssertionError("%r didn't expect any more calls "
260
% (self, method, args,))
261
if next_call is None:
263
if method != next_call[0] or args != next_call[1]:
264
raise AssertionError("%r expected %r%r "
266
% (self, next_call[0], next_call[1], method, args,))
129
268
def call(self, method, *args):
269
self._check_call(method, args)
130
270
self._calls.append(('call', method, args))
131
return self.responses.pop(0)[0]
271
return self._get_next_response()[1]
133
273
def call_expecting_body(self, method, *args):
274
self._check_call(method, args)
134
275
self._calls.append(('call_expecting_body', method, args))
135
result = self.responses.pop(0)
136
return result[0], FakeProtocol(result[1])
139
class TestBzrDirOpenBranch(tests.TestCase):
276
result = self._get_next_response()
277
self.expecting_body = True
278
return result[1], FakeProtocol(result[2], self)
280
def call_with_body_bytes_expecting_body(self, method, args, body):
281
self._check_call(method, args)
282
self._calls.append(('call_with_body_bytes_expecting_body', method,
284
result = self._get_next_response()
285
self.expecting_body = True
286
return result[1], FakeProtocol(result[2], self)
288
def call_with_body_stream(self, args, stream):
289
# Explicitly consume the stream before checking for an error, because
290
# that's what happens a real medium.
291
stream = list(stream)
292
self._check_call(args[0], args[1:])
293
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
294
result = self._get_next_response()
295
# The second value returned from call_with_body_stream is supposed to
296
# be a response_handler object, but so far no tests depend on that.
297
response_handler = None
298
return result[1], response_handler
301
class FakeMedium(medium.SmartClientMedium):
303
def __init__(self, client_calls, base):
304
medium.SmartClientMedium.__init__(self, base)
305
self._client_calls = client_calls
307
def disconnect(self):
308
self._client_calls.append(('disconnect medium',))
311
class TestVfsHas(tests.TestCase):
313
def test_unicode_path(self):
314
client = FakeClient('/')
315
client.add_success_response('yes',)
316
transport = RemoteTransport('bzr://localhost/', _client=client)
317
filename = u'/hell\u00d8'.encode('utf8')
318
result = transport.has(filename)
320
[('call', 'has', (filename,))],
322
self.assertTrue(result)
325
class TestRemote(tests.TestCaseWithMemoryTransport):
327
def get_branch_format(self):
328
reference_bzrdir_format = bzrdir.format_registry.get('default')()
329
return reference_bzrdir_format.get_branch_format()
331
def get_repo_format(self):
332
reference_bzrdir_format = bzrdir.format_registry.get('default')()
333
return reference_bzrdir_format.repository_format
335
def disable_verb(self, verb):
336
"""Disable a verb for one test."""
337
request_handlers = smart.request.request_handlers
338
orig_method = request_handlers.get(verb)
339
request_handlers.remove(verb)
341
request_handlers.register(verb, orig_method)
342
self.addCleanup(restoreVerb)
345
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
346
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
348
def assertRemotePath(self, expected, client_base, transport_base):
349
"""Assert that the result of
350
SmartClientMedium.remote_path_from_transport is the expected value for
351
a given client_base and transport_base.
353
client_medium = medium.SmartClientMedium(client_base)
354
transport = get_transport(transport_base)
355
result = client_medium.remote_path_from_transport(transport)
356
self.assertEqual(expected, result)
358
def test_remote_path_from_transport(self):
359
"""SmartClientMedium.remote_path_from_transport calculates a URL for
360
the given transport relative to the root of the client base URL.
362
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
363
self.assertRemotePath(
364
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
366
def assertRemotePathHTTP(self, expected, transport_base, relpath):
367
"""Assert that the result of
368
HttpTransportBase.remote_path_from_transport is the expected value for
369
a given transport_base and relpath of that transport. (Note that
370
HttpTransportBase is a subclass of SmartClientMedium)
372
base_transport = get_transport(transport_base)
373
client_medium = base_transport.get_smart_medium()
374
cloned_transport = base_transport.clone(relpath)
375
result = client_medium.remote_path_from_transport(cloned_transport)
376
self.assertEqual(expected, result)
378
def test_remote_path_from_transport_http(self):
379
"""Remote paths for HTTP transports are calculated differently to other
380
transports. They are just relative to the client base, not the root
381
directory of the host.
383
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
384
self.assertRemotePathHTTP(
385
'../xyz/', scheme + '//host/path', '../xyz/')
386
self.assertRemotePathHTTP(
387
'xyz/', scheme + '//host/path', 'xyz/')
390
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
391
"""Tests for the behaviour of client_medium.remote_is_at_least."""
393
def test_initially_unlimited(self):
394
"""A fresh medium assumes that the remote side supports all
397
client_medium = medium.SmartClientMedium('dummy base')
398
self.assertFalse(client_medium._is_remote_before((99, 99)))
400
def test__remember_remote_is_before(self):
401
"""Calling _remember_remote_is_before ratchets down the known remote
404
client_medium = medium.SmartClientMedium('dummy base')
405
# Mark the remote side as being less than 1.6. The remote side may
407
client_medium._remember_remote_is_before((1, 6))
408
self.assertTrue(client_medium._is_remote_before((1, 6)))
409
self.assertFalse(client_medium._is_remote_before((1, 5)))
410
# Calling _remember_remote_is_before again with a lower value works.
411
client_medium._remember_remote_is_before((1, 5))
412
self.assertTrue(client_medium._is_remote_before((1, 5)))
413
# You cannot call _remember_remote_is_before with a larger value.
415
AssertionError, client_medium._remember_remote_is_before, (1, 9))
418
class TestBzrDirCloningMetaDir(TestRemote):
420
def test_backwards_compat(self):
421
self.setup_smart_server_with_call_log()
422
a_dir = self.make_bzrdir('.')
423
self.reset_smart_call_log()
424
verb = 'BzrDir.cloning_metadir'
425
self.disable_verb(verb)
426
format = a_dir.cloning_metadir()
427
call_count = len([call for call in self.hpss_calls if
428
call.call.method == verb])
429
self.assertEqual(1, call_count)
431
def test_branch_reference(self):
432
transport = self.get_transport('quack')
433
referenced = self.make_branch('referenced')
434
expected = referenced.bzrdir.cloning_metadir()
435
client = FakeClient(transport.base)
436
client.add_expected_call(
437
'BzrDir.cloning_metadir', ('quack/', 'False'),
438
'error', ('BranchReference',)),
439
client.add_expected_call(
440
'BzrDir.open_branchV2', ('quack/',),
441
'success', ('ref', self.get_url('referenced'))),
442
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
444
result = a_bzrdir.cloning_metadir()
445
# We should have got a control dir matching the referenced branch.
446
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
447
self.assertEqual(expected._repository_format, result._repository_format)
448
self.assertEqual(expected._branch_format, result._branch_format)
449
client.finished_test()
451
def test_current_server(self):
452
transport = self.get_transport('.')
453
transport = transport.clone('quack')
454
self.make_bzrdir('quack')
455
client = FakeClient(transport.base)
456
reference_bzrdir_format = bzrdir.format_registry.get('default')()
457
control_name = reference_bzrdir_format.network_name()
458
client.add_expected_call(
459
'BzrDir.cloning_metadir', ('quack/', 'False'),
460
'success', (control_name, '', ('branch', ''))),
461
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
463
result = a_bzrdir.cloning_metadir()
464
# We should have got a reference control dir with default branch and
465
# repository formats.
466
# This pokes a little, just to be sure.
467
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
468
self.assertEqual(None, result._repository_format)
469
self.assertEqual(None, result._branch_format)
470
client.finished_test()
473
class TestBzrDirOpenBranch(TestRemote):
475
def test_backwards_compat(self):
476
self.setup_smart_server_with_call_log()
477
self.make_branch('.')
478
a_dir = BzrDir.open(self.get_url('.'))
479
self.reset_smart_call_log()
480
verb = 'BzrDir.open_branchV2'
481
self.disable_verb(verb)
482
format = a_dir.open_branch()
483
call_count = len([call for call in self.hpss_calls if
484
call.call.method == verb])
485
self.assertEqual(1, call_count)
141
487
def test_branch_present(self):
142
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )])
488
reference_format = self.get_repo_format()
489
network_name = reference_format.network_name()
490
branch_network_name = self.get_branch_format().network_name()
143
491
transport = MemoryTransport()
144
492
transport.mkdir('quack')
145
493
transport = transport.clone('quack')
146
bzrdir = RemoteBzrDir(transport, _client=client)
494
client = FakeClient(transport.base)
495
client.add_expected_call(
496
'BzrDir.open_branchV2', ('quack/',),
497
'success', ('branch', branch_network_name))
498
client.add_expected_call(
499
'BzrDir.find_repositoryV3', ('quack/',),
500
'success', ('ok', '', 'no', 'no', 'no', network_name))
501
client.add_expected_call(
502
'Branch.get_stacked_on_url', ('quack/',),
503
'error', ('NotStacked',))
504
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
147
506
result = bzrdir.open_branch()
149
[('call', 'BzrDir.open_branch', ('///quack/',)),
150
('call', 'BzrDir.find_repository', ('///quack/',))],
152
507
self.assertIsInstance(result, RemoteBranch)
153
508
self.assertEqual(bzrdir, result.bzrdir)
509
client.finished_test()
155
511
def test_branch_missing(self):
156
client = FakeClient([(('nobranch',), )])
157
512
transport = MemoryTransport()
158
513
transport.mkdir('quack')
159
514
transport = transport.clone('quack')
160
bzrdir = RemoteBzrDir(transport, _client=client)
515
client = FakeClient(transport.base)
516
client.add_error_response('nobranch')
517
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
161
519
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
162
520
self.assertEqual(
163
[('call', 'BzrDir.open_branch', ('///quack/',))],
521
[('call', 'BzrDir.open_branchV2', ('quack/',))],
166
def check_open_repository(self, rich_root, subtrees):
524
def test__get_tree_branch(self):
525
# _get_tree_branch is a form of open_branch, but it should only ask for
526
# branch opening, not any other network requests.
529
calls.append("Called")
531
transport = MemoryTransport()
532
# no requests on the network - catches other api calls being made.
533
client = FakeClient(transport.base)
534
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
536
# patch the open_branch call to record that it was called.
537
bzrdir.open_branch = open_branch
538
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
539
self.assertEqual(["Called"], calls)
540
self.assertEqual([], client._calls)
542
def test_url_quoting_of_path(self):
543
# Relpaths on the wire should not be URL-escaped. So "~" should be
544
# transmitted as "~", not "%7E".
545
transport = RemoteTCPTransport('bzr://localhost/~hello/')
546
client = FakeClient(transport.base)
547
reference_format = self.get_repo_format()
548
network_name = reference_format.network_name()
549
branch_network_name = self.get_branch_format().network_name()
550
client.add_expected_call(
551
'BzrDir.open_branchV2', ('~hello/',),
552
'success', ('branch', branch_network_name))
553
client.add_expected_call(
554
'BzrDir.find_repositoryV3', ('~hello/',),
555
'success', ('ok', '', 'no', 'no', 'no', network_name))
556
client.add_expected_call(
557
'Branch.get_stacked_on_url', ('~hello/',),
558
'error', ('NotStacked',))
559
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
561
result = bzrdir.open_branch()
562
client.finished_test()
564
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
565
reference_format = self.get_repo_format()
566
network_name = reference_format.network_name()
567
transport = MemoryTransport()
568
transport.mkdir('quack')
569
transport = transport.clone('quack')
168
571
rich_response = 'yes'
200
605
RemoteBzrDirFormat.probe_transport, OldServerTransport())
608
class TestBzrDirCreateBranch(TestRemote):
610
def test_backwards_compat(self):
611
self.setup_smart_server_with_call_log()
612
repo = self.make_repository('.')
613
self.reset_smart_call_log()
614
self.disable_verb('BzrDir.create_branch')
615
branch = repo.bzrdir.create_branch()
616
create_branch_call_count = len([call for call in self.hpss_calls if
617
call.call.method == 'BzrDir.create_branch'])
618
self.assertEqual(1, create_branch_call_count)
620
def test_current_server(self):
621
transport = self.get_transport('.')
622
transport = transport.clone('quack')
623
self.make_repository('quack')
624
client = FakeClient(transport.base)
625
reference_bzrdir_format = bzrdir.format_registry.get('default')()
626
reference_format = reference_bzrdir_format.get_branch_format()
627
network_name = reference_format.network_name()
628
reference_repo_fmt = reference_bzrdir_format.repository_format
629
reference_repo_name = reference_repo_fmt.network_name()
630
client.add_expected_call(
631
'BzrDir.create_branch', ('quack/', network_name),
632
'success', ('ok', network_name, '', 'no', 'no', 'yes',
633
reference_repo_name))
634
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
636
branch = a_bzrdir.create_branch()
637
# We should have got a remote branch
638
self.assertIsInstance(branch, remote.RemoteBranch)
639
# its format should have the settings from the response
640
format = branch._format
641
self.assertEqual(network_name, format.network_name())
644
class TestBzrDirCreateRepository(TestRemote):
646
def test_backwards_compat(self):
647
self.setup_smart_server_with_call_log()
648
bzrdir = self.make_bzrdir('.')
649
self.reset_smart_call_log()
650
self.disable_verb('BzrDir.create_repository')
651
repo = bzrdir.create_repository()
652
create_repo_call_count = len([call for call in self.hpss_calls if
653
call.call.method == 'BzrDir.create_repository'])
654
self.assertEqual(1, create_repo_call_count)
656
def test_current_server(self):
657
transport = self.get_transport('.')
658
transport = transport.clone('quack')
659
self.make_bzrdir('quack')
660
client = FakeClient(transport.base)
661
reference_bzrdir_format = bzrdir.format_registry.get('default')()
662
reference_format = reference_bzrdir_format.repository_format
663
network_name = reference_format.network_name()
664
client.add_expected_call(
665
'BzrDir.create_repository', ('quack/',
666
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
667
'success', ('ok', 'no', 'no', 'no', network_name))
668
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
670
repo = a_bzrdir.create_repository()
671
# We should have got a remote repository
672
self.assertIsInstance(repo, remote.RemoteRepository)
673
# its format should have the settings from the response
674
format = repo._format
675
self.assertFalse(format.rich_root_data)
676
self.assertFalse(format.supports_tree_reference)
677
self.assertFalse(format.supports_external_lookups)
678
self.assertEqual(network_name, format.network_name())
681
class TestBzrDirOpenRepository(TestRemote):
683
def test_backwards_compat_1_2_3(self):
684
# fallback all the way to the first version.
685
reference_format = self.get_repo_format()
686
network_name = reference_format.network_name()
687
client = FakeClient('bzr://example.com/')
688
client.add_unknown_method_response('BzrDir.find_repositoryV3')
689
client.add_unknown_method_response('BzrDir.find_repositoryV2')
690
client.add_success_response('ok', '', 'no', 'no')
691
# A real repository instance will be created to determine the network
693
client.add_success_response_with_body(
694
"Bazaar-NG meta directory, format 1\n", 'ok')
695
client.add_success_response_with_body(
696
reference_format.get_format_string(), 'ok')
697
# PackRepository wants to do a stat
698
client.add_success_response('stat', '0', '65535')
699
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
701
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
703
repo = bzrdir.open_repository()
705
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
706
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
707
('call', 'BzrDir.find_repository', ('quack/',)),
708
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
709
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
710
('call', 'stat', ('/quack/.bzr/repository',)),
713
self.assertEqual(network_name, repo._format.network_name())
715
def test_backwards_compat_2(self):
716
# fallback to find_repositoryV2
717
reference_format = self.get_repo_format()
718
network_name = reference_format.network_name()
719
client = FakeClient('bzr://example.com/')
720
client.add_unknown_method_response('BzrDir.find_repositoryV3')
721
client.add_success_response('ok', '', 'no', 'no', 'no')
722
# A real repository instance will be created to determine the network
724
client.add_success_response_with_body(
725
"Bazaar-NG meta directory, format 1\n", 'ok')
726
client.add_success_response_with_body(
727
reference_format.get_format_string(), 'ok')
728
# PackRepository wants to do a stat
729
client.add_success_response('stat', '0', '65535')
730
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
732
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
734
repo = bzrdir.open_repository()
736
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
737
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
738
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
739
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
740
('call', 'stat', ('/quack/.bzr/repository',)),
743
self.assertEqual(network_name, repo._format.network_name())
745
def test_current_server(self):
746
reference_format = self.get_repo_format()
747
network_name = reference_format.network_name()
748
transport = MemoryTransport()
749
transport.mkdir('quack')
750
transport = transport.clone('quack')
751
client = FakeClient(transport.base)
752
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
753
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
755
repo = bzrdir.open_repository()
757
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
759
self.assertEqual(network_name, repo._format.network_name())
762
class TestBzrDirFormatInitializeEx(TestRemote):
764
def test_success(self):
765
"""Simple test for typical successful call."""
766
fmt = bzrdir.RemoteBzrDirFormat()
767
default_format_name = BzrDirFormat.get_default_format().network_name()
768
transport = self.get_transport()
769
client = FakeClient(transport.base)
770
client.add_expected_call(
771
'BzrDirFormat.initialize_ex_1.16',
772
(default_format_name, 'path', 'False', 'False', 'False', '',
773
'', '', '', 'False'),
775
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
776
'bzrdir fmt', 'False', '', '', 'repo lock token'))
777
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
778
# it's currently hard to test that without supplying a real remote
779
# transport connected to a real server.
780
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
781
transport, False, False, False, None, None, None, None, False)
782
client.finished_test()
784
def test_error(self):
785
"""Error responses are translated, e.g. 'PermissionDenied' raises the
786
corresponding error from the client.
788
fmt = bzrdir.RemoteBzrDirFormat()
789
default_format_name = BzrDirFormat.get_default_format().network_name()
790
transport = self.get_transport()
791
client = FakeClient(transport.base)
792
client.add_expected_call(
793
'BzrDirFormat.initialize_ex_1.16',
794
(default_format_name, 'path', 'False', 'False', 'False', '',
795
'', '', '', 'False'),
797
('PermissionDenied', 'path', 'extra info'))
798
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
799
# it's currently hard to test that without supplying a real remote
800
# transport connected to a real server.
801
err = self.assertRaises(errors.PermissionDenied,
802
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
803
False, False, False, None, None, None, None, False)
804
self.assertEqual('path', err.path)
805
self.assertEqual(': extra info', err.extra)
806
client.finished_test()
808
def test_error_from_real_server(self):
809
"""Integration test for error translation."""
810
transport = self.make_smart_server('foo')
811
transport = transport.clone('no-such-path')
812
fmt = bzrdir.RemoteBzrDirFormat()
813
err = self.assertRaises(errors.NoSuchFile,
814
fmt.initialize_on_transport_ex, transport, create_prefix=False)
203
817
class OldSmartClient(object):
204
818
"""A fake smart client for test_old_version that just returns a version one
205
819
response to the 'hello' (query version) command.
225
842
return OldSmartClient()
228
class TestBranchLastRevisionInfo(tests.TestCase):
845
class RemoteBzrDirTestCase(TestRemote):
847
def make_remote_bzrdir(self, transport, client):
848
"""Make a RemotebzrDir using 'client' as the _client."""
849
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
853
class RemoteBranchTestCase(RemoteBzrDirTestCase):
855
def make_remote_branch(self, transport, client):
856
"""Make a RemoteBranch using 'client' as its _SmartClient.
858
A RemoteBzrDir and RemoteRepository will also be created to fill out
859
the RemoteBranch, albeit with stub values for some of their attributes.
861
# we do not want bzrdir to make any remote calls, so use False as its
862
# _client. If it tries to make a remote call, this will fail
864
bzrdir = self.make_remote_bzrdir(transport, False)
865
repo = RemoteRepository(bzrdir, None, _client=client)
866
branch_format = self.get_branch_format()
867
format = RemoteBranchFormat(network_name=branch_format.network_name())
868
return RemoteBranch(bzrdir, repo, _client=client, format=format)
871
class TestBranchGetParent(RemoteBranchTestCase):
873
def test_no_parent(self):
874
# in an empty branch we decode the response properly
875
transport = MemoryTransport()
876
client = FakeClient(transport.base)
877
client.add_expected_call(
878
'Branch.get_stacked_on_url', ('quack/',),
879
'error', ('NotStacked',))
880
client.add_expected_call(
881
'Branch.get_parent', ('quack/',),
883
transport.mkdir('quack')
884
transport = transport.clone('quack')
885
branch = self.make_remote_branch(transport, client)
886
result = branch.get_parent()
887
client.finished_test()
888
self.assertEqual(None, result)
890
def test_parent_relative(self):
891
transport = MemoryTransport()
892
client = FakeClient(transport.base)
893
client.add_expected_call(
894
'Branch.get_stacked_on_url', ('kwaak/',),
895
'error', ('NotStacked',))
896
client.add_expected_call(
897
'Branch.get_parent', ('kwaak/',),
898
'success', ('../foo/',))
899
transport.mkdir('kwaak')
900
transport = transport.clone('kwaak')
901
branch = self.make_remote_branch(transport, client)
902
result = branch.get_parent()
903
self.assertEqual(transport.clone('../foo').base, result)
905
def test_parent_absolute(self):
906
transport = MemoryTransport()
907
client = FakeClient(transport.base)
908
client.add_expected_call(
909
'Branch.get_stacked_on_url', ('kwaak/',),
910
'error', ('NotStacked',))
911
client.add_expected_call(
912
'Branch.get_parent', ('kwaak/',),
913
'success', ('http://foo/',))
914
transport.mkdir('kwaak')
915
transport = transport.clone('kwaak')
916
branch = self.make_remote_branch(transport, client)
917
result = branch.get_parent()
918
self.assertEqual('http://foo/', result)
919
client.finished_test()
922
class TestBranchSetParentLocation(RemoteBranchTestCase):
924
def test_no_parent(self):
925
# We call the verb when setting parent to None
926
transport = MemoryTransport()
927
client = FakeClient(transport.base)
928
client.add_expected_call(
929
'Branch.get_stacked_on_url', ('quack/',),
930
'error', ('NotStacked',))
931
client.add_expected_call(
932
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
934
transport.mkdir('quack')
935
transport = transport.clone('quack')
936
branch = self.make_remote_branch(transport, client)
937
branch._lock_token = 'b'
938
branch._repo_lock_token = 'r'
939
branch._set_parent_location(None)
940
client.finished_test()
942
def test_parent(self):
943
transport = MemoryTransport()
944
client = FakeClient(transport.base)
945
client.add_expected_call(
946
'Branch.get_stacked_on_url', ('kwaak/',),
947
'error', ('NotStacked',))
948
client.add_expected_call(
949
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
951
transport.mkdir('kwaak')
952
transport = transport.clone('kwaak')
953
branch = self.make_remote_branch(transport, client)
954
branch._lock_token = 'b'
955
branch._repo_lock_token = 'r'
956
branch._set_parent_location('foo')
957
client.finished_test()
959
def test_backwards_compat(self):
960
self.setup_smart_server_with_call_log()
961
branch = self.make_branch('.')
962
self.reset_smart_call_log()
963
verb = 'Branch.set_parent_location'
964
self.disable_verb(verb)
965
branch.set_parent('http://foo/')
966
self.assertLength(12, self.hpss_calls)
969
class TestBranchGetTagsBytes(RemoteBranchTestCase):
971
def test_backwards_compat(self):
972
self.setup_smart_server_with_call_log()
973
branch = self.make_branch('.')
974
self.reset_smart_call_log()
975
verb = 'Branch.get_tags_bytes'
976
self.disable_verb(verb)
977
branch.tags.get_tag_dict()
978
call_count = len([call for call in self.hpss_calls if
979
call.call.method == verb])
980
self.assertEqual(1, call_count)
982
def test_trivial(self):
983
transport = MemoryTransport()
984
client = FakeClient(transport.base)
985
client.add_expected_call(
986
'Branch.get_stacked_on_url', ('quack/',),
987
'error', ('NotStacked',))
988
client.add_expected_call(
989
'Branch.get_tags_bytes', ('quack/',),
991
transport.mkdir('quack')
992
transport = transport.clone('quack')
993
branch = self.make_remote_branch(transport, client)
994
result = branch.tags.get_tag_dict()
995
client.finished_test()
996
self.assertEqual({}, result)
999
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
230
1001
def test_empty_branch(self):
231
1002
# in an empty branch we decode the response properly
232
client = FakeClient([(('ok', '0', 'null:'), )])
233
1003
transport = MemoryTransport()
1004
client = FakeClient(transport.base)
1005
client.add_expected_call(
1006
'Branch.get_stacked_on_url', ('quack/',),
1007
'error', ('NotStacked',))
1008
client.add_expected_call(
1009
'Branch.last_revision_info', ('quack/',),
1010
'success', ('ok', '0', 'null:'))
234
1011
transport.mkdir('quack')
235
1012
transport = transport.clone('quack')
236
# we do not want bzrdir to make any remote calls
237
bzrdir = RemoteBzrDir(transport, _client=False)
238
branch = RemoteBranch(bzrdir, None, _client=client)
1013
branch = self.make_remote_branch(transport, client)
239
1014
result = branch.last_revision_info()
242
[('call', 'Branch.last_revision_info', ('///quack/',))],
1015
client.finished_test()
244
1016
self.assertEqual((0, NULL_REVISION), result)
246
1018
def test_non_empty_branch(self):
247
1019
# in a non-empty branch we also decode the response properly
248
1020
revid = u'\xc8'.encode('utf8')
249
client = FakeClient([(('ok', '2', revid), )])
250
1021
transport = MemoryTransport()
1022
client = FakeClient(transport.base)
1023
client.add_expected_call(
1024
'Branch.get_stacked_on_url', ('kwaak/',),
1025
'error', ('NotStacked',))
1026
client.add_expected_call(
1027
'Branch.last_revision_info', ('kwaak/',),
1028
'success', ('ok', '2', revid))
251
1029
transport.mkdir('kwaak')
252
1030
transport = transport.clone('kwaak')
253
# we do not want bzrdir to make any remote calls
254
bzrdir = RemoteBzrDir(transport, _client=False)
255
branch = RemoteBranch(bzrdir, None, _client=client)
1031
branch = self.make_remote_branch(transport, client)
256
1032
result = branch.last_revision_info()
259
[('call', 'Branch.last_revision_info', ('///kwaak/',))],
261
1033
self.assertEqual((2, revid), result)
264
class TestBranchSetLastRevision(tests.TestCase):
1036
class TestBranch_get_stacked_on_url(TestRemote):
1037
"""Test Branch._get_stacked_on_url rpc"""
1039
def test_get_stacked_on_invalid_url(self):
1040
# test that asking for a stacked on url the server can't access works.
1041
# This isn't perfect, but then as we're in the same process there
1042
# really isn't anything we can do to be 100% sure that the server
1043
# doesn't just open in - this test probably needs to be rewritten using
1044
# a spawn()ed server.
1045
stacked_branch = self.make_branch('stacked', format='1.9')
1046
memory_branch = self.make_branch('base', format='1.9')
1047
vfs_url = self.get_vfs_only_url('base')
1048
stacked_branch.set_stacked_on_url(vfs_url)
1049
transport = stacked_branch.bzrdir.root_transport
1050
client = FakeClient(transport.base)
1051
client.add_expected_call(
1052
'Branch.get_stacked_on_url', ('stacked/',),
1053
'success', ('ok', vfs_url))
1054
# XXX: Multiple calls are bad, this second call documents what is
1056
client.add_expected_call(
1057
'Branch.get_stacked_on_url', ('stacked/',),
1058
'success', ('ok', vfs_url))
1059
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1061
repo_fmt = remote.RemoteRepositoryFormat()
1062
repo_fmt._custom_format = stacked_branch.repository._format
1063
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1065
result = branch.get_stacked_on_url()
1066
self.assertEqual(vfs_url, result)
1068
def test_backwards_compatible(self):
1069
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1070
base_branch = self.make_branch('base', format='1.6')
1071
stacked_branch = self.make_branch('stacked', format='1.6')
1072
stacked_branch.set_stacked_on_url('../base')
1073
client = FakeClient(self.get_url())
1074
branch_network_name = self.get_branch_format().network_name()
1075
client.add_expected_call(
1076
'BzrDir.open_branchV2', ('stacked/',),
1077
'success', ('branch', branch_network_name))
1078
client.add_expected_call(
1079
'BzrDir.find_repositoryV3', ('stacked/',),
1080
'success', ('ok', '', 'no', 'no', 'yes',
1081
stacked_branch.repository._format.network_name()))
1082
# called twice, once from constructor and then again by us
1083
client.add_expected_call(
1084
'Branch.get_stacked_on_url', ('stacked/',),
1085
'unknown', ('Branch.get_stacked_on_url',))
1086
client.add_expected_call(
1087
'Branch.get_stacked_on_url', ('stacked/',),
1088
'unknown', ('Branch.get_stacked_on_url',))
1089
# this will also do vfs access, but that goes direct to the transport
1090
# and isn't seen by the FakeClient.
1091
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1092
remote.RemoteBzrDirFormat(), _client=client)
1093
branch = bzrdir.open_branch()
1094
result = branch.get_stacked_on_url()
1095
self.assertEqual('../base', result)
1096
client.finished_test()
1097
# it's in the fallback list both for the RemoteRepository and its vfs
1099
self.assertEqual(1, len(branch.repository._fallback_repositories))
1101
len(branch.repository._real_repository._fallback_repositories))
1103
def test_get_stacked_on_real_branch(self):
1104
base_branch = self.make_branch('base', format='1.6')
1105
stacked_branch = self.make_branch('stacked', format='1.6')
1106
stacked_branch.set_stacked_on_url('../base')
1107
reference_format = self.get_repo_format()
1108
network_name = reference_format.network_name()
1109
client = FakeClient(self.get_url())
1110
branch_network_name = self.get_branch_format().network_name()
1111
client.add_expected_call(
1112
'BzrDir.open_branchV2', ('stacked/',),
1113
'success', ('branch', branch_network_name))
1114
client.add_expected_call(
1115
'BzrDir.find_repositoryV3', ('stacked/',),
1116
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1117
# called twice, once from constructor and then again by us
1118
client.add_expected_call(
1119
'Branch.get_stacked_on_url', ('stacked/',),
1120
'success', ('ok', '../base'))
1121
client.add_expected_call(
1122
'Branch.get_stacked_on_url', ('stacked/',),
1123
'success', ('ok', '../base'))
1124
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1125
remote.RemoteBzrDirFormat(), _client=client)
1126
branch = bzrdir.open_branch()
1127
result = branch.get_stacked_on_url()
1128
self.assertEqual('../base', result)
1129
client.finished_test()
1130
# it's in the fallback list both for the RemoteRepository.
1131
self.assertEqual(1, len(branch.repository._fallback_repositories))
1132
# And we haven't had to construct a real repository.
1133
self.assertEqual(None, branch.repository._real_repository)
1136
class TestBranchSetLastRevision(RemoteBranchTestCase):
266
1138
def test_set_empty(self):
267
1139
# set_revision_history([]) is translated to calling
268
1140
# Branch.set_last_revision(path, '') on the wire.
269
client = FakeClient([
271
(('ok', 'branch token', 'repo token'), ),
276
1141
transport = MemoryTransport()
277
1142
transport.mkdir('branch')
278
1143
transport = transport.clone('branch')
280
bzrdir = RemoteBzrDir(transport, _client=False)
281
branch = RemoteBranch(bzrdir, None, _client=client)
1145
client = FakeClient(transport.base)
1146
client.add_expected_call(
1147
'Branch.get_stacked_on_url', ('branch/',),
1148
'error', ('NotStacked',))
1149
client.add_expected_call(
1150
'Branch.lock_write', ('branch/', '', ''),
1151
'success', ('ok', 'branch token', 'repo token'))
1152
client.add_expected_call(
1153
'Branch.last_revision_info',
1155
'success', ('ok', '0', 'null:'))
1156
client.add_expected_call(
1157
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1159
client.add_expected_call(
1160
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1162
branch = self.make_remote_branch(transport, client)
282
1163
# This is a hack to work around the problem that RemoteBranch currently
283
1164
# unnecessarily invokes _ensure_real upon a call to lock_write.
284
1165
branch._ensure_real = lambda: None
285
1166
branch.lock_write()
287
1167
result = branch.set_revision_history([])
289
[('call', 'Branch.set_last_revision',
290
('///branch/', 'branch token', 'repo token', 'null:'))],
293
1169
self.assertEqual(None, result)
1170
client.finished_test()
295
1172
def test_set_nonempty(self):
296
1173
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
297
1174
# Branch.set_last_revision(path, rev-idN) on the wire.
298
client = FakeClient([
300
(('ok', 'branch token', 'repo token'), ),
305
1175
transport = MemoryTransport()
306
1176
transport.mkdir('branch')
307
1177
transport = transport.clone('branch')
309
bzrdir = RemoteBzrDir(transport, _client=False)
310
branch = RemoteBranch(bzrdir, None, _client=client)
1179
client = FakeClient(transport.base)
1180
client.add_expected_call(
1181
'Branch.get_stacked_on_url', ('branch/',),
1182
'error', ('NotStacked',))
1183
client.add_expected_call(
1184
'Branch.lock_write', ('branch/', '', ''),
1185
'success', ('ok', 'branch token', 'repo token'))
1186
client.add_expected_call(
1187
'Branch.last_revision_info',
1189
'success', ('ok', '0', 'null:'))
1191
encoded_body = bz2.compress('\n'.join(lines))
1192
client.add_success_response_with_body(encoded_body, 'ok')
1193
client.add_expected_call(
1194
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1196
client.add_expected_call(
1197
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1199
branch = self.make_remote_branch(transport, client)
311
1200
# This is a hack to work around the problem that RemoteBranch currently
312
1201
# unnecessarily invokes _ensure_real upon a call to lock_write.
313
1202
branch._ensure_real = lambda: None
314
1203
# Lock the branch, reset the record of remote calls.
315
1204
branch.lock_write()
318
1205
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
320
[('call', 'Branch.set_last_revision',
321
('///branch/', 'branch token', 'repo token', 'rev-id2'))],
324
1207
self.assertEqual(None, result)
1208
client.finished_test()
326
1210
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
1211
transport = MemoryTransport()
336
1212
transport.mkdir('branch')
337
1213
transport = transport.clone('branch')
1214
# A response of 'NoSuchRevision' is translated into an exception.
1215
client = FakeClient(transport.base)
1216
client.add_expected_call(
1217
'Branch.get_stacked_on_url', ('branch/',),
1218
'error', ('NotStacked',))
1219
client.add_expected_call(
1220
'Branch.lock_write', ('branch/', '', ''),
1221
'success', ('ok', 'branch token', 'repo token'))
1222
client.add_expected_call(
1223
'Branch.last_revision_info',
1225
'success', ('ok', '0', 'null:'))
1226
# get_graph calls to construct the revision history, for the set_rh
1229
encoded_body = bz2.compress('\n'.join(lines))
1230
client.add_success_response_with_body(encoded_body, 'ok')
1231
client.add_expected_call(
1232
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1233
'error', ('NoSuchRevision', 'rev-id'))
1234
client.add_expected_call(
1235
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
339
bzrdir = RemoteBzrDir(transport, _client=False)
340
branch = RemoteBranch(bzrdir, None, _client=client)
341
branch._ensure_real = lambda: None
1238
branch = self.make_remote_branch(transport, client)
342
1239
branch.lock_write()
345
1240
self.assertRaises(
346
1241
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'.
1243
client.finished_test()
1245
def test_tip_change_rejected(self):
1246
"""TipChangeRejected responses cause a TipChangeRejected exception to
1249
transport = MemoryTransport()
1250
transport.mkdir('branch')
1251
transport = transport.clone('branch')
1252
client = FakeClient(transport.base)
1253
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1254
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1255
client.add_expected_call(
1256
'Branch.get_stacked_on_url', ('branch/',),
1257
'error', ('NotStacked',))
1258
client.add_expected_call(
1259
'Branch.lock_write', ('branch/', '', ''),
1260
'success', ('ok', 'branch token', 'repo token'))
1261
client.add_expected_call(
1262
'Branch.last_revision_info',
1264
'success', ('ok', '0', 'null:'))
1266
encoded_body = bz2.compress('\n'.join(lines))
1267
client.add_success_response_with_body(encoded_body, 'ok')
1268
client.add_expected_call(
1269
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1270
'error', ('TipChangeRejected', rejection_msg_utf8))
1271
client.add_expected_call(
1272
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1274
branch = self.make_remote_branch(transport, client)
1275
branch._ensure_real = lambda: None
1277
# The 'TipChangeRejected' error response triggered by calling
1278
# set_revision_history causes a TipChangeRejected exception.
1279
err = self.assertRaises(
1280
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1281
# The UTF-8 message from the response has been decoded into a unicode
1283
self.assertIsInstance(err.msg, unicode)
1284
self.assertEqual(rejection_msg_unicode, err.msg)
1286
client.finished_test()
1289
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1291
def test_set_last_revision_info(self):
1292
# set_last_revision_info(num, 'rev-id') is translated to calling
1293
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1294
transport = MemoryTransport()
1295
transport.mkdir('branch')
1296
transport = transport.clone('branch')
1297
client = FakeClient(transport.base)
1298
# get_stacked_on_url
1299
client.add_error_response('NotStacked')
1301
client.add_success_response('ok', 'branch token', 'repo token')
1302
# query the current revision
1303
client.add_success_response('ok', '0', 'null:')
1305
client.add_success_response('ok')
1307
client.add_success_response('ok')
1309
branch = self.make_remote_branch(transport, client)
1310
# Lock the branch, reset the record of remote calls.
1313
result = branch.set_last_revision_info(1234, 'a-revision-id')
1315
[('call', 'Branch.last_revision_info', ('branch/',)),
1316
('call', 'Branch.set_last_revision_info',
1317
('branch/', 'branch token', 'repo token',
1318
'1234', 'a-revision-id'))],
1320
self.assertEqual(None, result)
1322
def test_no_such_revision(self):
1323
# A response of 'NoSuchRevision' is translated into an exception.
1324
transport = MemoryTransport()
1325
transport.mkdir('branch')
1326
transport = transport.clone('branch')
1327
client = FakeClient(transport.base)
1328
# get_stacked_on_url
1329
client.add_error_response('NotStacked')
1331
client.add_success_response('ok', 'branch token', 'repo token')
1333
client.add_error_response('NoSuchRevision', 'revid')
1335
client.add_success_response('ok')
1337
branch = self.make_remote_branch(transport, client)
1338
# Lock the branch, reset the record of remote calls.
1343
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1346
def lock_remote_branch(self, branch):
1347
"""Trick a RemoteBranch into thinking it is locked."""
1348
branch._lock_mode = 'w'
1349
branch._lock_count = 2
1350
branch._lock_token = 'branch token'
1351
branch._repo_lock_token = 'repo token'
1352
branch.repository._lock_mode = 'w'
1353
branch.repository._lock_count = 2
1354
branch.repository._lock_token = 'repo token'
1356
def test_backwards_compatibility(self):
1357
"""If the server does not support the Branch.set_last_revision_info
1358
verb (which is new in 1.4), then the client falls back to VFS methods.
1360
# This test is a little messy. Unlike most tests in this file, it
1361
# doesn't purely test what a Remote* object sends over the wire, and
1362
# how it reacts to responses from the wire. It instead relies partly
1363
# on asserting that the RemoteBranch will call
1364
# self._real_branch.set_last_revision_info(...).
1366
# First, set up our RemoteBranch with a FakeClient that raises
1367
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1368
transport = MemoryTransport()
1369
transport.mkdir('branch')
1370
transport = transport.clone('branch')
1371
client = FakeClient(transport.base)
1372
client.add_expected_call(
1373
'Branch.get_stacked_on_url', ('branch/',),
1374
'error', ('NotStacked',))
1375
client.add_expected_call(
1376
'Branch.last_revision_info',
1378
'success', ('ok', '0', 'null:'))
1379
client.add_expected_call(
1380
'Branch.set_last_revision_info',
1381
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1382
'unknown', 'Branch.set_last_revision_info')
1384
branch = self.make_remote_branch(transport, client)
1385
class StubRealBranch(object):
1388
def set_last_revision_info(self, revno, revision_id):
1390
('set_last_revision_info', revno, revision_id))
1391
def _clear_cached_state(self):
1393
real_branch = StubRealBranch()
1394
branch._real_branch = real_branch
1395
self.lock_remote_branch(branch)
1397
# Call set_last_revision_info, and verify it behaved as expected.
1398
result = branch.set_last_revision_info(1234, 'a-revision-id')
1400
[('set_last_revision_info', 1234, 'a-revision-id')],
1402
client.finished_test()
1404
def test_unexpected_error(self):
1405
# If the server sends an error the client doesn't understand, it gets
1406
# turned into an UnknownErrorFromSmartServer, which is presented as a
1407
# non-internal error to the user.
1408
transport = MemoryTransport()
1409
transport.mkdir('branch')
1410
transport = transport.clone('branch')
1411
client = FakeClient(transport.base)
1412
# get_stacked_on_url
1413
client.add_error_response('NotStacked')
1415
client.add_success_response('ok', 'branch token', 'repo token')
1417
client.add_error_response('UnexpectedError')
1419
client.add_success_response('ok')
1421
branch = self.make_remote_branch(transport, client)
1422
# Lock the branch, reset the record of remote calls.
1426
err = self.assertRaises(
1427
errors.UnknownErrorFromSmartServer,
1428
branch.set_last_revision_info, 123, 'revid')
1429
self.assertEqual(('UnexpectedError',), err.error_tuple)
1432
def test_tip_change_rejected(self):
1433
"""TipChangeRejected responses cause a TipChangeRejected exception to
1436
transport = MemoryTransport()
1437
transport.mkdir('branch')
1438
transport = transport.clone('branch')
1439
client = FakeClient(transport.base)
1440
# get_stacked_on_url
1441
client.add_error_response('NotStacked')
1443
client.add_success_response('ok', 'branch token', 'repo token')
1445
client.add_error_response('TipChangeRejected', 'rejection message')
1447
client.add_success_response('ok')
1449
branch = self.make_remote_branch(transport, client)
1450
# Lock the branch, reset the record of remote calls.
1452
self.addCleanup(branch.unlock)
1455
# The 'TipChangeRejected' error response triggered by calling
1456
# set_last_revision_info causes a TipChangeRejected exception.
1457
err = self.assertRaises(
1458
errors.TipChangeRejected,
1459
branch.set_last_revision_info, 123, 'revid')
1460
self.assertEqual('rejection message', err.msg)
1463
class TestBranchGetSetConfig(RemoteBranchTestCase):
359
1465
def test_get_branch_conf(self):
360
1466
# 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')
1467
client = FakeClient()
1468
client.add_expected_call(
1469
'Branch.get_stacked_on_url', ('memory:///',),
1470
'error', ('NotStacked',),)
1471
client.add_success_response_with_body('# config file body', 'ok')
1472
transport = MemoryTransport()
1473
branch = self.make_remote_branch(transport, client)
1474
config = branch.get_config()
1475
config.has_explicit_nickname()
370
1476
self.assertEqual(
371
[('call_expecting_body', 'Branch.get_config_file', ('///quack/',))],
1477
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1478
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
373
self.assertEqual('config file body', result.read())
376
class TestBranchLockWrite(tests.TestCase):
1481
def test_get_multi_line_branch_conf(self):
1482
# Make sure that multiple-line branch.conf files are supported
1484
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1485
client = FakeClient()
1486
client.add_expected_call(
1487
'Branch.get_stacked_on_url', ('memory:///',),
1488
'error', ('NotStacked',),)
1489
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1490
transport = MemoryTransport()
1491
branch = self.make_remote_branch(transport, client)
1492
config = branch.get_config()
1493
self.assertEqual(u'2', config.get_user_option('b'))
1495
def test_set_option(self):
1496
client = FakeClient()
1497
client.add_expected_call(
1498
'Branch.get_stacked_on_url', ('memory:///',),
1499
'error', ('NotStacked',),)
1500
client.add_expected_call(
1501
'Branch.lock_write', ('memory:///', '', ''),
1502
'success', ('ok', 'branch token', 'repo token'))
1503
client.add_expected_call(
1504
'Branch.set_config_option', ('memory:///', 'branch token',
1505
'repo token', 'foo', 'bar', ''),
1507
client.add_expected_call(
1508
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1510
transport = MemoryTransport()
1511
branch = self.make_remote_branch(transport, client)
1513
config = branch._get_config()
1514
config.set_option('foo', 'bar')
1516
client.finished_test()
1518
def test_backwards_compat_set_option(self):
1519
self.setup_smart_server_with_call_log()
1520
branch = self.make_branch('.')
1521
verb = 'Branch.set_config_option'
1522
self.disable_verb(verb)
1524
self.addCleanup(branch.unlock)
1525
self.reset_smart_call_log()
1526
branch._get_config().set_option('value', 'name')
1527
self.assertLength(10, self.hpss_calls)
1528
self.assertEqual('value', branch._get_config().get_option('name'))
1531
class TestBranchLockWrite(RemoteBranchTestCase):
378
1533
def test_lock_write_unlockable(self):
379
client = FakeClient([(('UnlockableTransport', ), '')])
380
1534
transport = MemoryTransport()
1535
client = FakeClient(transport.base)
1536
client.add_expected_call(
1537
'Branch.get_stacked_on_url', ('quack/',),
1538
'error', ('NotStacked',),)
1539
client.add_expected_call(
1540
'Branch.lock_write', ('quack/', '', ''),
1541
'error', ('UnlockableTransport',))
381
1542
transport.mkdir('quack')
382
1543
transport = transport.clone('quack')
383
# we do not want bzrdir to make any remote calls
384
bzrdir = RemoteBzrDir(transport, _client=False)
385
branch = RemoteBranch(bzrdir, None, _client=client)
1544
branch = self.make_remote_branch(transport, client)
386
1545
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1546
client.finished_test()
1549
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1551
def test__get_config(self):
1552
client = FakeClient()
1553
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1554
transport = MemoryTransport()
1555
bzrdir = self.make_remote_bzrdir(transport, client)
1556
config = bzrdir.get_config()
1557
self.assertEqual('/', config.get_default_stack_on())
387
1558
self.assertEqual(
388
[('call', 'Branch.lock_write', ('///quack/', '', ''))],
1559
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1562
def test_set_option_uses_vfs(self):
1563
self.setup_smart_server_with_call_log()
1564
bzrdir = self.make_bzrdir('.')
1565
self.reset_smart_call_log()
1566
config = bzrdir.get_config()
1567
config.set_default_stack_on('/')
1568
self.assertLength(3, self.hpss_calls)
1570
def test_backwards_compat_get_option(self):
1571
self.setup_smart_server_with_call_log()
1572
bzrdir = self.make_bzrdir('.')
1573
verb = 'BzrDir.get_config_file'
1574
self.disable_verb(verb)
1575
self.reset_smart_call_log()
1576
self.assertEqual(None,
1577
bzrdir._get_config().get_option('default_stack_on'))
1578
self.assertLength(3, self.hpss_calls)
392
1581
class TestTransportIsReadonly(tests.TestCase):
394
1583
def test_true(self):
395
client = FakeClient([(('yes',), '')])
1584
client = FakeClient()
1585
client.add_success_response('yes')
396
1586
transport = RemoteTransport('bzr://example.com/', medium=False,
398
1588
self.assertEqual(True, transport.is_readonly())
1748
class TestRepositoryGetGraph(TestRemoteRepository):
1750
def test_get_graph(self):
1751
# get_graph returns a graph with a custom parents provider.
1752
transport_path = 'quack'
1753
repo, client = self.setup_fake_client_and_repository(transport_path)
1754
graph = repo.get_graph()
1755
self.assertNotEqual(graph._parents_provider, repo)
1758
class TestRepositoryGetParentMap(TestRemoteRepository):
1760
def test_get_parent_map_caching(self):
1761
# get_parent_map returns from cache until unlock()
1762
# setup a reponse with two revisions
1763
r1 = u'\u0e33'.encode('utf8')
1764
r2 = u'\u0dab'.encode('utf8')
1765
lines = [' '.join([r2, r1]), r1]
1766
encoded_body = bz2.compress('\n'.join(lines))
1768
transport_path = 'quack'
1769
repo, client = self.setup_fake_client_and_repository(transport_path)
1770
client.add_success_response_with_body(encoded_body, 'ok')
1771
client.add_success_response_with_body(encoded_body, 'ok')
1773
graph = repo.get_graph()
1774
parents = graph.get_parent_map([r2])
1775
self.assertEqual({r2: (r1,)}, parents)
1776
# locking and unlocking deeper should not reset
1779
parents = graph.get_parent_map([r1])
1780
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1782
[('call_with_body_bytes_expecting_body',
1783
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1787
# now we call again, and it should use the second response.
1789
graph = repo.get_graph()
1790
parents = graph.get_parent_map([r1])
1791
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1793
[('call_with_body_bytes_expecting_body',
1794
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1796
('call_with_body_bytes_expecting_body',
1797
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1803
def test_get_parent_map_reconnects_if_unknown_method(self):
1804
transport_path = 'quack'
1805
rev_id = 'revision-id'
1806
repo, client = self.setup_fake_client_and_repository(transport_path)
1807
client.add_unknown_method_response('Repository.get_parent_map')
1808
client.add_success_response_with_body(rev_id, 'ok')
1809
self.assertFalse(client._medium._is_remote_before((1, 2)))
1810
parents = repo.get_parent_map([rev_id])
1812
[('call_with_body_bytes_expecting_body',
1813
'Repository.get_parent_map', ('quack/', 'include-missing:',
1815
('disconnect medium',),
1816
('call_expecting_body', 'Repository.get_revision_graph',
1819
# The medium is now marked as being connected to an older server
1820
self.assertTrue(client._medium._is_remote_before((1, 2)))
1821
self.assertEqual({rev_id: ('null:',)}, parents)
1823
def test_get_parent_map_fallback_parentless_node(self):
1824
"""get_parent_map falls back to get_revision_graph on old servers. The
1825
results from get_revision_graph are tweaked to match the get_parent_map
1828
Specifically, a {key: ()} result from get_revision_graph means "no
1829
parents" for that key, which in get_parent_map results should be
1830
represented as {key: ('null:',)}.
1832
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1834
rev_id = 'revision-id'
1835
transport_path = 'quack'
1836
repo, client = self.setup_fake_client_and_repository(transport_path)
1837
client.add_success_response_with_body(rev_id, 'ok')
1838
client._medium._remember_remote_is_before((1, 2))
1839
parents = repo.get_parent_map([rev_id])
1841
[('call_expecting_body', 'Repository.get_revision_graph',
1844
self.assertEqual({rev_id: ('null:',)}, parents)
1846
def test_get_parent_map_unexpected_response(self):
1847
repo, client = self.setup_fake_client_and_repository('path')
1848
client.add_success_response('something unexpected!')
1850
errors.UnexpectedSmartServerResponse,
1851
repo.get_parent_map, ['a-revision-id'])
1853
def test_get_parent_map_negative_caches_missing_keys(self):
1854
self.setup_smart_server_with_call_log()
1855
repo = self.make_repository('foo')
1856
self.assertIsInstance(repo, RemoteRepository)
1858
self.addCleanup(repo.unlock)
1859
self.reset_smart_call_log()
1860
graph = repo.get_graph()
1861
self.assertEqual({},
1862
graph.get_parent_map(['some-missing', 'other-missing']))
1863
self.assertLength(1, self.hpss_calls)
1864
# No call if we repeat this
1865
self.reset_smart_call_log()
1866
graph = repo.get_graph()
1867
self.assertEqual({},
1868
graph.get_parent_map(['some-missing', 'other-missing']))
1869
self.assertLength(0, self.hpss_calls)
1870
# Asking for more unknown keys makes a request.
1871
self.reset_smart_call_log()
1872
graph = repo.get_graph()
1873
self.assertEqual({},
1874
graph.get_parent_map(['some-missing', 'other-missing',
1876
self.assertLength(1, self.hpss_calls)
1878
def disableExtraResults(self):
1879
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1880
SmartServerRepositoryGetParentMap.no_extra_results = True
1882
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1883
self.addCleanup(reset_values)
1885
def test_null_cached_missing_and_stop_key(self):
1886
self.setup_smart_server_with_call_log()
1887
# Make a branch with a single revision.
1888
builder = self.make_branch_builder('foo')
1889
builder.start_series()
1890
builder.build_snapshot('first', None, [
1891
('add', ('', 'root-id', 'directory', ''))])
1892
builder.finish_series()
1893
branch = builder.get_branch()
1894
repo = branch.repository
1895
self.assertIsInstance(repo, RemoteRepository)
1896
# Stop the server from sending extra results.
1897
self.disableExtraResults()
1899
self.addCleanup(repo.unlock)
1900
self.reset_smart_call_log()
1901
graph = repo.get_graph()
1902
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1903
# 'first' it will be a candidate for the stop_keys of subsequent
1904
# requests, and because 'null:' was queried but not returned it will be
1905
# cached as missing.
1906
self.assertEqual({'first': ('null:',)},
1907
graph.get_parent_map(['first', 'null:']))
1908
# Now query for another key. This request will pass along a recipe of
1909
# start and stop keys describing the already cached results, and this
1910
# recipe's revision count must be correct (or else it will trigger an
1911
# error from the server).
1912
self.assertEqual({}, graph.get_parent_map(['another-key']))
1913
# This assertion guards against disableExtraResults silently failing to
1914
# work, thus invalidating the test.
1915
self.assertLength(2, self.hpss_calls)
1917
def test_get_parent_map_gets_ghosts_from_result(self):
1918
# asking for a revision should negatively cache close ghosts in its
1920
self.setup_smart_server_with_call_log()
1921
tree = self.make_branch_and_memory_tree('foo')
1924
builder = treebuilder.TreeBuilder()
1925
builder.start_tree(tree)
1927
builder.finish_tree()
1928
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1929
rev_id = tree.commit('')
1933
self.addCleanup(tree.unlock)
1934
repo = tree.branch.repository
1935
self.assertIsInstance(repo, RemoteRepository)
1937
repo.get_parent_map([rev_id])
1938
self.reset_smart_call_log()
1939
# Now asking for rev_id's ghost parent should not make calls
1940
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1941
self.assertLength(0, self.hpss_calls)
1944
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1946
def test_allows_new_revisions(self):
1947
"""get_parent_map's results can be updated by commit."""
1948
smart_server = server.SmartTCPServer_for_testing()
1949
smart_server.setUp()
1950
self.addCleanup(smart_server.tearDown)
1951
self.make_branch('branch')
1952
branch = Branch.open(smart_server.get_url() + '/branch')
1953
tree = branch.create_checkout('tree', lightweight=True)
1955
self.addCleanup(tree.unlock)
1956
graph = tree.branch.repository.get_graph()
1957
# This provides an opportunity for the missing rev-id to be cached.
1958
self.assertEqual({}, graph.get_parent_map(['rev1']))
1959
tree.commit('message', rev_id='rev1')
1960
graph = tree.branch.repository.get_graph()
1961
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
531
1964
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
533
1966
def test_null_revision(self):
534
1967
# a null revision has the predictable result {}, we should have no wire
535
1968
# traffic when calling it with this argument
536
responses = [(('notused', ), '')]
537
1969
transport_path = 'empty'
538
repo, client = self.setup_fake_client_and_repository(
539
responses, transport_path)
540
result = repo.get_revision_graph(NULL_REVISION)
1970
repo, client = self.setup_fake_client_and_repository(transport_path)
1971
client.add_success_response('notused')
1972
# actual RemoteRepository.get_revision_graph is gone, but there's an
1973
# equivalent private method for testing
1974
result = repo._get_revision_graph(NULL_REVISION)
541
1975
self.assertEqual([], client._calls)
542
1976
self.assertEqual({}, result)
769
2326
self.assertFalse(isinstance(dest_repo, RemoteRepository))
770
2327
self.assertTrue(isinstance(src_repo, RemoteRepository))
771
2328
src_repo.copy_content_into(dest_repo)
2331
class _StubRealPackRepository(object):
2333
def __init__(self, calls):
2335
self._pack_collection = _StubPackCollection(calls)
2337
def is_in_write_group(self):
2340
def refresh_data(self):
2341
self.calls.append(('pack collection reload_pack_names',))
2344
class _StubPackCollection(object):
2346
def __init__(self, calls):
2350
self.calls.append(('pack collection autopack',))
2353
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2354
"""Tests for RemoteRepository.autopack implementation."""
2357
"""When the server returns 'ok' and there's no _real_repository, then
2358
nothing else happens: the autopack method is done.
2360
transport_path = 'quack'
2361
repo, client = self.setup_fake_client_and_repository(transport_path)
2362
client.add_expected_call(
2363
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2365
client.finished_test()
2367
def test_ok_with_real_repo(self):
2368
"""When the server returns 'ok' and there is a _real_repository, then
2369
the _real_repository's reload_pack_name's method will be called.
2371
transport_path = 'quack'
2372
repo, client = self.setup_fake_client_and_repository(transport_path)
2373
client.add_expected_call(
2374
'PackRepository.autopack', ('quack/',),
2376
repo._real_repository = _StubRealPackRepository(client._calls)
2379
[('call', 'PackRepository.autopack', ('quack/',)),
2380
('pack collection reload_pack_names',)],
2383
def test_backwards_compatibility(self):
2384
"""If the server does not recognise the PackRepository.autopack verb,
2385
fallback to the real_repository's implementation.
2387
transport_path = 'quack'
2388
repo, client = self.setup_fake_client_and_repository(transport_path)
2389
client.add_unknown_method_response('PackRepository.autopack')
2390
def stub_ensure_real():
2391
client._calls.append(('_ensure_real',))
2392
repo._real_repository = _StubRealPackRepository(client._calls)
2393
repo._ensure_real = stub_ensure_real
2396
[('call', 'PackRepository.autopack', ('quack/',)),
2398
('pack collection autopack',)],
2402
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2403
"""Base class for unit tests for bzrlib.remote._translate_error."""
2405
def translateTuple(self, error_tuple, **context):
2406
"""Call _translate_error with an ErrorFromSmartServer built from the
2409
:param error_tuple: A tuple of a smart server response, as would be
2410
passed to an ErrorFromSmartServer.
2411
:kwargs context: context items to call _translate_error with.
2413
:returns: The error raised by _translate_error.
2415
# Raise the ErrorFromSmartServer before passing it as an argument,
2416
# because _translate_error may need to re-raise it with a bare 'raise'
2418
server_error = errors.ErrorFromSmartServer(error_tuple)
2419
translated_error = self.translateErrorFromSmartServer(
2420
server_error, **context)
2421
return translated_error
2423
def translateErrorFromSmartServer(self, error_object, **context):
2424
"""Like translateTuple, but takes an already constructed
2425
ErrorFromSmartServer rather than a tuple.
2429
except errors.ErrorFromSmartServer, server_error:
2430
translated_error = self.assertRaises(
2431
errors.BzrError, remote._translate_error, server_error,
2433
return translated_error
2436
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2437
"""Unit tests for bzrlib.remote._translate_error.
2439
Given an ErrorFromSmartServer (which has an error tuple from a smart
2440
server) and some context, _translate_error raises more specific errors from
2443
This test case covers the cases where _translate_error succeeds in
2444
translating an ErrorFromSmartServer to something better. See
2445
TestErrorTranslationRobustness for other cases.
2448
def test_NoSuchRevision(self):
2449
branch = self.make_branch('')
2451
translated_error = self.translateTuple(
2452
('NoSuchRevision', revid), branch=branch)
2453
expected_error = errors.NoSuchRevision(branch, revid)
2454
self.assertEqual(expected_error, translated_error)
2456
def test_nosuchrevision(self):
2457
repository = self.make_repository('')
2459
translated_error = self.translateTuple(
2460
('nosuchrevision', revid), repository=repository)
2461
expected_error = errors.NoSuchRevision(repository, revid)
2462
self.assertEqual(expected_error, translated_error)
2464
def test_nobranch(self):
2465
bzrdir = self.make_bzrdir('')
2466
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2467
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2468
self.assertEqual(expected_error, translated_error)
2470
def test_LockContention(self):
2471
translated_error = self.translateTuple(('LockContention',))
2472
expected_error = errors.LockContention('(remote lock)')
2473
self.assertEqual(expected_error, translated_error)
2475
def test_UnlockableTransport(self):
2476
bzrdir = self.make_bzrdir('')
2477
translated_error = self.translateTuple(
2478
('UnlockableTransport',), bzrdir=bzrdir)
2479
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2480
self.assertEqual(expected_error, translated_error)
2482
def test_LockFailed(self):
2483
lock = 'str() of a server lock'
2484
why = 'str() of why'
2485
translated_error = self.translateTuple(('LockFailed', lock, why))
2486
expected_error = errors.LockFailed(lock, why)
2487
self.assertEqual(expected_error, translated_error)
2489
def test_TokenMismatch(self):
2490
token = 'a lock token'
2491
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2492
expected_error = errors.TokenMismatch(token, '(remote token)')
2493
self.assertEqual(expected_error, translated_error)
2495
def test_Diverged(self):
2496
branch = self.make_branch('a')
2497
other_branch = self.make_branch('b')
2498
translated_error = self.translateTuple(
2499
('Diverged',), branch=branch, other_branch=other_branch)
2500
expected_error = errors.DivergedBranches(branch, other_branch)
2501
self.assertEqual(expected_error, translated_error)
2503
def test_ReadError_no_args(self):
2505
translated_error = self.translateTuple(('ReadError',), path=path)
2506
expected_error = errors.ReadError(path)
2507
self.assertEqual(expected_error, translated_error)
2509
def test_ReadError(self):
2511
translated_error = self.translateTuple(('ReadError', path))
2512
expected_error = errors.ReadError(path)
2513
self.assertEqual(expected_error, translated_error)
2515
def test_PermissionDenied_no_args(self):
2517
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2518
expected_error = errors.PermissionDenied(path)
2519
self.assertEqual(expected_error, translated_error)
2521
def test_PermissionDenied_one_arg(self):
2523
translated_error = self.translateTuple(('PermissionDenied', path))
2524
expected_error = errors.PermissionDenied(path)
2525
self.assertEqual(expected_error, translated_error)
2527
def test_PermissionDenied_one_arg_and_context(self):
2528
"""Given a choice between a path from the local context and a path on
2529
the wire, _translate_error prefers the path from the local context.
2531
local_path = 'local path'
2532
remote_path = 'remote path'
2533
translated_error = self.translateTuple(
2534
('PermissionDenied', remote_path), path=local_path)
2535
expected_error = errors.PermissionDenied(local_path)
2536
self.assertEqual(expected_error, translated_error)
2538
def test_PermissionDenied_two_args(self):
2540
extra = 'a string with extra info'
2541
translated_error = self.translateTuple(
2542
('PermissionDenied', path, extra))
2543
expected_error = errors.PermissionDenied(path, extra)
2544
self.assertEqual(expected_error, translated_error)
2547
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2548
"""Unit tests for bzrlib.remote._translate_error's robustness.
2550
TestErrorTranslationSuccess is for cases where _translate_error can
2551
translate successfully. This class about how _translate_err behaves when
2552
it fails to translate: it re-raises the original error.
2555
def test_unrecognised_server_error(self):
2556
"""If the error code from the server is not recognised, the original
2557
ErrorFromSmartServer is propagated unmodified.
2559
error_tuple = ('An unknown error tuple',)
2560
server_error = errors.ErrorFromSmartServer(error_tuple)
2561
translated_error = self.translateErrorFromSmartServer(server_error)
2562
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2563
self.assertEqual(expected_error, translated_error)
2565
def test_context_missing_a_key(self):
2566
"""In case of a bug in the client, or perhaps an unexpected response
2567
from a server, _translate_error returns the original error tuple from
2568
the server and mutters a warning.
2570
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2571
# in the context dict. So let's give it an empty context dict instead
2572
# to exercise its error recovery.
2574
error_tuple = ('NoSuchRevision', 'revid')
2575
server_error = errors.ErrorFromSmartServer(error_tuple)
2576
translated_error = self.translateErrorFromSmartServer(server_error)
2577
self.assertEqual(server_error, translated_error)
2578
# In addition to re-raising ErrorFromSmartServer, some debug info has
2579
# been muttered to the log file for developer to look at.
2580
self.assertContainsRe(
2581
self._get_log(keep_log_file=True),
2582
"Missing key 'branch' in context")
2584
def test_path_missing(self):
2585
"""Some translations (PermissionDenied, ReadError) can determine the
2586
'path' variable from either the wire or the local context. If neither
2587
has it, then an error is raised.
2589
error_tuple = ('ReadError',)
2590
server_error = errors.ErrorFromSmartServer(error_tuple)
2591
translated_error = self.translateErrorFromSmartServer(server_error)
2592
self.assertEqual(server_error, translated_error)
2593
# In addition to re-raising ErrorFromSmartServer, some debug info has
2594
# been muttered to the log file for developer to look at.
2595
self.assertContainsRe(
2596
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2599
class TestStacking(tests.TestCaseWithTransport):
2600
"""Tests for operations on stacked remote repositories.
2602
The underlying format type must support stacking.
2605
def test_access_stacked_remote(self):
2606
# based on <http://launchpad.net/bugs/261315>
2607
# make a branch stacked on another repository containing an empty
2608
# revision, then open it over hpss - we should be able to see that
2610
base_transport = self.get_transport()
2611
base_builder = self.make_branch_builder('base', format='1.9')
2612
base_builder.start_series()
2613
base_revid = base_builder.build_snapshot('rev-id', None,
2614
[('add', ('', None, 'directory', None))],
2616
base_builder.finish_series()
2617
stacked_branch = self.make_branch('stacked', format='1.9')
2618
stacked_branch.set_stacked_on_url('../base')
2619
# start a server looking at this
2620
smart_server = server.SmartTCPServer_for_testing()
2621
smart_server.setUp()
2622
self.addCleanup(smart_server.tearDown)
2623
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2624
# can get its branch and repository
2625
remote_branch = remote_bzrdir.open_branch()
2626
remote_repo = remote_branch.repository
2627
remote_repo.lock_read()
2629
# it should have an appropriate fallback repository, which should also
2630
# be a RemoteRepository
2631
self.assertLength(1, remote_repo._fallback_repositories)
2632
self.assertIsInstance(remote_repo._fallback_repositories[0],
2634
# and it has the revision committed to the underlying repository;
2635
# these have varying implementations so we try several of them
2636
self.assertTrue(remote_repo.has_revisions([base_revid]))
2637
self.assertTrue(remote_repo.has_revision(base_revid))
2638
self.assertEqual(remote_repo.get_revision(base_revid).message,
2641
remote_repo.unlock()
2643
def prepare_stacked_remote_branch(self):
2644
"""Get stacked_upon and stacked branches with content in each."""
2645
self.setup_smart_server_with_call_log()
2646
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2647
tree1.commit('rev1', rev_id='rev1')
2648
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2649
).open_workingtree()
2650
tree2.commit('local changes make me feel good.')
2651
branch2 = Branch.open(self.get_url('tree2'))
2653
self.addCleanup(branch2.unlock)
2654
return tree1.branch, branch2
2656
def test_stacked_get_parent_map(self):
2657
# the public implementation of get_parent_map obeys stacking
2658
_, branch = self.prepare_stacked_remote_branch()
2659
repo = branch.repository
2660
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2662
def test_unstacked_get_parent_map(self):
2663
# _unstacked_provider.get_parent_map ignores stacking
2664
_, branch = self.prepare_stacked_remote_branch()
2665
provider = branch.repository._unstacked_provider
2666
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2668
def fetch_stream_to_rev_order(self, stream):
2670
for kind, substream in stream:
2671
if not kind == 'revisions':
2674
for content in substream:
2675
result.append(content.key[-1])
2678
def get_ordered_revs(self, format, order):
2679
"""Get a list of the revisions in a stream to format format.
2681
:param format: The format of the target.
2682
:param order: the order that target should have requested.
2683
:result: The revision ids in the stream, in the order seen,
2684
the topological order of revisions in the source.
2686
unordered_format = bzrdir.format_registry.get(format)()
2687
target_repository_format = unordered_format.repository_format
2689
self.assertEqual(order, target_repository_format._fetch_order)
2690
trunk, stacked = self.prepare_stacked_remote_branch()
2691
source = stacked.repository._get_source(target_repository_format)
2692
tip = stacked.last_revision()
2693
revs = stacked.repository.get_ancestry(tip)
2694
search = graph.PendingAncestryResult([tip], stacked.repository)
2695
self.reset_smart_call_log()
2696
stream = source.get_stream(search)
2699
# We trust that if a revision is in the stream the rest of the new
2700
# content for it is too, as per our main fetch tests; here we are
2701
# checking that the revisions are actually included at all, and their
2703
return self.fetch_stream_to_rev_order(stream), revs
2705
def test_stacked_get_stream_unordered(self):
2706
# Repository._get_source.get_stream() from a stacked repository with
2707
# unordered yields the full data from both stacked and stacked upon
2709
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2710
self.assertEqual(set(expected_revs), set(rev_ord))
2711
# Getting unordered results should have made a streaming data request
2712
# from the server, then one from the backing branch.
2713
self.assertLength(2, self.hpss_calls)
2715
def test_stacked_get_stream_topological(self):
2716
# Repository._get_source.get_stream() from a stacked repository with
2717
# topological sorting yields the full data from both stacked and
2718
# stacked upon sources in topological order.
2719
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2720
self.assertEqual(expected_revs, rev_ord)
2721
# Getting topological sort requires VFS calls still
2722
self.assertLength(12, self.hpss_calls)
2724
def test_stacked_get_stream_groupcompress(self):
2725
# Repository._get_source.get_stream() from a stacked repository with
2726
# groupcompress sorting yields the full data from both stacked and
2727
# stacked upon sources in groupcompress order.
2728
raise tests.TestSkipped('No groupcompress ordered format available')
2729
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2730
self.assertEqual(expected_revs, reversed(rev_ord))
2731
# Getting unordered results should have made a streaming data request
2732
# from the backing branch, and one from the stacked on branch.
2733
self.assertLength(2, self.hpss_calls)
2735
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
2736
# When pulling some fixed amount of content that is more than the
2737
# source has (because some is coming from a fallback branch, no error
2738
# should be received. This was reported as bug 360791.
2739
# Need three branches: a trunk, a stacked branch, and a preexisting
2740
# branch pulling content from stacked and trunk.
2741
self.setup_smart_server_with_call_log()
2742
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
2743
r1 = trunk.commit('start')
2744
stacked_branch = trunk.branch.create_clone_on_transport(
2745
self.get_transport('stacked'), stacked_on=trunk.branch.base)
2746
local = self.make_branch('local', format='1.9-rich-root')
2747
local.repository.fetch(stacked_branch.repository,
2748
stacked_branch.last_revision())
2751
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2754
super(TestRemoteBranchEffort, self).setUp()
2755
# Create a smart server that publishes whatever the backing VFS server
2757
self.smart_server = server.SmartTCPServer_for_testing()
2758
self.smart_server.setUp(self.get_server())
2759
self.addCleanup(self.smart_server.tearDown)
2760
# Log all HPSS calls into self.hpss_calls.
2761
_SmartClient.hooks.install_named_hook(
2762
'call', self.capture_hpss_call, None)
2763
self.hpss_calls = []
2765
def capture_hpss_call(self, params):
2766
self.hpss_calls.append(params.method)
2768
def test_copy_content_into_avoids_revision_history(self):
2769
local = self.make_branch('local')
2770
remote_backing_tree = self.make_branch_and_tree('remote')
2771
remote_backing_tree.commit("Commit.")
2772
remote_branch_url = self.smart_server.get_url() + 'remote'
2773
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2774
local.repository.fetch(remote_branch.repository)
2775
self.hpss_calls = []
2776
remote_branch.copy_content_into(local)
2777
self.assertFalse('Branch.revision_history' in self.hpss_calls)