1
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for remote bzrdir/branch/repo/etc
19
These are proxy objects which act on remote objects by sending messages
20
through a smart client. The proxies are to be created when attempting to open
21
the object given a transport that supports smartserver rpc operations.
23
These tests correspond to tests.test_smart, which exercises the server side.
27
from cStringIO import StringIO
45
from bzrlib.branch import Branch
46
from bzrlib.bzrdir import BzrDir, BzrDirFormat
47
from bzrlib.remote import (
53
RemoteRepositoryFormat,
55
from bzrlib.repofmt import groupcompress_repo, pack_repo
56
from bzrlib.revision import NULL_REVISION
57
from bzrlib.smart import server, medium
58
from bzrlib.smart.client import _SmartClient
59
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
60
from bzrlib.tests import (
62
split_suite_by_condition,
66
from bzrlib.transport import get_transport, http
67
from bzrlib.transport.memory import MemoryTransport
68
from bzrlib.transport.remote import (
74
def load_tests(standard_tests, module, loader):
75
to_adapt, result = split_suite_by_condition(
76
standard_tests, condition_isinstance(BasicRemoteObjectTests))
77
smart_server_version_scenarios = [
79
{'transport_server': server.SmartTCPServer_for_testing_v2_only}),
81
{'transport_server': server.SmartTCPServer_for_testing})]
82
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
85
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
88
super(BasicRemoteObjectTests, self).setUp()
89
self.transport = self.get_transport()
90
# make a branch that can be opened over the smart transport
91
self.local_wt = BzrDir.create_standalone_workingtree('.')
94
self.transport.disconnect()
95
tests.TestCaseWithTransport.tearDown(self)
97
def test_create_remote_bzrdir(self):
98
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
99
self.assertIsInstance(b, BzrDir)
101
def test_open_remote_branch(self):
102
# open a standalone branch in the working directory
103
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
104
branch = b.open_branch()
105
self.assertIsInstance(branch, Branch)
107
def test_remote_repository(self):
108
b = BzrDir.open_from_transport(self.transport)
109
repo = b.open_repository()
110
revid = u'\xc823123123'.encode('utf8')
111
self.assertFalse(repo.has_revision(revid))
112
self.local_wt.commit(message='test commit', rev_id=revid)
113
self.assertTrue(repo.has_revision(revid))
115
def test_remote_branch_revision_history(self):
116
b = BzrDir.open_from_transport(self.transport).open_branch()
117
self.assertEqual([], b.revision_history())
118
r1 = self.local_wt.commit('1st commit')
119
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
120
self.assertEqual([r1, r2], b.revision_history())
122
def test_find_correct_format(self):
123
"""Should open a RemoteBzrDir over a RemoteTransport"""
124
fmt = BzrDirFormat.find_format(self.transport)
125
self.assertTrue(RemoteBzrDirFormat
126
in BzrDirFormat._control_server_formats)
127
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
129
def test_open_detected_smart_format(self):
130
fmt = BzrDirFormat.find_format(self.transport)
131
d = fmt.open(self.transport)
132
self.assertIsInstance(d, BzrDir)
134
def test_remote_branch_repr(self):
135
b = BzrDir.open_from_transport(self.transport).open_branch()
136
self.assertStartsWith(str(b), 'RemoteBranch(')
138
def test_remote_branch_format_supports_stacking(self):
140
self.make_branch('unstackable', format='pack-0.92')
141
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
142
self.assertFalse(b._format.supports_stacking())
143
self.make_branch('stackable', format='1.9')
144
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
145
self.assertTrue(b._format.supports_stacking())
147
def test_remote_repo_format_supports_external_references(self):
149
bd = self.make_bzrdir('unstackable', format='pack-0.92')
150
r = bd.create_repository()
151
self.assertFalse(r._format.supports_external_lookups)
152
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
153
self.assertFalse(r._format.supports_external_lookups)
154
bd = self.make_bzrdir('stackable', format='1.9')
155
r = bd.create_repository()
156
self.assertTrue(r._format.supports_external_lookups)
157
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
158
self.assertTrue(r._format.supports_external_lookups)
160
def test_remote_branch_set_append_revisions_only(self):
161
# Make a format 1.9 branch, which supports append_revisions_only
162
branch = self.make_branch('branch', format='1.9')
163
config = branch.get_config()
164
branch.set_append_revisions_only(True)
166
'True', config.get_user_option('append_revisions_only'))
167
branch.set_append_revisions_only(False)
169
'False', config.get_user_option('append_revisions_only'))
171
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
172
branch = self.make_branch('branch', format='knit')
173
config = branch.get_config()
175
errors.UpgradeRequired, branch.set_append_revisions_only, True)
178
class FakeProtocol(object):
179
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
181
def __init__(self, body, fake_client):
183
self._body_buffer = None
184
self._fake_client = fake_client
186
def read_body_bytes(self, count=-1):
187
if self._body_buffer is None:
188
self._body_buffer = StringIO(self.body)
189
bytes = self._body_buffer.read(count)
190
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
191
self._fake_client.expecting_body = False
194
def cancel_read_body(self):
195
self._fake_client.expecting_body = False
197
def read_streamed_body(self):
201
class FakeClient(_SmartClient):
202
"""Lookalike for _SmartClient allowing testing."""
204
def __init__(self, fake_medium_base='fake base'):
205
"""Create a FakeClient."""
208
self.expecting_body = False
209
# if non-None, this is the list of expected calls, with only the
210
# method name and arguments included. the body might be hard to
211
# compute so is not included. If a call is None, that call can
213
self._expected_calls = None
214
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
216
def add_expected_call(self, call_name, call_args, response_type,
217
response_args, response_body=None):
218
if self._expected_calls is None:
219
self._expected_calls = []
220
self._expected_calls.append((call_name, call_args))
221
self.responses.append((response_type, response_args, response_body))
223
def add_success_response(self, *args):
224
self.responses.append(('success', args, None))
226
def add_success_response_with_body(self, body, *args):
227
self.responses.append(('success', args, body))
228
if self._expected_calls is not None:
229
self._expected_calls.append(None)
231
def add_error_response(self, *args):
232
self.responses.append(('error', args))
234
def add_unknown_method_response(self, verb):
235
self.responses.append(('unknown', verb))
237
def finished_test(self):
238
if self._expected_calls:
239
raise AssertionError("%r finished but was still expecting %r"
240
% (self, self._expected_calls[0]))
242
def _get_next_response(self):
244
response_tuple = self.responses.pop(0)
245
except IndexError, e:
246
raise AssertionError("%r didn't expect any more calls"
248
if response_tuple[0] == 'unknown':
249
raise errors.UnknownSmartMethod(response_tuple[1])
250
elif response_tuple[0] == 'error':
251
raise errors.ErrorFromSmartServer(response_tuple[1])
252
return response_tuple
254
def _check_call(self, method, args):
255
if self._expected_calls is None:
256
# the test should be updated to say what it expects
259
next_call = self._expected_calls.pop(0)
261
raise AssertionError("%r didn't expect any more calls "
263
% (self, method, args,))
264
if next_call is None:
266
if method != next_call[0] or args != next_call[1]:
267
raise AssertionError("%r expected %r%r "
269
% (self, next_call[0], next_call[1], method, args,))
271
def call(self, method, *args):
272
self._check_call(method, args)
273
self._calls.append(('call', method, args))
274
return self._get_next_response()[1]
276
def call_expecting_body(self, method, *args):
277
self._check_call(method, args)
278
self._calls.append(('call_expecting_body', method, args))
279
result = self._get_next_response()
280
self.expecting_body = True
281
return result[1], FakeProtocol(result[2], self)
283
def call_with_body_bytes_expecting_body(self, method, args, body):
284
self._check_call(method, args)
285
self._calls.append(('call_with_body_bytes_expecting_body', method,
287
result = self._get_next_response()
288
self.expecting_body = True
289
return result[1], FakeProtocol(result[2], self)
291
def call_with_body_stream(self, args, stream):
292
# Explicitly consume the stream before checking for an error, because
293
# that's what happens a real medium.
294
stream = list(stream)
295
self._check_call(args[0], args[1:])
296
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
297
result = self._get_next_response()
298
# The second value returned from call_with_body_stream is supposed to
299
# be a response_handler object, but so far no tests depend on that.
300
response_handler = None
301
return result[1], response_handler
304
class FakeMedium(medium.SmartClientMedium):
306
def __init__(self, client_calls, base):
307
medium.SmartClientMedium.__init__(self, base)
308
self._client_calls = client_calls
310
def disconnect(self):
311
self._client_calls.append(('disconnect medium',))
314
class TestVfsHas(tests.TestCase):
316
def test_unicode_path(self):
317
client = FakeClient('/')
318
client.add_success_response('yes',)
319
transport = RemoteTransport('bzr://localhost/', _client=client)
320
filename = u'/hell\u00d8'.encode('utf8')
321
result = transport.has(filename)
323
[('call', 'has', (filename,))],
325
self.assertTrue(result)
328
class TestRemote(tests.TestCaseWithMemoryTransport):
330
def get_branch_format(self):
331
reference_bzrdir_format = bzrdir.format_registry.get('default')()
332
return reference_bzrdir_format.get_branch_format()
334
def get_repo_format(self):
335
reference_bzrdir_format = bzrdir.format_registry.get('default')()
336
return reference_bzrdir_format.repository_format
338
def assertFinished(self, fake_client):
339
"""Assert that all of a FakeClient's expected calls have occurred."""
340
fake_client.finished_test()
343
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
344
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
346
def assertRemotePath(self, expected, client_base, transport_base):
347
"""Assert that the result of
348
SmartClientMedium.remote_path_from_transport is the expected value for
349
a given client_base and transport_base.
351
client_medium = medium.SmartClientMedium(client_base)
352
transport = get_transport(transport_base)
353
result = client_medium.remote_path_from_transport(transport)
354
self.assertEqual(expected, result)
356
def test_remote_path_from_transport(self):
357
"""SmartClientMedium.remote_path_from_transport calculates a URL for
358
the given transport relative to the root of the client base URL.
360
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
361
self.assertRemotePath(
362
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
364
def assertRemotePathHTTP(self, expected, transport_base, relpath):
365
"""Assert that the result of
366
HttpTransportBase.remote_path_from_transport is the expected value for
367
a given transport_base and relpath of that transport. (Note that
368
HttpTransportBase is a subclass of SmartClientMedium)
370
base_transport = get_transport(transport_base)
371
client_medium = base_transport.get_smart_medium()
372
cloned_transport = base_transport.clone(relpath)
373
result = client_medium.remote_path_from_transport(cloned_transport)
374
self.assertEqual(expected, result)
376
def test_remote_path_from_transport_http(self):
377
"""Remote paths for HTTP transports are calculated differently to other
378
transports. They are just relative to the client base, not the root
379
directory of the host.
381
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
382
self.assertRemotePathHTTP(
383
'../xyz/', scheme + '//host/path', '../xyz/')
384
self.assertRemotePathHTTP(
385
'xyz/', scheme + '//host/path', 'xyz/')
388
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
389
"""Tests for the behaviour of client_medium.remote_is_at_least."""
391
def test_initially_unlimited(self):
392
"""A fresh medium assumes that the remote side supports all
395
client_medium = medium.SmartClientMedium('dummy base')
396
self.assertFalse(client_medium._is_remote_before((99, 99)))
398
def test__remember_remote_is_before(self):
399
"""Calling _remember_remote_is_before ratchets down the known remote
402
client_medium = medium.SmartClientMedium('dummy base')
403
# Mark the remote side as being less than 1.6. The remote side may
405
client_medium._remember_remote_is_before((1, 6))
406
self.assertTrue(client_medium._is_remote_before((1, 6)))
407
self.assertFalse(client_medium._is_remote_before((1, 5)))
408
# Calling _remember_remote_is_before again with a lower value works.
409
client_medium._remember_remote_is_before((1, 5))
410
self.assertTrue(client_medium._is_remote_before((1, 5)))
411
# You cannot call _remember_remote_is_before with a larger value.
413
AssertionError, client_medium._remember_remote_is_before, (1, 9))
416
class TestBzrDirCloningMetaDir(TestRemote):
418
def test_backwards_compat(self):
419
self.setup_smart_server_with_call_log()
420
a_dir = self.make_bzrdir('.')
421
self.reset_smart_call_log()
422
verb = 'BzrDir.cloning_metadir'
423
self.disable_verb(verb)
424
format = a_dir.cloning_metadir()
425
call_count = len([call for call in self.hpss_calls if
426
call.call.method == verb])
427
self.assertEqual(1, call_count)
429
def test_branch_reference(self):
430
transport = self.get_transport('quack')
431
referenced = self.make_branch('referenced')
432
expected = referenced.bzrdir.cloning_metadir()
433
client = FakeClient(transport.base)
434
client.add_expected_call(
435
'BzrDir.cloning_metadir', ('quack/', 'False'),
436
'error', ('BranchReference',)),
437
client.add_expected_call(
438
'BzrDir.open_branchV2', ('quack/',),
439
'success', ('ref', self.get_url('referenced'))),
440
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
442
result = a_bzrdir.cloning_metadir()
443
# We should have got a control dir matching the referenced branch.
444
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
445
self.assertEqual(expected._repository_format, result._repository_format)
446
self.assertEqual(expected._branch_format, result._branch_format)
447
self.assertFinished(client)
449
def test_current_server(self):
450
transport = self.get_transport('.')
451
transport = transport.clone('quack')
452
self.make_bzrdir('quack')
453
client = FakeClient(transport.base)
454
reference_bzrdir_format = bzrdir.format_registry.get('default')()
455
control_name = reference_bzrdir_format.network_name()
456
client.add_expected_call(
457
'BzrDir.cloning_metadir', ('quack/', 'False'),
458
'success', (control_name, '', ('branch', ''))),
459
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
461
result = a_bzrdir.cloning_metadir()
462
# We should have got a reference control dir with default branch and
463
# repository formats.
464
# This pokes a little, just to be sure.
465
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
466
self.assertEqual(None, result._repository_format)
467
self.assertEqual(None, result._branch_format)
468
self.assertFinished(client)
471
class TestBzrDirOpenBranch(TestRemote):
473
def test_backwards_compat(self):
474
self.setup_smart_server_with_call_log()
475
self.make_branch('.')
476
a_dir = BzrDir.open(self.get_url('.'))
477
self.reset_smart_call_log()
478
verb = 'BzrDir.open_branchV2'
479
self.disable_verb(verb)
480
format = a_dir.open_branch()
481
call_count = len([call for call in self.hpss_calls if
482
call.call.method == verb])
483
self.assertEqual(1, call_count)
485
def test_branch_present(self):
486
reference_format = self.get_repo_format()
487
network_name = reference_format.network_name()
488
branch_network_name = self.get_branch_format().network_name()
489
transport = MemoryTransport()
490
transport.mkdir('quack')
491
transport = transport.clone('quack')
492
client = FakeClient(transport.base)
493
client.add_expected_call(
494
'BzrDir.open_branchV2', ('quack/',),
495
'success', ('branch', branch_network_name))
496
client.add_expected_call(
497
'BzrDir.find_repositoryV3', ('quack/',),
498
'success', ('ok', '', 'no', 'no', 'no', network_name))
499
client.add_expected_call(
500
'Branch.get_stacked_on_url', ('quack/',),
501
'error', ('NotStacked',))
502
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
504
result = bzrdir.open_branch()
505
self.assertIsInstance(result, RemoteBranch)
506
self.assertEqual(bzrdir, result.bzrdir)
507
self.assertFinished(client)
509
def test_branch_missing(self):
510
transport = MemoryTransport()
511
transport.mkdir('quack')
512
transport = transport.clone('quack')
513
client = FakeClient(transport.base)
514
client.add_error_response('nobranch')
515
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
517
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
519
[('call', 'BzrDir.open_branchV2', ('quack/',))],
522
def test__get_tree_branch(self):
523
# _get_tree_branch is a form of open_branch, but it should only ask for
524
# branch opening, not any other network requests.
527
calls.append("Called")
529
transport = MemoryTransport()
530
# no requests on the network - catches other api calls being made.
531
client = FakeClient(transport.base)
532
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
534
# patch the open_branch call to record that it was called.
535
bzrdir.open_branch = open_branch
536
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
537
self.assertEqual(["Called"], calls)
538
self.assertEqual([], client._calls)
540
def test_url_quoting_of_path(self):
541
# Relpaths on the wire should not be URL-escaped. So "~" should be
542
# transmitted as "~", not "%7E".
543
transport = RemoteTCPTransport('bzr://localhost/~hello/')
544
client = FakeClient(transport.base)
545
reference_format = self.get_repo_format()
546
network_name = reference_format.network_name()
547
branch_network_name = self.get_branch_format().network_name()
548
client.add_expected_call(
549
'BzrDir.open_branchV2', ('~hello/',),
550
'success', ('branch', branch_network_name))
551
client.add_expected_call(
552
'BzrDir.find_repositoryV3', ('~hello/',),
553
'success', ('ok', '', 'no', 'no', 'no', network_name))
554
client.add_expected_call(
555
'Branch.get_stacked_on_url', ('~hello/',),
556
'error', ('NotStacked',))
557
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
559
result = bzrdir.open_branch()
560
self.assertFinished(client)
562
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
563
reference_format = self.get_repo_format()
564
network_name = reference_format.network_name()
565
transport = MemoryTransport()
566
transport.mkdir('quack')
567
transport = transport.clone('quack')
569
rich_response = 'yes'
573
subtree_response = 'yes'
575
subtree_response = 'no'
576
client = FakeClient(transport.base)
577
client.add_success_response(
578
'ok', '', rich_response, subtree_response, external_lookup,
580
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
582
result = bzrdir.open_repository()
584
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
586
self.assertIsInstance(result, RemoteRepository)
587
self.assertEqual(bzrdir, result.bzrdir)
588
self.assertEqual(rich_root, result._format.rich_root_data)
589
self.assertEqual(subtrees, result._format.supports_tree_reference)
591
def test_open_repository_sets_format_attributes(self):
592
self.check_open_repository(True, True)
593
self.check_open_repository(False, True)
594
self.check_open_repository(True, False)
595
self.check_open_repository(False, False)
596
self.check_open_repository(False, False, 'yes')
598
def test_old_server(self):
599
"""RemoteBzrDirFormat should fail to probe if the server version is too
602
self.assertRaises(errors.NotBranchError,
603
RemoteBzrDirFormat.probe_transport, OldServerTransport())
606
class TestBzrDirCreateBranch(TestRemote):
608
def test_backwards_compat(self):
609
self.setup_smart_server_with_call_log()
610
repo = self.make_repository('.')
611
self.reset_smart_call_log()
612
self.disable_verb('BzrDir.create_branch')
613
branch = repo.bzrdir.create_branch()
614
create_branch_call_count = len([call for call in self.hpss_calls if
615
call.call.method == 'BzrDir.create_branch'])
616
self.assertEqual(1, create_branch_call_count)
618
def test_current_server(self):
619
transport = self.get_transport('.')
620
transport = transport.clone('quack')
621
self.make_repository('quack')
622
client = FakeClient(transport.base)
623
reference_bzrdir_format = bzrdir.format_registry.get('default')()
624
reference_format = reference_bzrdir_format.get_branch_format()
625
network_name = reference_format.network_name()
626
reference_repo_fmt = reference_bzrdir_format.repository_format
627
reference_repo_name = reference_repo_fmt.network_name()
628
client.add_expected_call(
629
'BzrDir.create_branch', ('quack/', network_name),
630
'success', ('ok', network_name, '', 'no', 'no', 'yes',
631
reference_repo_name))
632
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
634
branch = a_bzrdir.create_branch()
635
# We should have got a remote branch
636
self.assertIsInstance(branch, remote.RemoteBranch)
637
# its format should have the settings from the response
638
format = branch._format
639
self.assertEqual(network_name, format.network_name())
642
class TestBzrDirCreateRepository(TestRemote):
644
def test_backwards_compat(self):
645
self.setup_smart_server_with_call_log()
646
bzrdir = self.make_bzrdir('.')
647
self.reset_smart_call_log()
648
self.disable_verb('BzrDir.create_repository')
649
repo = bzrdir.create_repository()
650
create_repo_call_count = len([call for call in self.hpss_calls if
651
call.call.method == 'BzrDir.create_repository'])
652
self.assertEqual(1, create_repo_call_count)
654
def test_current_server(self):
655
transport = self.get_transport('.')
656
transport = transport.clone('quack')
657
self.make_bzrdir('quack')
658
client = FakeClient(transport.base)
659
reference_bzrdir_format = bzrdir.format_registry.get('default')()
660
reference_format = reference_bzrdir_format.repository_format
661
network_name = reference_format.network_name()
662
client.add_expected_call(
663
'BzrDir.create_repository', ('quack/',
664
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
666
'success', ('ok', 'yes', 'yes', 'yes', network_name))
667
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
669
repo = a_bzrdir.create_repository()
670
# We should have got a remote repository
671
self.assertIsInstance(repo, remote.RemoteRepository)
672
# its format should have the settings from the response
673
format = repo._format
674
self.assertTrue(format.rich_root_data)
675
self.assertTrue(format.supports_tree_reference)
676
self.assertTrue(format.supports_external_lookups)
677
self.assertEqual(network_name, format.network_name())
680
class TestBzrDirOpenRepository(TestRemote):
682
def test_backwards_compat_1_2_3(self):
683
# fallback all the way to the first version.
684
reference_format = self.get_repo_format()
685
network_name = reference_format.network_name()
686
client = FakeClient('bzr://example.com/')
687
client.add_unknown_method_response('BzrDir.find_repositoryV3')
688
client.add_unknown_method_response('BzrDir.find_repositoryV2')
689
client.add_success_response('ok', '', 'no', 'no')
690
# A real repository instance will be created to determine the network
692
client.add_success_response_with_body(
693
"Bazaar-NG meta directory, format 1\n", 'ok')
694
client.add_success_response_with_body(
695
reference_format.get_format_string(), 'ok')
696
# PackRepository wants to do a stat
697
client.add_success_response('stat', '0', '65535')
698
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
700
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
702
repo = bzrdir.open_repository()
704
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
705
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
706
('call', 'BzrDir.find_repository', ('quack/',)),
707
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
708
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
709
('call', 'stat', ('/quack/.bzr/repository',)),
712
self.assertEqual(network_name, repo._format.network_name())
714
def test_backwards_compat_2(self):
715
# fallback to find_repositoryV2
716
reference_format = self.get_repo_format()
717
network_name = reference_format.network_name()
718
client = FakeClient('bzr://example.com/')
719
client.add_unknown_method_response('BzrDir.find_repositoryV3')
720
client.add_success_response('ok', '', 'no', 'no', 'no')
721
# A real repository instance will be created to determine the network
723
client.add_success_response_with_body(
724
"Bazaar-NG meta directory, format 1\n", 'ok')
725
client.add_success_response_with_body(
726
reference_format.get_format_string(), 'ok')
727
# PackRepository wants to do a stat
728
client.add_success_response('stat', '0', '65535')
729
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
731
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
733
repo = bzrdir.open_repository()
735
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
736
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
737
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
738
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
739
('call', 'stat', ('/quack/.bzr/repository',)),
742
self.assertEqual(network_name, repo._format.network_name())
744
def test_current_server(self):
745
reference_format = self.get_repo_format()
746
network_name = reference_format.network_name()
747
transport = MemoryTransport()
748
transport.mkdir('quack')
749
transport = transport.clone('quack')
750
client = FakeClient(transport.base)
751
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
752
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
754
repo = bzrdir.open_repository()
756
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
758
self.assertEqual(network_name, repo._format.network_name())
761
class TestBzrDirFormatInitializeEx(TestRemote):
763
def test_success(self):
764
"""Simple test for typical successful call."""
765
fmt = bzrdir.RemoteBzrDirFormat()
766
default_format_name = BzrDirFormat.get_default_format().network_name()
767
transport = self.get_transport()
768
client = FakeClient(transport.base)
769
client.add_expected_call(
770
'BzrDirFormat.initialize_ex_1.16',
771
(default_format_name, 'path', 'False', 'False', 'False', '',
772
'', '', '', 'False'),
774
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
775
'bzrdir fmt', 'False', '', '', 'repo lock token'))
776
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
777
# it's currently hard to test that without supplying a real remote
778
# transport connected to a real server.
779
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
780
transport, False, False, False, None, None, None, None, False)
781
self.assertFinished(client)
783
def test_error(self):
784
"""Error responses are translated, e.g. 'PermissionDenied' raises the
785
corresponding error from the client.
787
fmt = bzrdir.RemoteBzrDirFormat()
788
default_format_name = BzrDirFormat.get_default_format().network_name()
789
transport = self.get_transport()
790
client = FakeClient(transport.base)
791
client.add_expected_call(
792
'BzrDirFormat.initialize_ex_1.16',
793
(default_format_name, 'path', 'False', 'False', 'False', '',
794
'', '', '', 'False'),
796
('PermissionDenied', 'path', 'extra info'))
797
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
798
# it's currently hard to test that without supplying a real remote
799
# transport connected to a real server.
800
err = self.assertRaises(errors.PermissionDenied,
801
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
802
False, False, False, None, None, None, None, False)
803
self.assertEqual('path', err.path)
804
self.assertEqual(': extra info', err.extra)
805
self.assertFinished(client)
807
def test_error_from_real_server(self):
808
"""Integration test for error translation."""
809
transport = self.make_smart_server('foo')
810
transport = transport.clone('no-such-path')
811
fmt = bzrdir.RemoteBzrDirFormat()
812
err = self.assertRaises(errors.NoSuchFile,
813
fmt.initialize_on_transport_ex, transport, create_prefix=False)
816
class OldSmartClient(object):
817
"""A fake smart client for test_old_version that just returns a version one
818
response to the 'hello' (query version) command.
821
def get_request(self):
822
input_file = StringIO('ok\x011\n')
823
output_file = StringIO()
824
client_medium = medium.SmartSimplePipesClientMedium(
825
input_file, output_file)
826
return medium.SmartClientStreamMediumRequest(client_medium)
828
def protocol_version(self):
832
class OldServerTransport(object):
833
"""A fake transport for test_old_server that reports it's smart server
834
protocol version as version one.
840
def get_smart_client(self):
841
return OldSmartClient()
844
class RemoteBzrDirTestCase(TestRemote):
846
def make_remote_bzrdir(self, transport, client):
847
"""Make a RemotebzrDir using 'client' as the _client."""
848
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
852
class RemoteBranchTestCase(RemoteBzrDirTestCase):
854
def make_remote_branch(self, transport, client):
855
"""Make a RemoteBranch using 'client' as its _SmartClient.
857
A RemoteBzrDir and RemoteRepository will also be created to fill out
858
the RemoteBranch, albeit with stub values for some of their attributes.
860
# we do not want bzrdir to make any remote calls, so use False as its
861
# _client. If it tries to make a remote call, this will fail
863
bzrdir = self.make_remote_bzrdir(transport, False)
864
repo = RemoteRepository(bzrdir, None, _client=client)
865
branch_format = self.get_branch_format()
866
format = RemoteBranchFormat(network_name=branch_format.network_name())
867
return RemoteBranch(bzrdir, repo, _client=client, format=format)
870
class TestBranchGetParent(RemoteBranchTestCase):
872
def test_no_parent(self):
873
# in an empty branch we decode the response properly
874
transport = MemoryTransport()
875
client = FakeClient(transport.base)
876
client.add_expected_call(
877
'Branch.get_stacked_on_url', ('quack/',),
878
'error', ('NotStacked',))
879
client.add_expected_call(
880
'Branch.get_parent', ('quack/',),
882
transport.mkdir('quack')
883
transport = transport.clone('quack')
884
branch = self.make_remote_branch(transport, client)
885
result = branch.get_parent()
886
self.assertFinished(client)
887
self.assertEqual(None, result)
889
def test_parent_relative(self):
890
transport = MemoryTransport()
891
client = FakeClient(transport.base)
892
client.add_expected_call(
893
'Branch.get_stacked_on_url', ('kwaak/',),
894
'error', ('NotStacked',))
895
client.add_expected_call(
896
'Branch.get_parent', ('kwaak/',),
897
'success', ('../foo/',))
898
transport.mkdir('kwaak')
899
transport = transport.clone('kwaak')
900
branch = self.make_remote_branch(transport, client)
901
result = branch.get_parent()
902
self.assertEqual(transport.clone('../foo').base, result)
904
def test_parent_absolute(self):
905
transport = MemoryTransport()
906
client = FakeClient(transport.base)
907
client.add_expected_call(
908
'Branch.get_stacked_on_url', ('kwaak/',),
909
'error', ('NotStacked',))
910
client.add_expected_call(
911
'Branch.get_parent', ('kwaak/',),
912
'success', ('http://foo/',))
913
transport.mkdir('kwaak')
914
transport = transport.clone('kwaak')
915
branch = self.make_remote_branch(transport, client)
916
result = branch.get_parent()
917
self.assertEqual('http://foo/', result)
918
self.assertFinished(client)
921
class TestBranchSetParentLocation(RemoteBranchTestCase):
923
def test_no_parent(self):
924
# We call the verb when setting parent to None
925
transport = MemoryTransport()
926
client = FakeClient(transport.base)
927
client.add_expected_call(
928
'Branch.get_stacked_on_url', ('quack/',),
929
'error', ('NotStacked',))
930
client.add_expected_call(
931
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
933
transport.mkdir('quack')
934
transport = transport.clone('quack')
935
branch = self.make_remote_branch(transport, client)
936
branch._lock_token = 'b'
937
branch._repo_lock_token = 'r'
938
branch._set_parent_location(None)
939
self.assertFinished(client)
941
def test_parent(self):
942
transport = MemoryTransport()
943
client = FakeClient(transport.base)
944
client.add_expected_call(
945
'Branch.get_stacked_on_url', ('kwaak/',),
946
'error', ('NotStacked',))
947
client.add_expected_call(
948
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
950
transport.mkdir('kwaak')
951
transport = transport.clone('kwaak')
952
branch = self.make_remote_branch(transport, client)
953
branch._lock_token = 'b'
954
branch._repo_lock_token = 'r'
955
branch._set_parent_location('foo')
956
self.assertFinished(client)
958
def test_backwards_compat(self):
959
self.setup_smart_server_with_call_log()
960
branch = self.make_branch('.')
961
self.reset_smart_call_log()
962
verb = 'Branch.set_parent_location'
963
self.disable_verb(verb)
964
branch.set_parent('http://foo/')
965
self.assertLength(12, self.hpss_calls)
968
class TestBranchGetTagsBytes(RemoteBranchTestCase):
970
def test_backwards_compat(self):
971
self.setup_smart_server_with_call_log()
972
branch = self.make_branch('.')
973
self.reset_smart_call_log()
974
verb = 'Branch.get_tags_bytes'
975
self.disable_verb(verb)
976
branch.tags.get_tag_dict()
977
call_count = len([call for call in self.hpss_calls if
978
call.call.method == verb])
979
self.assertEqual(1, call_count)
981
def test_trivial(self):
982
transport = MemoryTransport()
983
client = FakeClient(transport.base)
984
client.add_expected_call(
985
'Branch.get_stacked_on_url', ('quack/',),
986
'error', ('NotStacked',))
987
client.add_expected_call(
988
'Branch.get_tags_bytes', ('quack/',),
990
transport.mkdir('quack')
991
transport = transport.clone('quack')
992
branch = self.make_remote_branch(transport, client)
993
result = branch.tags.get_tag_dict()
994
self.assertFinished(client)
995
self.assertEqual({}, result)
998
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1000
def test_empty_branch(self):
1001
# in an empty branch we decode the response properly
1002
transport = MemoryTransport()
1003
client = FakeClient(transport.base)
1004
client.add_expected_call(
1005
'Branch.get_stacked_on_url', ('quack/',),
1006
'error', ('NotStacked',))
1007
client.add_expected_call(
1008
'Branch.last_revision_info', ('quack/',),
1009
'success', ('ok', '0', 'null:'))
1010
transport.mkdir('quack')
1011
transport = transport.clone('quack')
1012
branch = self.make_remote_branch(transport, client)
1013
result = branch.last_revision_info()
1014
self.assertFinished(client)
1015
self.assertEqual((0, NULL_REVISION), result)
1017
def test_non_empty_branch(self):
1018
# in a non-empty branch we also decode the response properly
1019
revid = u'\xc8'.encode('utf8')
1020
transport = MemoryTransport()
1021
client = FakeClient(transport.base)
1022
client.add_expected_call(
1023
'Branch.get_stacked_on_url', ('kwaak/',),
1024
'error', ('NotStacked',))
1025
client.add_expected_call(
1026
'Branch.last_revision_info', ('kwaak/',),
1027
'success', ('ok', '2', revid))
1028
transport.mkdir('kwaak')
1029
transport = transport.clone('kwaak')
1030
branch = self.make_remote_branch(transport, client)
1031
result = branch.last_revision_info()
1032
self.assertEqual((2, revid), result)
1035
class TestBranch_get_stacked_on_url(TestRemote):
1036
"""Test Branch._get_stacked_on_url rpc"""
1038
def test_get_stacked_on_invalid_url(self):
1039
# test that asking for a stacked on url the server can't access works.
1040
# This isn't perfect, but then as we're in the same process there
1041
# really isn't anything we can do to be 100% sure that the server
1042
# doesn't just open in - this test probably needs to be rewritten using
1043
# a spawn()ed server.
1044
stacked_branch = self.make_branch('stacked', format='1.9')
1045
memory_branch = self.make_branch('base', format='1.9')
1046
vfs_url = self.get_vfs_only_url('base')
1047
stacked_branch.set_stacked_on_url(vfs_url)
1048
transport = stacked_branch.bzrdir.root_transport
1049
client = FakeClient(transport.base)
1050
client.add_expected_call(
1051
'Branch.get_stacked_on_url', ('stacked/',),
1052
'success', ('ok', vfs_url))
1053
# XXX: Multiple calls are bad, this second call documents what is
1055
client.add_expected_call(
1056
'Branch.get_stacked_on_url', ('stacked/',),
1057
'success', ('ok', vfs_url))
1058
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1060
repo_fmt = remote.RemoteRepositoryFormat()
1061
repo_fmt._custom_format = stacked_branch.repository._format
1062
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1064
result = branch.get_stacked_on_url()
1065
self.assertEqual(vfs_url, result)
1067
def test_backwards_compatible(self):
1068
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1069
base_branch = self.make_branch('base', format='1.6')
1070
stacked_branch = self.make_branch('stacked', format='1.6')
1071
stacked_branch.set_stacked_on_url('../base')
1072
client = FakeClient(self.get_url())
1073
branch_network_name = self.get_branch_format().network_name()
1074
client.add_expected_call(
1075
'BzrDir.open_branchV2', ('stacked/',),
1076
'success', ('branch', branch_network_name))
1077
client.add_expected_call(
1078
'BzrDir.find_repositoryV3', ('stacked/',),
1079
'success', ('ok', '', 'no', 'no', 'yes',
1080
stacked_branch.repository._format.network_name()))
1081
# called twice, once from constructor and then again by us
1082
client.add_expected_call(
1083
'Branch.get_stacked_on_url', ('stacked/',),
1084
'unknown', ('Branch.get_stacked_on_url',))
1085
client.add_expected_call(
1086
'Branch.get_stacked_on_url', ('stacked/',),
1087
'unknown', ('Branch.get_stacked_on_url',))
1088
# this will also do vfs access, but that goes direct to the transport
1089
# and isn't seen by the FakeClient.
1090
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1091
remote.RemoteBzrDirFormat(), _client=client)
1092
branch = bzrdir.open_branch()
1093
result = branch.get_stacked_on_url()
1094
self.assertEqual('../base', result)
1095
self.assertFinished(client)
1096
# it's in the fallback list both for the RemoteRepository and its vfs
1098
self.assertEqual(1, len(branch.repository._fallback_repositories))
1100
len(branch.repository._real_repository._fallback_repositories))
1102
def test_get_stacked_on_real_branch(self):
1103
base_branch = self.make_branch('base', format='1.6')
1104
stacked_branch = self.make_branch('stacked', format='1.6')
1105
stacked_branch.set_stacked_on_url('../base')
1106
reference_format = self.get_repo_format()
1107
network_name = reference_format.network_name()
1108
client = FakeClient(self.get_url())
1109
branch_network_name = self.get_branch_format().network_name()
1110
client.add_expected_call(
1111
'BzrDir.open_branchV2', ('stacked/',),
1112
'success', ('branch', branch_network_name))
1113
client.add_expected_call(
1114
'BzrDir.find_repositoryV3', ('stacked/',),
1115
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1116
# called twice, once from constructor and then again by us
1117
client.add_expected_call(
1118
'Branch.get_stacked_on_url', ('stacked/',),
1119
'success', ('ok', '../base'))
1120
client.add_expected_call(
1121
'Branch.get_stacked_on_url', ('stacked/',),
1122
'success', ('ok', '../base'))
1123
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1124
remote.RemoteBzrDirFormat(), _client=client)
1125
branch = bzrdir.open_branch()
1126
result = branch.get_stacked_on_url()
1127
self.assertEqual('../base', result)
1128
self.assertFinished(client)
1129
# it's in the fallback list both for the RemoteRepository.
1130
self.assertEqual(1, len(branch.repository._fallback_repositories))
1131
# And we haven't had to construct a real repository.
1132
self.assertEqual(None, branch.repository._real_repository)
1135
class TestBranchSetLastRevision(RemoteBranchTestCase):
1137
def test_set_empty(self):
1138
# set_revision_history([]) is translated to calling
1139
# Branch.set_last_revision(path, '') on the wire.
1140
transport = MemoryTransport()
1141
transport.mkdir('branch')
1142
transport = transport.clone('branch')
1144
client = FakeClient(transport.base)
1145
client.add_expected_call(
1146
'Branch.get_stacked_on_url', ('branch/',),
1147
'error', ('NotStacked',))
1148
client.add_expected_call(
1149
'Branch.lock_write', ('branch/', '', ''),
1150
'success', ('ok', 'branch token', 'repo token'))
1151
client.add_expected_call(
1152
'Branch.last_revision_info',
1154
'success', ('ok', '0', 'null:'))
1155
client.add_expected_call(
1156
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1158
client.add_expected_call(
1159
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1161
branch = self.make_remote_branch(transport, client)
1162
# This is a hack to work around the problem that RemoteBranch currently
1163
# unnecessarily invokes _ensure_real upon a call to lock_write.
1164
branch._ensure_real = lambda: None
1166
result = branch.set_revision_history([])
1168
self.assertEqual(None, result)
1169
self.assertFinished(client)
1171
def test_set_nonempty(self):
1172
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1173
# Branch.set_last_revision(path, rev-idN) on the wire.
1174
transport = MemoryTransport()
1175
transport.mkdir('branch')
1176
transport = transport.clone('branch')
1178
client = FakeClient(transport.base)
1179
client.add_expected_call(
1180
'Branch.get_stacked_on_url', ('branch/',),
1181
'error', ('NotStacked',))
1182
client.add_expected_call(
1183
'Branch.lock_write', ('branch/', '', ''),
1184
'success', ('ok', 'branch token', 'repo token'))
1185
client.add_expected_call(
1186
'Branch.last_revision_info',
1188
'success', ('ok', '0', 'null:'))
1190
encoded_body = bz2.compress('\n'.join(lines))
1191
client.add_success_response_with_body(encoded_body, 'ok')
1192
client.add_expected_call(
1193
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1195
client.add_expected_call(
1196
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1198
branch = self.make_remote_branch(transport, client)
1199
# This is a hack to work around the problem that RemoteBranch currently
1200
# unnecessarily invokes _ensure_real upon a call to lock_write.
1201
branch._ensure_real = lambda: None
1202
# Lock the branch, reset the record of remote calls.
1204
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1206
self.assertEqual(None, result)
1207
self.assertFinished(client)
1209
def test_no_such_revision(self):
1210
transport = MemoryTransport()
1211
transport.mkdir('branch')
1212
transport = transport.clone('branch')
1213
# A response of 'NoSuchRevision' is translated into an exception.
1214
client = FakeClient(transport.base)
1215
client.add_expected_call(
1216
'Branch.get_stacked_on_url', ('branch/',),
1217
'error', ('NotStacked',))
1218
client.add_expected_call(
1219
'Branch.lock_write', ('branch/', '', ''),
1220
'success', ('ok', 'branch token', 'repo token'))
1221
client.add_expected_call(
1222
'Branch.last_revision_info',
1224
'success', ('ok', '0', 'null:'))
1225
# get_graph calls to construct the revision history, for the set_rh
1228
encoded_body = bz2.compress('\n'.join(lines))
1229
client.add_success_response_with_body(encoded_body, 'ok')
1230
client.add_expected_call(
1231
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1232
'error', ('NoSuchRevision', 'rev-id'))
1233
client.add_expected_call(
1234
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1237
branch = self.make_remote_branch(transport, client)
1240
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1242
self.assertFinished(client)
1244
def test_tip_change_rejected(self):
1245
"""TipChangeRejected responses cause a TipChangeRejected exception to
1248
transport = MemoryTransport()
1249
transport.mkdir('branch')
1250
transport = transport.clone('branch')
1251
client = FakeClient(transport.base)
1252
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1253
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1254
client.add_expected_call(
1255
'Branch.get_stacked_on_url', ('branch/',),
1256
'error', ('NotStacked',))
1257
client.add_expected_call(
1258
'Branch.lock_write', ('branch/', '', ''),
1259
'success', ('ok', 'branch token', 'repo token'))
1260
client.add_expected_call(
1261
'Branch.last_revision_info',
1263
'success', ('ok', '0', 'null:'))
1265
encoded_body = bz2.compress('\n'.join(lines))
1266
client.add_success_response_with_body(encoded_body, 'ok')
1267
client.add_expected_call(
1268
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1269
'error', ('TipChangeRejected', rejection_msg_utf8))
1270
client.add_expected_call(
1271
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1273
branch = self.make_remote_branch(transport, client)
1274
branch._ensure_real = lambda: None
1276
# The 'TipChangeRejected' error response triggered by calling
1277
# set_revision_history causes a TipChangeRejected exception.
1278
err = self.assertRaises(
1279
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1280
# The UTF-8 message from the response has been decoded into a unicode
1282
self.assertIsInstance(err.msg, unicode)
1283
self.assertEqual(rejection_msg_unicode, err.msg)
1285
self.assertFinished(client)
1288
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1290
def test_set_last_revision_info(self):
1291
# set_last_revision_info(num, 'rev-id') is translated to calling
1292
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1293
transport = MemoryTransport()
1294
transport.mkdir('branch')
1295
transport = transport.clone('branch')
1296
client = FakeClient(transport.base)
1297
# get_stacked_on_url
1298
client.add_error_response('NotStacked')
1300
client.add_success_response('ok', 'branch token', 'repo token')
1301
# query the current revision
1302
client.add_success_response('ok', '0', 'null:')
1304
client.add_success_response('ok')
1306
client.add_success_response('ok')
1308
branch = self.make_remote_branch(transport, client)
1309
# Lock the branch, reset the record of remote calls.
1312
result = branch.set_last_revision_info(1234, 'a-revision-id')
1314
[('call', 'Branch.last_revision_info', ('branch/',)),
1315
('call', 'Branch.set_last_revision_info',
1316
('branch/', 'branch token', 'repo token',
1317
'1234', 'a-revision-id'))],
1319
self.assertEqual(None, result)
1321
def test_no_such_revision(self):
1322
# A response of 'NoSuchRevision' is translated into an exception.
1323
transport = MemoryTransport()
1324
transport.mkdir('branch')
1325
transport = transport.clone('branch')
1326
client = FakeClient(transport.base)
1327
# get_stacked_on_url
1328
client.add_error_response('NotStacked')
1330
client.add_success_response('ok', 'branch token', 'repo token')
1332
client.add_error_response('NoSuchRevision', 'revid')
1334
client.add_success_response('ok')
1336
branch = self.make_remote_branch(transport, client)
1337
# Lock the branch, reset the record of remote calls.
1342
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1345
def lock_remote_branch(self, branch):
1346
"""Trick a RemoteBranch into thinking it is locked."""
1347
branch._lock_mode = 'w'
1348
branch._lock_count = 2
1349
branch._lock_token = 'branch token'
1350
branch._repo_lock_token = 'repo token'
1351
branch.repository._lock_mode = 'w'
1352
branch.repository._lock_count = 2
1353
branch.repository._lock_token = 'repo token'
1355
def test_backwards_compatibility(self):
1356
"""If the server does not support the Branch.set_last_revision_info
1357
verb (which is new in 1.4), then the client falls back to VFS methods.
1359
# This test is a little messy. Unlike most tests in this file, it
1360
# doesn't purely test what a Remote* object sends over the wire, and
1361
# how it reacts to responses from the wire. It instead relies partly
1362
# on asserting that the RemoteBranch will call
1363
# self._real_branch.set_last_revision_info(...).
1365
# First, set up our RemoteBranch with a FakeClient that raises
1366
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1367
transport = MemoryTransport()
1368
transport.mkdir('branch')
1369
transport = transport.clone('branch')
1370
client = FakeClient(transport.base)
1371
client.add_expected_call(
1372
'Branch.get_stacked_on_url', ('branch/',),
1373
'error', ('NotStacked',))
1374
client.add_expected_call(
1375
'Branch.last_revision_info',
1377
'success', ('ok', '0', 'null:'))
1378
client.add_expected_call(
1379
'Branch.set_last_revision_info',
1380
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1381
'unknown', 'Branch.set_last_revision_info')
1383
branch = self.make_remote_branch(transport, client)
1384
class StubRealBranch(object):
1387
def set_last_revision_info(self, revno, revision_id):
1389
('set_last_revision_info', revno, revision_id))
1390
def _clear_cached_state(self):
1392
real_branch = StubRealBranch()
1393
branch._real_branch = real_branch
1394
self.lock_remote_branch(branch)
1396
# Call set_last_revision_info, and verify it behaved as expected.
1397
result = branch.set_last_revision_info(1234, 'a-revision-id')
1399
[('set_last_revision_info', 1234, 'a-revision-id')],
1401
self.assertFinished(client)
1403
def test_unexpected_error(self):
1404
# If the server sends an error the client doesn't understand, it gets
1405
# turned into an UnknownErrorFromSmartServer, which is presented as a
1406
# non-internal error to the user.
1407
transport = MemoryTransport()
1408
transport.mkdir('branch')
1409
transport = transport.clone('branch')
1410
client = FakeClient(transport.base)
1411
# get_stacked_on_url
1412
client.add_error_response('NotStacked')
1414
client.add_success_response('ok', 'branch token', 'repo token')
1416
client.add_error_response('UnexpectedError')
1418
client.add_success_response('ok')
1420
branch = self.make_remote_branch(transport, client)
1421
# Lock the branch, reset the record of remote calls.
1425
err = self.assertRaises(
1426
errors.UnknownErrorFromSmartServer,
1427
branch.set_last_revision_info, 123, 'revid')
1428
self.assertEqual(('UnexpectedError',), err.error_tuple)
1431
def test_tip_change_rejected(self):
1432
"""TipChangeRejected responses cause a TipChangeRejected exception to
1435
transport = MemoryTransport()
1436
transport.mkdir('branch')
1437
transport = transport.clone('branch')
1438
client = FakeClient(transport.base)
1439
# get_stacked_on_url
1440
client.add_error_response('NotStacked')
1442
client.add_success_response('ok', 'branch token', 'repo token')
1444
client.add_error_response('TipChangeRejected', 'rejection message')
1446
client.add_success_response('ok')
1448
branch = self.make_remote_branch(transport, client)
1449
# Lock the branch, reset the record of remote calls.
1451
self.addCleanup(branch.unlock)
1454
# The 'TipChangeRejected' error response triggered by calling
1455
# set_last_revision_info causes a TipChangeRejected exception.
1456
err = self.assertRaises(
1457
errors.TipChangeRejected,
1458
branch.set_last_revision_info, 123, 'revid')
1459
self.assertEqual('rejection message', err.msg)
1462
class TestBranchGetSetConfig(RemoteBranchTestCase):
1464
def test_get_branch_conf(self):
1465
# in an empty branch we decode the response properly
1466
client = FakeClient()
1467
client.add_expected_call(
1468
'Branch.get_stacked_on_url', ('memory:///',),
1469
'error', ('NotStacked',),)
1470
client.add_success_response_with_body('# config file body', 'ok')
1471
transport = MemoryTransport()
1472
branch = self.make_remote_branch(transport, client)
1473
config = branch.get_config()
1474
config.has_explicit_nickname()
1476
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1477
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1480
def test_get_multi_line_branch_conf(self):
1481
# Make sure that multiple-line branch.conf files are supported
1483
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1484
client = FakeClient()
1485
client.add_expected_call(
1486
'Branch.get_stacked_on_url', ('memory:///',),
1487
'error', ('NotStacked',),)
1488
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1489
transport = MemoryTransport()
1490
branch = self.make_remote_branch(transport, client)
1491
config = branch.get_config()
1492
self.assertEqual(u'2', config.get_user_option('b'))
1494
def test_set_option(self):
1495
client = FakeClient()
1496
client.add_expected_call(
1497
'Branch.get_stacked_on_url', ('memory:///',),
1498
'error', ('NotStacked',),)
1499
client.add_expected_call(
1500
'Branch.lock_write', ('memory:///', '', ''),
1501
'success', ('ok', 'branch token', 'repo token'))
1502
client.add_expected_call(
1503
'Branch.set_config_option', ('memory:///', 'branch token',
1504
'repo token', 'foo', 'bar', ''),
1506
client.add_expected_call(
1507
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1509
transport = MemoryTransport()
1510
branch = self.make_remote_branch(transport, client)
1512
config = branch._get_config()
1513
config.set_option('foo', 'bar')
1515
self.assertFinished(client)
1517
def test_backwards_compat_set_option(self):
1518
self.setup_smart_server_with_call_log()
1519
branch = self.make_branch('.')
1520
verb = 'Branch.set_config_option'
1521
self.disable_verb(verb)
1523
self.addCleanup(branch.unlock)
1524
self.reset_smart_call_log()
1525
branch._get_config().set_option('value', 'name')
1526
self.assertLength(10, self.hpss_calls)
1527
self.assertEqual('value', branch._get_config().get_option('name'))
1530
class TestBranchLockWrite(RemoteBranchTestCase):
1532
def test_lock_write_unlockable(self):
1533
transport = MemoryTransport()
1534
client = FakeClient(transport.base)
1535
client.add_expected_call(
1536
'Branch.get_stacked_on_url', ('quack/',),
1537
'error', ('NotStacked',),)
1538
client.add_expected_call(
1539
'Branch.lock_write', ('quack/', '', ''),
1540
'error', ('UnlockableTransport',))
1541
transport.mkdir('quack')
1542
transport = transport.clone('quack')
1543
branch = self.make_remote_branch(transport, client)
1544
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1545
self.assertFinished(client)
1548
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1550
def test__get_config(self):
1551
client = FakeClient()
1552
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1553
transport = MemoryTransport()
1554
bzrdir = self.make_remote_bzrdir(transport, client)
1555
config = bzrdir.get_config()
1556
self.assertEqual('/', config.get_default_stack_on())
1558
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1561
def test_set_option_uses_vfs(self):
1562
self.setup_smart_server_with_call_log()
1563
bzrdir = self.make_bzrdir('.')
1564
self.reset_smart_call_log()
1565
config = bzrdir.get_config()
1566
config.set_default_stack_on('/')
1567
self.assertLength(3, self.hpss_calls)
1569
def test_backwards_compat_get_option(self):
1570
self.setup_smart_server_with_call_log()
1571
bzrdir = self.make_bzrdir('.')
1572
verb = 'BzrDir.get_config_file'
1573
self.disable_verb(verb)
1574
self.reset_smart_call_log()
1575
self.assertEqual(None,
1576
bzrdir._get_config().get_option('default_stack_on'))
1577
self.assertLength(3, self.hpss_calls)
1580
class TestTransportIsReadonly(tests.TestCase):
1582
def test_true(self):
1583
client = FakeClient()
1584
client.add_success_response('yes')
1585
transport = RemoteTransport('bzr://example.com/', medium=False,
1587
self.assertEqual(True, transport.is_readonly())
1589
[('call', 'Transport.is_readonly', ())],
1592
def test_false(self):
1593
client = FakeClient()
1594
client.add_success_response('no')
1595
transport = RemoteTransport('bzr://example.com/', medium=False,
1597
self.assertEqual(False, transport.is_readonly())
1599
[('call', 'Transport.is_readonly', ())],
1602
def test_error_from_old_server(self):
1603
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1605
Clients should treat it as a "no" response, because is_readonly is only
1606
advisory anyway (a transport could be read-write, but then the
1607
underlying filesystem could be readonly anyway).
1609
client = FakeClient()
1610
client.add_unknown_method_response('Transport.is_readonly')
1611
transport = RemoteTransport('bzr://example.com/', medium=False,
1613
self.assertEqual(False, transport.is_readonly())
1615
[('call', 'Transport.is_readonly', ())],
1619
class TestTransportMkdir(tests.TestCase):
1621
def test_permissiondenied(self):
1622
client = FakeClient()
1623
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1624
transport = RemoteTransport('bzr://example.com/', medium=False,
1626
exc = self.assertRaises(
1627
errors.PermissionDenied, transport.mkdir, 'client path')
1628
expected_error = errors.PermissionDenied('/client path', 'extra')
1629
self.assertEqual(expected_error, exc)
1632
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1634
def test_defaults_to_none(self):
1635
t = RemoteSSHTransport('bzr+ssh://example.com')
1636
self.assertIs(None, t._get_credentials()[0])
1638
def test_uses_authentication_config(self):
1639
conf = config.AuthenticationConfig()
1640
conf._get_config().update(
1641
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1644
t = RemoteSSHTransport('bzr+ssh://example.com')
1645
self.assertEqual('bar', t._get_credentials()[0])
1648
class TestRemoteRepository(TestRemote):
1649
"""Base for testing RemoteRepository protocol usage.
1651
These tests contain frozen requests and responses. We want any changes to
1652
what is sent or expected to be require a thoughtful update to these tests
1653
because they might break compatibility with different-versioned servers.
1656
def setup_fake_client_and_repository(self, transport_path):
1657
"""Create the fake client and repository for testing with.
1659
There's no real server here; we just have canned responses sent
1662
:param transport_path: Path below the root of the MemoryTransport
1663
where the repository will be created.
1665
transport = MemoryTransport()
1666
transport.mkdir(transport_path)
1667
client = FakeClient(transport.base)
1668
transport = transport.clone(transport_path)
1669
# we do not want bzrdir to make any remote calls
1670
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1672
repo = RemoteRepository(bzrdir, None, _client=client)
1676
class TestRepositoryFormat(TestRemoteRepository):
1678
def test_fast_delta(self):
1679
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1680
true_format = RemoteRepositoryFormat()
1681
true_format._network_name = true_name
1682
self.assertEqual(True, true_format.fast_deltas)
1683
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1684
false_format = RemoteRepositoryFormat()
1685
false_format._network_name = false_name
1686
self.assertEqual(False, false_format.fast_deltas)
1689
class TestRepositoryGatherStats(TestRemoteRepository):
1691
def test_revid_none(self):
1692
# ('ok',), body with revisions and size
1693
transport_path = 'quack'
1694
repo, client = self.setup_fake_client_and_repository(transport_path)
1695
client.add_success_response_with_body(
1696
'revisions: 2\nsize: 18\n', 'ok')
1697
result = repo.gather_stats(None)
1699
[('call_expecting_body', 'Repository.gather_stats',
1700
('quack/','','no'))],
1702
self.assertEqual({'revisions': 2, 'size': 18}, result)
1704
def test_revid_no_committers(self):
1705
# ('ok',), body without committers
1706
body = ('firstrev: 123456.300 3600\n'
1707
'latestrev: 654231.400 0\n'
1710
transport_path = 'quick'
1711
revid = u'\xc8'.encode('utf8')
1712
repo, client = self.setup_fake_client_and_repository(transport_path)
1713
client.add_success_response_with_body(body, 'ok')
1714
result = repo.gather_stats(revid)
1716
[('call_expecting_body', 'Repository.gather_stats',
1717
('quick/', revid, 'no'))],
1719
self.assertEqual({'revisions': 2, 'size': 18,
1720
'firstrev': (123456.300, 3600),
1721
'latestrev': (654231.400, 0),},
1724
def test_revid_with_committers(self):
1725
# ('ok',), body with committers
1726
body = ('committers: 128\n'
1727
'firstrev: 123456.300 3600\n'
1728
'latestrev: 654231.400 0\n'
1731
transport_path = 'buick'
1732
revid = u'\xc8'.encode('utf8')
1733
repo, client = self.setup_fake_client_and_repository(transport_path)
1734
client.add_success_response_with_body(body, 'ok')
1735
result = repo.gather_stats(revid, True)
1737
[('call_expecting_body', 'Repository.gather_stats',
1738
('buick/', revid, 'yes'))],
1740
self.assertEqual({'revisions': 2, 'size': 18,
1742
'firstrev': (123456.300, 3600),
1743
'latestrev': (654231.400, 0),},
1747
class TestRepositoryGetGraph(TestRemoteRepository):
1749
def test_get_graph(self):
1750
# get_graph returns a graph with a custom parents provider.
1751
transport_path = 'quack'
1752
repo, client = self.setup_fake_client_and_repository(transport_path)
1753
graph = repo.get_graph()
1754
self.assertNotEqual(graph._parents_provider, repo)
1757
class TestRepositoryGetParentMap(TestRemoteRepository):
1759
def test_get_parent_map_caching(self):
1760
# get_parent_map returns from cache until unlock()
1761
# setup a reponse with two revisions
1762
r1 = u'\u0e33'.encode('utf8')
1763
r2 = u'\u0dab'.encode('utf8')
1764
lines = [' '.join([r2, r1]), r1]
1765
encoded_body = bz2.compress('\n'.join(lines))
1767
transport_path = 'quack'
1768
repo, client = self.setup_fake_client_and_repository(transport_path)
1769
client.add_success_response_with_body(encoded_body, 'ok')
1770
client.add_success_response_with_body(encoded_body, 'ok')
1772
graph = repo.get_graph()
1773
parents = graph.get_parent_map([r2])
1774
self.assertEqual({r2: (r1,)}, parents)
1775
# locking and unlocking deeper should not reset
1778
parents = graph.get_parent_map([r1])
1779
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1781
[('call_with_body_bytes_expecting_body',
1782
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1786
# now we call again, and it should use the second response.
1788
graph = repo.get_graph()
1789
parents = graph.get_parent_map([r1])
1790
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1792
[('call_with_body_bytes_expecting_body',
1793
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1795
('call_with_body_bytes_expecting_body',
1796
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1802
def test_get_parent_map_reconnects_if_unknown_method(self):
1803
transport_path = 'quack'
1804
rev_id = 'revision-id'
1805
repo, client = self.setup_fake_client_and_repository(transport_path)
1806
client.add_unknown_method_response('Repository.get_parent_map')
1807
client.add_success_response_with_body(rev_id, 'ok')
1808
self.assertFalse(client._medium._is_remote_before((1, 2)))
1809
parents = repo.get_parent_map([rev_id])
1811
[('call_with_body_bytes_expecting_body',
1812
'Repository.get_parent_map', ('quack/', 'include-missing:',
1814
('disconnect medium',),
1815
('call_expecting_body', 'Repository.get_revision_graph',
1818
# The medium is now marked as being connected to an older server
1819
self.assertTrue(client._medium._is_remote_before((1, 2)))
1820
self.assertEqual({rev_id: ('null:',)}, parents)
1822
def test_get_parent_map_fallback_parentless_node(self):
1823
"""get_parent_map falls back to get_revision_graph on old servers. The
1824
results from get_revision_graph are tweaked to match the get_parent_map
1827
Specifically, a {key: ()} result from get_revision_graph means "no
1828
parents" for that key, which in get_parent_map results should be
1829
represented as {key: ('null:',)}.
1831
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1833
rev_id = 'revision-id'
1834
transport_path = 'quack'
1835
repo, client = self.setup_fake_client_and_repository(transport_path)
1836
client.add_success_response_with_body(rev_id, 'ok')
1837
client._medium._remember_remote_is_before((1, 2))
1838
parents = repo.get_parent_map([rev_id])
1840
[('call_expecting_body', 'Repository.get_revision_graph',
1843
self.assertEqual({rev_id: ('null:',)}, parents)
1845
def test_get_parent_map_unexpected_response(self):
1846
repo, client = self.setup_fake_client_and_repository('path')
1847
client.add_success_response('something unexpected!')
1849
errors.UnexpectedSmartServerResponse,
1850
repo.get_parent_map, ['a-revision-id'])
1852
def test_get_parent_map_negative_caches_missing_keys(self):
1853
self.setup_smart_server_with_call_log()
1854
repo = self.make_repository('foo')
1855
self.assertIsInstance(repo, RemoteRepository)
1857
self.addCleanup(repo.unlock)
1858
self.reset_smart_call_log()
1859
graph = repo.get_graph()
1860
self.assertEqual({},
1861
graph.get_parent_map(['some-missing', 'other-missing']))
1862
self.assertLength(1, self.hpss_calls)
1863
# No call if we repeat this
1864
self.reset_smart_call_log()
1865
graph = repo.get_graph()
1866
self.assertEqual({},
1867
graph.get_parent_map(['some-missing', 'other-missing']))
1868
self.assertLength(0, self.hpss_calls)
1869
# Asking for more unknown keys makes a request.
1870
self.reset_smart_call_log()
1871
graph = repo.get_graph()
1872
self.assertEqual({},
1873
graph.get_parent_map(['some-missing', 'other-missing',
1875
self.assertLength(1, self.hpss_calls)
1877
def disableExtraResults(self):
1878
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1879
SmartServerRepositoryGetParentMap.no_extra_results = True
1881
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1882
self.addCleanup(reset_values)
1884
def test_null_cached_missing_and_stop_key(self):
1885
self.setup_smart_server_with_call_log()
1886
# Make a branch with a single revision.
1887
builder = self.make_branch_builder('foo')
1888
builder.start_series()
1889
builder.build_snapshot('first', None, [
1890
('add', ('', 'root-id', 'directory', ''))])
1891
builder.finish_series()
1892
branch = builder.get_branch()
1893
repo = branch.repository
1894
self.assertIsInstance(repo, RemoteRepository)
1895
# Stop the server from sending extra results.
1896
self.disableExtraResults()
1898
self.addCleanup(repo.unlock)
1899
self.reset_smart_call_log()
1900
graph = repo.get_graph()
1901
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1902
# 'first' it will be a candidate for the stop_keys of subsequent
1903
# requests, and because 'null:' was queried but not returned it will be
1904
# cached as missing.
1905
self.assertEqual({'first': ('null:',)},
1906
graph.get_parent_map(['first', 'null:']))
1907
# Now query for another key. This request will pass along a recipe of
1908
# start and stop keys describing the already cached results, and this
1909
# recipe's revision count must be correct (or else it will trigger an
1910
# error from the server).
1911
self.assertEqual({}, graph.get_parent_map(['another-key']))
1912
# This assertion guards against disableExtraResults silently failing to
1913
# work, thus invalidating the test.
1914
self.assertLength(2, self.hpss_calls)
1916
def test_get_parent_map_gets_ghosts_from_result(self):
1917
# asking for a revision should negatively cache close ghosts in its
1919
self.setup_smart_server_with_call_log()
1920
tree = self.make_branch_and_memory_tree('foo')
1923
builder = treebuilder.TreeBuilder()
1924
builder.start_tree(tree)
1926
builder.finish_tree()
1927
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1928
rev_id = tree.commit('')
1932
self.addCleanup(tree.unlock)
1933
repo = tree.branch.repository
1934
self.assertIsInstance(repo, RemoteRepository)
1936
repo.get_parent_map([rev_id])
1937
self.reset_smart_call_log()
1938
# Now asking for rev_id's ghost parent should not make calls
1939
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1940
self.assertLength(0, self.hpss_calls)
1943
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1945
def test_allows_new_revisions(self):
1946
"""get_parent_map's results can be updated by commit."""
1947
smart_server = server.SmartTCPServer_for_testing()
1948
self.start_server(smart_server)
1949
self.make_branch('branch')
1950
branch = Branch.open(smart_server.get_url() + '/branch')
1951
tree = branch.create_checkout('tree', lightweight=True)
1953
self.addCleanup(tree.unlock)
1954
graph = tree.branch.repository.get_graph()
1955
# This provides an opportunity for the missing rev-id to be cached.
1956
self.assertEqual({}, graph.get_parent_map(['rev1']))
1957
tree.commit('message', rev_id='rev1')
1958
graph = tree.branch.repository.get_graph()
1959
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1962
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1964
def test_null_revision(self):
1965
# a null revision has the predictable result {}, we should have no wire
1966
# traffic when calling it with this argument
1967
transport_path = 'empty'
1968
repo, client = self.setup_fake_client_and_repository(transport_path)
1969
client.add_success_response('notused')
1970
# actual RemoteRepository.get_revision_graph is gone, but there's an
1971
# equivalent private method for testing
1972
result = repo._get_revision_graph(NULL_REVISION)
1973
self.assertEqual([], client._calls)
1974
self.assertEqual({}, result)
1976
def test_none_revision(self):
1977
# with none we want the entire graph
1978
r1 = u'\u0e33'.encode('utf8')
1979
r2 = u'\u0dab'.encode('utf8')
1980
lines = [' '.join([r2, r1]), r1]
1981
encoded_body = '\n'.join(lines)
1983
transport_path = 'sinhala'
1984
repo, client = self.setup_fake_client_and_repository(transport_path)
1985
client.add_success_response_with_body(encoded_body, 'ok')
1986
# actual RemoteRepository.get_revision_graph is gone, but there's an
1987
# equivalent private method for testing
1988
result = repo._get_revision_graph(None)
1990
[('call_expecting_body', 'Repository.get_revision_graph',
1993
self.assertEqual({r1: (), r2: (r1, )}, result)
1995
def test_specific_revision(self):
1996
# with a specific revision we want the graph for that
1997
# with none we want the entire graph
1998
r11 = u'\u0e33'.encode('utf8')
1999
r12 = u'\xc9'.encode('utf8')
2000
r2 = u'\u0dab'.encode('utf8')
2001
lines = [' '.join([r2, r11, r12]), r11, r12]
2002
encoded_body = '\n'.join(lines)
2004
transport_path = 'sinhala'
2005
repo, client = self.setup_fake_client_and_repository(transport_path)
2006
client.add_success_response_with_body(encoded_body, 'ok')
2007
result = repo._get_revision_graph(r2)
2009
[('call_expecting_body', 'Repository.get_revision_graph',
2012
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2014
def test_no_such_revision(self):
2016
transport_path = 'sinhala'
2017
repo, client = self.setup_fake_client_and_repository(transport_path)
2018
client.add_error_response('nosuchrevision', revid)
2019
# also check that the right revision is reported in the error
2020
self.assertRaises(errors.NoSuchRevision,
2021
repo._get_revision_graph, revid)
2023
[('call_expecting_body', 'Repository.get_revision_graph',
2024
('sinhala/', revid))],
2027
def test_unexpected_error(self):
2029
transport_path = 'sinhala'
2030
repo, client = self.setup_fake_client_and_repository(transport_path)
2031
client.add_error_response('AnUnexpectedError')
2032
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2033
repo._get_revision_graph, revid)
2034
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2037
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2040
repo, client = self.setup_fake_client_and_repository('quack')
2041
client.add_expected_call(
2042
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2043
'success', ('ok', 'rev-five'))
2044
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2045
self.assertEqual((True, 'rev-five'), result)
2046
self.assertFinished(client)
2048
def test_history_incomplete(self):
2049
repo, client = self.setup_fake_client_and_repository('quack')
2050
client.add_expected_call(
2051
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2052
'success', ('history-incomplete', 10, 'rev-ten'))
2053
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2054
self.assertEqual((False, (10, 'rev-ten')), result)
2055
self.assertFinished(client)
2057
def test_history_incomplete_with_fallback(self):
2058
"""A 'history-incomplete' response causes the fallback repository to be
2059
queried too, if one is set.
2061
# Make a repo with a fallback repo, both using a FakeClient.
2062
format = remote.response_tuple_to_repo_format(
2063
('yes', 'no', 'yes', 'fake-network-name'))
2064
repo, client = self.setup_fake_client_and_repository('quack')
2065
repo._format = format
2066
fallback_repo, ignored = self.setup_fake_client_and_repository(
2068
fallback_repo._client = client
2069
repo.add_fallback_repository(fallback_repo)
2070
# First the client should ask the primary repo
2071
client.add_expected_call(
2072
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2073
'success', ('history-incomplete', 2, 'rev-two'))
2074
# Then it should ask the fallback, using revno/revid from the
2075
# history-incomplete response as the known revno/revid.
2076
client.add_expected_call(
2077
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2078
'success', ('ok', 'rev-one'))
2079
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2080
self.assertEqual((True, 'rev-one'), result)
2081
self.assertFinished(client)
2083
def test_nosuchrevision(self):
2084
# 'nosuchrevision' is returned when the known-revid is not found in the
2085
# remote repo. The client translates that response to NoSuchRevision.
2086
repo, client = self.setup_fake_client_and_repository('quack')
2087
client.add_expected_call(
2088
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2089
'error', ('nosuchrevision', 'rev-foo'))
2091
errors.NoSuchRevision,
2092
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2093
self.assertFinished(client)
2096
class TestRepositoryIsShared(TestRemoteRepository):
2098
def test_is_shared(self):
2099
# ('yes', ) for Repository.is_shared -> 'True'.
2100
transport_path = 'quack'
2101
repo, client = self.setup_fake_client_and_repository(transport_path)
2102
client.add_success_response('yes')
2103
result = repo.is_shared()
2105
[('call', 'Repository.is_shared', ('quack/',))],
2107
self.assertEqual(True, result)
2109
def test_is_not_shared(self):
2110
# ('no', ) for Repository.is_shared -> 'False'.
2111
transport_path = 'qwack'
2112
repo, client = self.setup_fake_client_and_repository(transport_path)
2113
client.add_success_response('no')
2114
result = repo.is_shared()
2116
[('call', 'Repository.is_shared', ('qwack/',))],
2118
self.assertEqual(False, result)
2121
class TestRepositoryLockWrite(TestRemoteRepository):
2123
def test_lock_write(self):
2124
transport_path = 'quack'
2125
repo, client = self.setup_fake_client_and_repository(transport_path)
2126
client.add_success_response('ok', 'a token')
2127
result = repo.lock_write()
2129
[('call', 'Repository.lock_write', ('quack/', ''))],
2131
self.assertEqual('a token', result)
2133
def test_lock_write_already_locked(self):
2134
transport_path = 'quack'
2135
repo, client = self.setup_fake_client_and_repository(transport_path)
2136
client.add_error_response('LockContention')
2137
self.assertRaises(errors.LockContention, repo.lock_write)
2139
[('call', 'Repository.lock_write', ('quack/', ''))],
2142
def test_lock_write_unlockable(self):
2143
transport_path = 'quack'
2144
repo, client = self.setup_fake_client_and_repository(transport_path)
2145
client.add_error_response('UnlockableTransport')
2146
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2148
[('call', 'Repository.lock_write', ('quack/', ''))],
2152
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2154
def test_backwards_compat(self):
2155
self.setup_smart_server_with_call_log()
2156
repo = self.make_repository('.')
2157
self.reset_smart_call_log()
2158
verb = 'Repository.set_make_working_trees'
2159
self.disable_verb(verb)
2160
repo.set_make_working_trees(True)
2161
call_count = len([call for call in self.hpss_calls if
2162
call.call.method == verb])
2163
self.assertEqual(1, call_count)
2165
def test_current(self):
2166
transport_path = 'quack'
2167
repo, client = self.setup_fake_client_and_repository(transport_path)
2168
client.add_expected_call(
2169
'Repository.set_make_working_trees', ('quack/', 'True'),
2171
client.add_expected_call(
2172
'Repository.set_make_working_trees', ('quack/', 'False'),
2174
repo.set_make_working_trees(True)
2175
repo.set_make_working_trees(False)
2178
class TestRepositoryUnlock(TestRemoteRepository):
2180
def test_unlock(self):
2181
transport_path = 'quack'
2182
repo, client = self.setup_fake_client_and_repository(transport_path)
2183
client.add_success_response('ok', 'a token')
2184
client.add_success_response('ok')
2188
[('call', 'Repository.lock_write', ('quack/', '')),
2189
('call', 'Repository.unlock', ('quack/', 'a token'))],
2192
def test_unlock_wrong_token(self):
2193
# If somehow the token is wrong, unlock will raise TokenMismatch.
2194
transport_path = 'quack'
2195
repo, client = self.setup_fake_client_and_repository(transport_path)
2196
client.add_success_response('ok', 'a token')
2197
client.add_error_response('TokenMismatch')
2199
self.assertRaises(errors.TokenMismatch, repo.unlock)
2202
class TestRepositoryHasRevision(TestRemoteRepository):
2204
def test_none(self):
2205
# repo.has_revision(None) should not cause any traffic.
2206
transport_path = 'quack'
2207
repo, client = self.setup_fake_client_and_repository(transport_path)
2209
# The null revision is always there, so has_revision(None) == True.
2210
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2212
# The remote repo shouldn't be accessed.
2213
self.assertEqual([], client._calls)
2216
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2217
"""Base class for Repository.insert_stream and .insert_stream_1.19
2221
def checkInsertEmptyStream(self, repo, client):
2222
"""Insert an empty stream, checking the result.
2224
This checks that there are no resume_tokens or missing_keys, and that
2225
the client is finished.
2227
sink = repo._get_sink()
2228
fmt = repository.RepositoryFormat.get_default_format()
2229
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2230
self.assertEqual([], resume_tokens)
2231
self.assertEqual(set(), missing_keys)
2232
self.assertFinished(client)
2235
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2236
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2239
This test case is very similar to TestRepositoryInsertStream_1_19.
2243
TestRemoteRepository.setUp(self)
2244
self.disable_verb('Repository.insert_stream_1.19')
2246
def test_unlocked_repo(self):
2247
transport_path = 'quack'
2248
repo, client = self.setup_fake_client_and_repository(transport_path)
2249
client.add_expected_call(
2250
'Repository.insert_stream_1.19', ('quack/', ''),
2251
'unknown', ('Repository.insert_stream_1.19',))
2252
client.add_expected_call(
2253
'Repository.insert_stream', ('quack/', ''),
2255
client.add_expected_call(
2256
'Repository.insert_stream', ('quack/', ''),
2258
self.checkInsertEmptyStream(repo, client)
2260
def test_locked_repo_with_no_lock_token(self):
2261
transport_path = 'quack'
2262
repo, client = self.setup_fake_client_and_repository(transport_path)
2263
client.add_expected_call(
2264
'Repository.lock_write', ('quack/', ''),
2265
'success', ('ok', ''))
2266
client.add_expected_call(
2267
'Repository.insert_stream_1.19', ('quack/', ''),
2268
'unknown', ('Repository.insert_stream_1.19',))
2269
client.add_expected_call(
2270
'Repository.insert_stream', ('quack/', ''),
2272
client.add_expected_call(
2273
'Repository.insert_stream', ('quack/', ''),
2276
self.checkInsertEmptyStream(repo, client)
2278
def test_locked_repo_with_lock_token(self):
2279
transport_path = 'quack'
2280
repo, client = self.setup_fake_client_and_repository(transport_path)
2281
client.add_expected_call(
2282
'Repository.lock_write', ('quack/', ''),
2283
'success', ('ok', 'a token'))
2284
client.add_expected_call(
2285
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2286
'unknown', ('Repository.insert_stream_1.19',))
2287
client.add_expected_call(
2288
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2290
client.add_expected_call(
2291
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2294
self.checkInsertEmptyStream(repo, client)
2296
def test_stream_with_inventory_deltas(self):
2297
"""'inventory-deltas' substreams cannot be sent to the
2298
Repository.insert_stream verb, because not all servers that implement
2299
that verb will accept them. So when one is encountered the RemoteSink
2300
immediately stops using that verb and falls back to VFS insert_stream.
2302
transport_path = 'quack'
2303
repo, client = self.setup_fake_client_and_repository(transport_path)
2304
client.add_expected_call(
2305
'Repository.insert_stream_1.19', ('quack/', ''),
2306
'unknown', ('Repository.insert_stream_1.19',))
2307
client.add_expected_call(
2308
'Repository.insert_stream', ('quack/', ''),
2310
client.add_expected_call(
2311
'Repository.insert_stream', ('quack/', ''),
2313
# Create a fake real repository for insert_stream to fall back on, so
2314
# that we can directly see the records the RemoteSink passes to the
2319
def insert_stream(self, stream, src_format, resume_tokens):
2320
for substream_kind, substream in stream:
2321
self.records.append(
2322
(substream_kind, [record.key for record in substream]))
2323
return ['fake tokens'], ['fake missing keys']
2324
fake_real_sink = FakeRealSink()
2325
class FakeRealRepository:
2326
def _get_sink(self):
2327
return fake_real_sink
2328
repo._real_repository = FakeRealRepository()
2329
sink = repo._get_sink()
2330
fmt = repository.RepositoryFormat.get_default_format()
2331
stream = self.make_stream_with_inv_deltas(fmt)
2332
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2333
# Every record from the first inventory delta should have been sent to
2335
expected_records = [
2336
('inventory-deltas', [('rev2',), ('rev3',)]),
2337
('texts', [('some-rev', 'some-file')])]
2338
self.assertEqual(expected_records, fake_real_sink.records)
2339
# The return values from the real sink's insert_stream are propagated
2340
# back to the original caller.
2341
self.assertEqual(['fake tokens'], resume_tokens)
2342
self.assertEqual(['fake missing keys'], missing_keys)
2343
self.assertFinished(client)
2345
def make_stream_with_inv_deltas(self, fmt):
2346
"""Make a simple stream with an inventory delta followed by more
2347
records and more substreams to test that all records and substreams
2348
from that point on are used.
2350
This sends, in order:
2351
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2353
* texts substream: (some-rev, some-file)
2355
# Define a stream using generators so that it isn't rewindable.
2356
inv = inventory.Inventory(revision_id='rev1')
2357
inv.root.revision = 'rev1'
2358
def stream_with_inv_delta():
2359
yield ('inventories', inventories_substream())
2360
yield ('inventory-deltas', inventory_delta_substream())
2362
versionedfile.FulltextContentFactory(
2363
('some-rev', 'some-file'), (), None, 'content')])
2364
def inventories_substream():
2365
# An empty inventory fulltext. This will be streamed normally.
2366
text = fmt._serializer.write_inventory_to_string(inv)
2367
yield versionedfile.FulltextContentFactory(
2368
('rev1',), (), None, text)
2369
def inventory_delta_substream():
2370
# An inventory delta. This can't be streamed via this verb, so it
2371
# will trigger a fallback to VFS insert_stream.
2372
entry = inv.make_entry(
2373
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2374
entry.revision = 'ghost'
2375
delta = [(None, 'newdir', 'newdir-id', entry)]
2376
serializer = inventory_delta.InventoryDeltaSerializer(
2377
versioned_root=True, tree_references=False)
2378
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2379
yield versionedfile.ChunkedContentFactory(
2380
('rev2',), (('rev1',)), None, lines)
2382
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2383
yield versionedfile.ChunkedContentFactory(
2384
('rev3',), (('rev1',)), None, lines)
2385
return stream_with_inv_delta()
2388
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2390
def test_unlocked_repo(self):
2391
transport_path = 'quack'
2392
repo, client = self.setup_fake_client_and_repository(transport_path)
2393
client.add_expected_call(
2394
'Repository.insert_stream_1.19', ('quack/', ''),
2396
client.add_expected_call(
2397
'Repository.insert_stream_1.19', ('quack/', ''),
2399
self.checkInsertEmptyStream(repo, client)
2401
def test_locked_repo_with_no_lock_token(self):
2402
transport_path = 'quack'
2403
repo, client = self.setup_fake_client_and_repository(transport_path)
2404
client.add_expected_call(
2405
'Repository.lock_write', ('quack/', ''),
2406
'success', ('ok', ''))
2407
client.add_expected_call(
2408
'Repository.insert_stream_1.19', ('quack/', ''),
2410
client.add_expected_call(
2411
'Repository.insert_stream_1.19', ('quack/', ''),
2414
self.checkInsertEmptyStream(repo, client)
2416
def test_locked_repo_with_lock_token(self):
2417
transport_path = 'quack'
2418
repo, client = self.setup_fake_client_and_repository(transport_path)
2419
client.add_expected_call(
2420
'Repository.lock_write', ('quack/', ''),
2421
'success', ('ok', 'a token'))
2422
client.add_expected_call(
2423
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2425
client.add_expected_call(
2426
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2429
self.checkInsertEmptyStream(repo, client)
2432
class TestRepositoryTarball(TestRemoteRepository):
2434
# This is a canned tarball reponse we can validate against
2436
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2437
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2438
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2439
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2440
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2441
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2442
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2443
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2444
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2445
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2446
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2447
'nWQ7QH/F3JFOFCQ0aSPfA='
2450
def test_repository_tarball(self):
2451
# Test that Repository.tarball generates the right operations
2452
transport_path = 'repo'
2453
expected_calls = [('call_expecting_body', 'Repository.tarball',
2454
('repo/', 'bz2',),),
2456
repo, client = self.setup_fake_client_and_repository(transport_path)
2457
client.add_success_response_with_body(self.tarball_content, 'ok')
2458
# Now actually ask for the tarball
2459
tarball_file = repo._get_tarball('bz2')
2461
self.assertEqual(expected_calls, client._calls)
2462
self.assertEqual(self.tarball_content, tarball_file.read())
2464
tarball_file.close()
2467
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2468
"""RemoteRepository.copy_content_into optimizations"""
2470
def test_copy_content_remote_to_local(self):
2471
self.transport_server = server.SmartTCPServer_for_testing
2472
src_repo = self.make_repository('repo1')
2473
src_repo = repository.Repository.open(self.get_url('repo1'))
2474
# At the moment the tarball-based copy_content_into can't write back
2475
# into a smart server. It would be good if it could upload the
2476
# tarball; once that works we'd have to create repositories of
2477
# different formats. -- mbp 20070410
2478
dest_url = self.get_vfs_only_url('repo2')
2479
dest_bzrdir = BzrDir.create(dest_url)
2480
dest_repo = dest_bzrdir.create_repository()
2481
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2482
self.assertTrue(isinstance(src_repo, RemoteRepository))
2483
src_repo.copy_content_into(dest_repo)
2486
class _StubRealPackRepository(object):
2488
def __init__(self, calls):
2490
self._pack_collection = _StubPackCollection(calls)
2492
def is_in_write_group(self):
2495
def refresh_data(self):
2496
self.calls.append(('pack collection reload_pack_names',))
2499
class _StubPackCollection(object):
2501
def __init__(self, calls):
2505
self.calls.append(('pack collection autopack',))
2508
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2509
"""Tests for RemoteRepository.autopack implementation."""
2512
"""When the server returns 'ok' and there's no _real_repository, then
2513
nothing else happens: the autopack method is done.
2515
transport_path = 'quack'
2516
repo, client = self.setup_fake_client_and_repository(transport_path)
2517
client.add_expected_call(
2518
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2520
self.assertFinished(client)
2522
def test_ok_with_real_repo(self):
2523
"""When the server returns 'ok' and there is a _real_repository, then
2524
the _real_repository's reload_pack_name's method will be called.
2526
transport_path = 'quack'
2527
repo, client = self.setup_fake_client_and_repository(transport_path)
2528
client.add_expected_call(
2529
'PackRepository.autopack', ('quack/',),
2531
repo._real_repository = _StubRealPackRepository(client._calls)
2534
[('call', 'PackRepository.autopack', ('quack/',)),
2535
('pack collection reload_pack_names',)],
2538
def test_backwards_compatibility(self):
2539
"""If the server does not recognise the PackRepository.autopack verb,
2540
fallback to the real_repository's implementation.
2542
transport_path = 'quack'
2543
repo, client = self.setup_fake_client_and_repository(transport_path)
2544
client.add_unknown_method_response('PackRepository.autopack')
2545
def stub_ensure_real():
2546
client._calls.append(('_ensure_real',))
2547
repo._real_repository = _StubRealPackRepository(client._calls)
2548
repo._ensure_real = stub_ensure_real
2551
[('call', 'PackRepository.autopack', ('quack/',)),
2553
('pack collection autopack',)],
2557
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2558
"""Base class for unit tests for bzrlib.remote._translate_error."""
2560
def translateTuple(self, error_tuple, **context):
2561
"""Call _translate_error with an ErrorFromSmartServer built from the
2564
:param error_tuple: A tuple of a smart server response, as would be
2565
passed to an ErrorFromSmartServer.
2566
:kwargs context: context items to call _translate_error with.
2568
:returns: The error raised by _translate_error.
2570
# Raise the ErrorFromSmartServer before passing it as an argument,
2571
# because _translate_error may need to re-raise it with a bare 'raise'
2573
server_error = errors.ErrorFromSmartServer(error_tuple)
2574
translated_error = self.translateErrorFromSmartServer(
2575
server_error, **context)
2576
return translated_error
2578
def translateErrorFromSmartServer(self, error_object, **context):
2579
"""Like translateTuple, but takes an already constructed
2580
ErrorFromSmartServer rather than a tuple.
2584
except errors.ErrorFromSmartServer, server_error:
2585
translated_error = self.assertRaises(
2586
errors.BzrError, remote._translate_error, server_error,
2588
return translated_error
2591
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2592
"""Unit tests for bzrlib.remote._translate_error.
2594
Given an ErrorFromSmartServer (which has an error tuple from a smart
2595
server) and some context, _translate_error raises more specific errors from
2598
This test case covers the cases where _translate_error succeeds in
2599
translating an ErrorFromSmartServer to something better. See
2600
TestErrorTranslationRobustness for other cases.
2603
def test_NoSuchRevision(self):
2604
branch = self.make_branch('')
2606
translated_error = self.translateTuple(
2607
('NoSuchRevision', revid), branch=branch)
2608
expected_error = errors.NoSuchRevision(branch, revid)
2609
self.assertEqual(expected_error, translated_error)
2611
def test_nosuchrevision(self):
2612
repository = self.make_repository('')
2614
translated_error = self.translateTuple(
2615
('nosuchrevision', revid), repository=repository)
2616
expected_error = errors.NoSuchRevision(repository, revid)
2617
self.assertEqual(expected_error, translated_error)
2619
def test_nobranch(self):
2620
bzrdir = self.make_bzrdir('')
2621
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2622
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2623
self.assertEqual(expected_error, translated_error)
2625
def test_LockContention(self):
2626
translated_error = self.translateTuple(('LockContention',))
2627
expected_error = errors.LockContention('(remote lock)')
2628
self.assertEqual(expected_error, translated_error)
2630
def test_UnlockableTransport(self):
2631
bzrdir = self.make_bzrdir('')
2632
translated_error = self.translateTuple(
2633
('UnlockableTransport',), bzrdir=bzrdir)
2634
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2635
self.assertEqual(expected_error, translated_error)
2637
def test_LockFailed(self):
2638
lock = 'str() of a server lock'
2639
why = 'str() of why'
2640
translated_error = self.translateTuple(('LockFailed', lock, why))
2641
expected_error = errors.LockFailed(lock, why)
2642
self.assertEqual(expected_error, translated_error)
2644
def test_TokenMismatch(self):
2645
token = 'a lock token'
2646
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2647
expected_error = errors.TokenMismatch(token, '(remote token)')
2648
self.assertEqual(expected_error, translated_error)
2650
def test_Diverged(self):
2651
branch = self.make_branch('a')
2652
other_branch = self.make_branch('b')
2653
translated_error = self.translateTuple(
2654
('Diverged',), branch=branch, other_branch=other_branch)
2655
expected_error = errors.DivergedBranches(branch, other_branch)
2656
self.assertEqual(expected_error, translated_error)
2658
def test_ReadError_no_args(self):
2660
translated_error = self.translateTuple(('ReadError',), path=path)
2661
expected_error = errors.ReadError(path)
2662
self.assertEqual(expected_error, translated_error)
2664
def test_ReadError(self):
2666
translated_error = self.translateTuple(('ReadError', path))
2667
expected_error = errors.ReadError(path)
2668
self.assertEqual(expected_error, translated_error)
2670
def test_IncompatibleRepositories(self):
2671
translated_error = self.translateTuple(('IncompatibleRepositories',
2672
"repo1", "repo2", "details here"))
2673
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
2675
self.assertEqual(expected_error, translated_error)
2677
def test_PermissionDenied_no_args(self):
2679
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2680
expected_error = errors.PermissionDenied(path)
2681
self.assertEqual(expected_error, translated_error)
2683
def test_PermissionDenied_one_arg(self):
2685
translated_error = self.translateTuple(('PermissionDenied', path))
2686
expected_error = errors.PermissionDenied(path)
2687
self.assertEqual(expected_error, translated_error)
2689
def test_PermissionDenied_one_arg_and_context(self):
2690
"""Given a choice between a path from the local context and a path on
2691
the wire, _translate_error prefers the path from the local context.
2693
local_path = 'local path'
2694
remote_path = 'remote path'
2695
translated_error = self.translateTuple(
2696
('PermissionDenied', remote_path), path=local_path)
2697
expected_error = errors.PermissionDenied(local_path)
2698
self.assertEqual(expected_error, translated_error)
2700
def test_PermissionDenied_two_args(self):
2702
extra = 'a string with extra info'
2703
translated_error = self.translateTuple(
2704
('PermissionDenied', path, extra))
2705
expected_error = errors.PermissionDenied(path, extra)
2706
self.assertEqual(expected_error, translated_error)
2709
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2710
"""Unit tests for bzrlib.remote._translate_error's robustness.
2712
TestErrorTranslationSuccess is for cases where _translate_error can
2713
translate successfully. This class about how _translate_err behaves when
2714
it fails to translate: it re-raises the original error.
2717
def test_unrecognised_server_error(self):
2718
"""If the error code from the server is not recognised, the original
2719
ErrorFromSmartServer is propagated unmodified.
2721
error_tuple = ('An unknown error tuple',)
2722
server_error = errors.ErrorFromSmartServer(error_tuple)
2723
translated_error = self.translateErrorFromSmartServer(server_error)
2724
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2725
self.assertEqual(expected_error, translated_error)
2727
def test_context_missing_a_key(self):
2728
"""In case of a bug in the client, or perhaps an unexpected response
2729
from a server, _translate_error returns the original error tuple from
2730
the server and mutters a warning.
2732
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2733
# in the context dict. So let's give it an empty context dict instead
2734
# to exercise its error recovery.
2736
error_tuple = ('NoSuchRevision', 'revid')
2737
server_error = errors.ErrorFromSmartServer(error_tuple)
2738
translated_error = self.translateErrorFromSmartServer(server_error)
2739
self.assertEqual(server_error, translated_error)
2740
# In addition to re-raising ErrorFromSmartServer, some debug info has
2741
# been muttered to the log file for developer to look at.
2742
self.assertContainsRe(
2743
self._get_log(keep_log_file=True),
2744
"Missing key 'branch' in context")
2746
def test_path_missing(self):
2747
"""Some translations (PermissionDenied, ReadError) can determine the
2748
'path' variable from either the wire or the local context. If neither
2749
has it, then an error is raised.
2751
error_tuple = ('ReadError',)
2752
server_error = errors.ErrorFromSmartServer(error_tuple)
2753
translated_error = self.translateErrorFromSmartServer(server_error)
2754
self.assertEqual(server_error, translated_error)
2755
# In addition to re-raising ErrorFromSmartServer, some debug info has
2756
# been muttered to the log file for developer to look at.
2757
self.assertContainsRe(
2758
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2761
class TestStacking(tests.TestCaseWithTransport):
2762
"""Tests for operations on stacked remote repositories.
2764
The underlying format type must support stacking.
2767
def test_access_stacked_remote(self):
2768
# based on <http://launchpad.net/bugs/261315>
2769
# make a branch stacked on another repository containing an empty
2770
# revision, then open it over hpss - we should be able to see that
2772
base_transport = self.get_transport()
2773
base_builder = self.make_branch_builder('base', format='1.9')
2774
base_builder.start_series()
2775
base_revid = base_builder.build_snapshot('rev-id', None,
2776
[('add', ('', None, 'directory', None))],
2778
base_builder.finish_series()
2779
stacked_branch = self.make_branch('stacked', format='1.9')
2780
stacked_branch.set_stacked_on_url('../base')
2781
# start a server looking at this
2782
smart_server = server.SmartTCPServer_for_testing()
2783
self.start_server(smart_server)
2784
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2785
# can get its branch and repository
2786
remote_branch = remote_bzrdir.open_branch()
2787
remote_repo = remote_branch.repository
2788
remote_repo.lock_read()
2790
# it should have an appropriate fallback repository, which should also
2791
# be a RemoteRepository
2792
self.assertLength(1, remote_repo._fallback_repositories)
2793
self.assertIsInstance(remote_repo._fallback_repositories[0],
2795
# and it has the revision committed to the underlying repository;
2796
# these have varying implementations so we try several of them
2797
self.assertTrue(remote_repo.has_revisions([base_revid]))
2798
self.assertTrue(remote_repo.has_revision(base_revid))
2799
self.assertEqual(remote_repo.get_revision(base_revid).message,
2802
remote_repo.unlock()
2804
def prepare_stacked_remote_branch(self):
2805
"""Get stacked_upon and stacked branches with content in each."""
2806
self.setup_smart_server_with_call_log()
2807
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2808
tree1.commit('rev1', rev_id='rev1')
2809
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2810
).open_workingtree()
2811
local_tree = tree2.branch.create_checkout('local')
2812
local_tree.commit('local changes make me feel good.')
2813
branch2 = Branch.open(self.get_url('tree2'))
2815
self.addCleanup(branch2.unlock)
2816
return tree1.branch, branch2
2818
def test_stacked_get_parent_map(self):
2819
# the public implementation of get_parent_map obeys stacking
2820
_, branch = self.prepare_stacked_remote_branch()
2821
repo = branch.repository
2822
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2824
def test_unstacked_get_parent_map(self):
2825
# _unstacked_provider.get_parent_map ignores stacking
2826
_, branch = self.prepare_stacked_remote_branch()
2827
provider = branch.repository._unstacked_provider
2828
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2830
def fetch_stream_to_rev_order(self, stream):
2832
for kind, substream in stream:
2833
if not kind == 'revisions':
2836
for content in substream:
2837
result.append(content.key[-1])
2840
def get_ordered_revs(self, format, order, branch_factory=None):
2841
"""Get a list of the revisions in a stream to format format.
2843
:param format: The format of the target.
2844
:param order: the order that target should have requested.
2845
:param branch_factory: A callable to create a trunk and stacked branch
2846
to fetch from. If none, self.prepare_stacked_remote_branch is used.
2847
:result: The revision ids in the stream, in the order seen,
2848
the topological order of revisions in the source.
2850
unordered_format = bzrdir.format_registry.get(format)()
2851
target_repository_format = unordered_format.repository_format
2853
self.assertEqual(order, target_repository_format._fetch_order)
2854
if branch_factory is None:
2855
branch_factory = self.prepare_stacked_remote_branch
2856
_, stacked = branch_factory()
2857
source = stacked.repository._get_source(target_repository_format)
2858
tip = stacked.last_revision()
2859
revs = stacked.repository.get_ancestry(tip)
2860
search = graph.PendingAncestryResult([tip], stacked.repository)
2861
self.reset_smart_call_log()
2862
stream = source.get_stream(search)
2865
# We trust that if a revision is in the stream the rest of the new
2866
# content for it is too, as per our main fetch tests; here we are
2867
# checking that the revisions are actually included at all, and their
2869
return self.fetch_stream_to_rev_order(stream), revs
2871
def test_stacked_get_stream_unordered(self):
2872
# Repository._get_source.get_stream() from a stacked repository with
2873
# unordered yields the full data from both stacked and stacked upon
2875
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2876
self.assertEqual(set(expected_revs), set(rev_ord))
2877
# Getting unordered results should have made a streaming data request
2878
# from the server, then one from the backing branch.
2879
self.assertLength(2, self.hpss_calls)
2881
def test_stacked_on_stacked_get_stream_unordered(self):
2882
# Repository._get_source.get_stream() from a stacked repository which
2883
# is itself stacked yields the full data from all three sources.
2884
def make_stacked_stacked():
2885
_, stacked = self.prepare_stacked_remote_branch()
2886
tree = stacked.bzrdir.sprout('tree3', stacked=True
2887
).open_workingtree()
2888
local_tree = tree.branch.create_checkout('local-tree3')
2889
local_tree.commit('more local changes are better')
2890
branch = Branch.open(self.get_url('tree3'))
2893
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
2894
branch_factory=make_stacked_stacked)
2895
self.assertEqual(set(expected_revs), set(rev_ord))
2896
# Getting unordered results should have made a streaming data request
2897
# from the server, and one from each backing repo
2898
self.assertLength(3, self.hpss_calls)
2900
def test_stacked_get_stream_topological(self):
2901
# Repository._get_source.get_stream() from a stacked repository with
2902
# topological sorting yields the full data from both stacked and
2903
# stacked upon sources in topological order.
2904
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2905
self.assertEqual(expected_revs, rev_ord)
2906
# Getting topological sort requires VFS calls still - one of which is
2907
# pushing up from the bound branch.
2908
self.assertLength(13, self.hpss_calls)
2910
def test_stacked_get_stream_groupcompress(self):
2911
# Repository._get_source.get_stream() from a stacked repository with
2912
# groupcompress sorting yields the full data from both stacked and
2913
# stacked upon sources in groupcompress order.
2914
raise tests.TestSkipped('No groupcompress ordered format available')
2915
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2916
self.assertEqual(expected_revs, reversed(rev_ord))
2917
# Getting unordered results should have made a streaming data request
2918
# from the backing branch, and one from the stacked on branch.
2919
self.assertLength(2, self.hpss_calls)
2921
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
2922
# When pulling some fixed amount of content that is more than the
2923
# source has (because some is coming from a fallback branch, no error
2924
# should be received. This was reported as bug 360791.
2925
# Need three branches: a trunk, a stacked branch, and a preexisting
2926
# branch pulling content from stacked and trunk.
2927
self.setup_smart_server_with_call_log()
2928
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
2929
r1 = trunk.commit('start')
2930
stacked_branch = trunk.branch.create_clone_on_transport(
2931
self.get_transport('stacked'), stacked_on=trunk.branch.base)
2932
local = self.make_branch('local', format='1.9-rich-root')
2933
local.repository.fetch(stacked_branch.repository,
2934
stacked_branch.last_revision())
2937
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2940
super(TestRemoteBranchEffort, self).setUp()
2941
# Create a smart server that publishes whatever the backing VFS server
2943
self.smart_server = server.SmartTCPServer_for_testing()
2944
self.start_server(self.smart_server, self.get_server())
2945
# Log all HPSS calls into self.hpss_calls.
2946
_SmartClient.hooks.install_named_hook(
2947
'call', self.capture_hpss_call, None)
2948
self.hpss_calls = []
2950
def capture_hpss_call(self, params):
2951
self.hpss_calls.append(params.method)
2953
def test_copy_content_into_avoids_revision_history(self):
2954
local = self.make_branch('local')
2955
remote_backing_tree = self.make_branch_and_tree('remote')
2956
remote_backing_tree.commit("Commit.")
2957
remote_branch_url = self.smart_server.get_url() + 'remote'
2958
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2959
local.repository.fetch(remote_branch.repository)
2960
self.hpss_calls = []
2961
remote_branch.copy_content_into(local)
2962
self.assertFalse('Branch.revision_history' in self.hpss_calls)