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 pack repository format 1 (needs bzr 0.92)\n', 'False'),
665
'success', ('ok', 'no', 'no', 'no', network_name))
666
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
668
repo = a_bzrdir.create_repository()
669
# We should have got a remote repository
670
self.assertIsInstance(repo, remote.RemoteRepository)
671
# its format should have the settings from the response
672
format = repo._format
673
self.assertFalse(format.rich_root_data)
674
self.assertFalse(format.supports_tree_reference)
675
self.assertFalse(format.supports_external_lookups)
676
self.assertEqual(network_name, format.network_name())
679
class TestBzrDirOpenRepository(TestRemote):
681
def test_backwards_compat_1_2_3(self):
682
# fallback all the way to the first version.
683
reference_format = self.get_repo_format()
684
network_name = reference_format.network_name()
685
client = FakeClient('bzr://example.com/')
686
client.add_unknown_method_response('BzrDir.find_repositoryV3')
687
client.add_unknown_method_response('BzrDir.find_repositoryV2')
688
client.add_success_response('ok', '', 'no', 'no')
689
# A real repository instance will be created to determine the network
691
client.add_success_response_with_body(
692
"Bazaar-NG meta directory, format 1\n", 'ok')
693
client.add_success_response_with_body(
694
reference_format.get_format_string(), 'ok')
695
# PackRepository wants to do a stat
696
client.add_success_response('stat', '0', '65535')
697
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
699
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
701
repo = bzrdir.open_repository()
703
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
704
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
705
('call', 'BzrDir.find_repository', ('quack/',)),
706
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
707
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
708
('call', 'stat', ('/quack/.bzr/repository',)),
711
self.assertEqual(network_name, repo._format.network_name())
713
def test_backwards_compat_2(self):
714
# fallback to find_repositoryV2
715
reference_format = self.get_repo_format()
716
network_name = reference_format.network_name()
717
client = FakeClient('bzr://example.com/')
718
client.add_unknown_method_response('BzrDir.find_repositoryV3')
719
client.add_success_response('ok', '', 'no', 'no', 'no')
720
# A real repository instance will be created to determine the network
722
client.add_success_response_with_body(
723
"Bazaar-NG meta directory, format 1\n", 'ok')
724
client.add_success_response_with_body(
725
reference_format.get_format_string(), 'ok')
726
# PackRepository wants to do a stat
727
client.add_success_response('stat', '0', '65535')
728
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
730
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
732
repo = bzrdir.open_repository()
734
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
735
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
736
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
737
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
738
('call', 'stat', ('/quack/.bzr/repository',)),
741
self.assertEqual(network_name, repo._format.network_name())
743
def test_current_server(self):
744
reference_format = self.get_repo_format()
745
network_name = reference_format.network_name()
746
transport = MemoryTransport()
747
transport.mkdir('quack')
748
transport = transport.clone('quack')
749
client = FakeClient(transport.base)
750
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
751
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
753
repo = bzrdir.open_repository()
755
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
757
self.assertEqual(network_name, repo._format.network_name())
760
class TestBzrDirFormatInitializeEx(TestRemote):
762
def test_success(self):
763
"""Simple test for typical successful call."""
764
fmt = bzrdir.RemoteBzrDirFormat()
765
default_format_name = BzrDirFormat.get_default_format().network_name()
766
transport = self.get_transport()
767
client = FakeClient(transport.base)
768
client.add_expected_call(
769
'BzrDirFormat.initialize_ex_1.16',
770
(default_format_name, 'path', 'False', 'False', 'False', '',
771
'', '', '', 'False'),
773
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
774
'bzrdir fmt', 'False', '', '', 'repo lock token'))
775
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
776
# it's currently hard to test that without supplying a real remote
777
# transport connected to a real server.
778
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
779
transport, False, False, False, None, None, None, None, False)
780
self.assertFinished(client)
782
def test_error(self):
783
"""Error responses are translated, e.g. 'PermissionDenied' raises the
784
corresponding error from the client.
786
fmt = bzrdir.RemoteBzrDirFormat()
787
default_format_name = BzrDirFormat.get_default_format().network_name()
788
transport = self.get_transport()
789
client = FakeClient(transport.base)
790
client.add_expected_call(
791
'BzrDirFormat.initialize_ex_1.16',
792
(default_format_name, 'path', 'False', 'False', 'False', '',
793
'', '', '', 'False'),
795
('PermissionDenied', 'path', 'extra info'))
796
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
797
# it's currently hard to test that without supplying a real remote
798
# transport connected to a real server.
799
err = self.assertRaises(errors.PermissionDenied,
800
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
801
False, False, False, None, None, None, None, False)
802
self.assertEqual('path', err.path)
803
self.assertEqual(': extra info', err.extra)
804
self.assertFinished(client)
806
def test_error_from_real_server(self):
807
"""Integration test for error translation."""
808
transport = self.make_smart_server('foo')
809
transport = transport.clone('no-such-path')
810
fmt = bzrdir.RemoteBzrDirFormat()
811
err = self.assertRaises(errors.NoSuchFile,
812
fmt.initialize_on_transport_ex, transport, create_prefix=False)
815
class OldSmartClient(object):
816
"""A fake smart client for test_old_version that just returns a version one
817
response to the 'hello' (query version) command.
820
def get_request(self):
821
input_file = StringIO('ok\x011\n')
822
output_file = StringIO()
823
client_medium = medium.SmartSimplePipesClientMedium(
824
input_file, output_file)
825
return medium.SmartClientStreamMediumRequest(client_medium)
827
def protocol_version(self):
831
class OldServerTransport(object):
832
"""A fake transport for test_old_server that reports it's smart server
833
protocol version as version one.
839
def get_smart_client(self):
840
return OldSmartClient()
843
class RemoteBzrDirTestCase(TestRemote):
845
def make_remote_bzrdir(self, transport, client):
846
"""Make a RemotebzrDir using 'client' as the _client."""
847
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
851
class RemoteBranchTestCase(RemoteBzrDirTestCase):
853
def make_remote_branch(self, transport, client):
854
"""Make a RemoteBranch using 'client' as its _SmartClient.
856
A RemoteBzrDir and RemoteRepository will also be created to fill out
857
the RemoteBranch, albeit with stub values for some of their attributes.
859
# we do not want bzrdir to make any remote calls, so use False as its
860
# _client. If it tries to make a remote call, this will fail
862
bzrdir = self.make_remote_bzrdir(transport, False)
863
repo = RemoteRepository(bzrdir, None, _client=client)
864
branch_format = self.get_branch_format()
865
format = RemoteBranchFormat(network_name=branch_format.network_name())
866
return RemoteBranch(bzrdir, repo, _client=client, format=format)
869
class TestBranchGetParent(RemoteBranchTestCase):
871
def test_no_parent(self):
872
# in an empty branch we decode the response properly
873
transport = MemoryTransport()
874
client = FakeClient(transport.base)
875
client.add_expected_call(
876
'Branch.get_stacked_on_url', ('quack/',),
877
'error', ('NotStacked',))
878
client.add_expected_call(
879
'Branch.get_parent', ('quack/',),
881
transport.mkdir('quack')
882
transport = transport.clone('quack')
883
branch = self.make_remote_branch(transport, client)
884
result = branch.get_parent()
885
self.assertFinished(client)
886
self.assertEqual(None, result)
888
def test_parent_relative(self):
889
transport = MemoryTransport()
890
client = FakeClient(transport.base)
891
client.add_expected_call(
892
'Branch.get_stacked_on_url', ('kwaak/',),
893
'error', ('NotStacked',))
894
client.add_expected_call(
895
'Branch.get_parent', ('kwaak/',),
896
'success', ('../foo/',))
897
transport.mkdir('kwaak')
898
transport = transport.clone('kwaak')
899
branch = self.make_remote_branch(transport, client)
900
result = branch.get_parent()
901
self.assertEqual(transport.clone('../foo').base, result)
903
def test_parent_absolute(self):
904
transport = MemoryTransport()
905
client = FakeClient(transport.base)
906
client.add_expected_call(
907
'Branch.get_stacked_on_url', ('kwaak/',),
908
'error', ('NotStacked',))
909
client.add_expected_call(
910
'Branch.get_parent', ('kwaak/',),
911
'success', ('http://foo/',))
912
transport.mkdir('kwaak')
913
transport = transport.clone('kwaak')
914
branch = self.make_remote_branch(transport, client)
915
result = branch.get_parent()
916
self.assertEqual('http://foo/', result)
917
self.assertFinished(client)
920
class TestBranchSetParentLocation(RemoteBranchTestCase):
922
def test_no_parent(self):
923
# We call the verb when setting parent to None
924
transport = MemoryTransport()
925
client = FakeClient(transport.base)
926
client.add_expected_call(
927
'Branch.get_stacked_on_url', ('quack/',),
928
'error', ('NotStacked',))
929
client.add_expected_call(
930
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
932
transport.mkdir('quack')
933
transport = transport.clone('quack')
934
branch = self.make_remote_branch(transport, client)
935
branch._lock_token = 'b'
936
branch._repo_lock_token = 'r'
937
branch._set_parent_location(None)
938
self.assertFinished(client)
940
def test_parent(self):
941
transport = MemoryTransport()
942
client = FakeClient(transport.base)
943
client.add_expected_call(
944
'Branch.get_stacked_on_url', ('kwaak/',),
945
'error', ('NotStacked',))
946
client.add_expected_call(
947
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
949
transport.mkdir('kwaak')
950
transport = transport.clone('kwaak')
951
branch = self.make_remote_branch(transport, client)
952
branch._lock_token = 'b'
953
branch._repo_lock_token = 'r'
954
branch._set_parent_location('foo')
955
self.assertFinished(client)
957
def test_backwards_compat(self):
958
self.setup_smart_server_with_call_log()
959
branch = self.make_branch('.')
960
self.reset_smart_call_log()
961
verb = 'Branch.set_parent_location'
962
self.disable_verb(verb)
963
branch.set_parent('http://foo/')
964
self.assertLength(12, self.hpss_calls)
967
class TestBranchGetTagsBytes(RemoteBranchTestCase):
969
def test_backwards_compat(self):
970
self.setup_smart_server_with_call_log()
971
branch = self.make_branch('.')
972
self.reset_smart_call_log()
973
verb = 'Branch.get_tags_bytes'
974
self.disable_verb(verb)
975
branch.tags.get_tag_dict()
976
call_count = len([call for call in self.hpss_calls if
977
call.call.method == verb])
978
self.assertEqual(1, call_count)
980
def test_trivial(self):
981
transport = MemoryTransport()
982
client = FakeClient(transport.base)
983
client.add_expected_call(
984
'Branch.get_stacked_on_url', ('quack/',),
985
'error', ('NotStacked',))
986
client.add_expected_call(
987
'Branch.get_tags_bytes', ('quack/',),
989
transport.mkdir('quack')
990
transport = transport.clone('quack')
991
branch = self.make_remote_branch(transport, client)
992
result = branch.tags.get_tag_dict()
993
self.assertFinished(client)
994
self.assertEqual({}, result)
997
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
999
def test_empty_branch(self):
1000
# in an empty branch we decode the response properly
1001
transport = MemoryTransport()
1002
client = FakeClient(transport.base)
1003
client.add_expected_call(
1004
'Branch.get_stacked_on_url', ('quack/',),
1005
'error', ('NotStacked',))
1006
client.add_expected_call(
1007
'Branch.last_revision_info', ('quack/',),
1008
'success', ('ok', '0', 'null:'))
1009
transport.mkdir('quack')
1010
transport = transport.clone('quack')
1011
branch = self.make_remote_branch(transport, client)
1012
result = branch.last_revision_info()
1013
self.assertFinished(client)
1014
self.assertEqual((0, NULL_REVISION), result)
1016
def test_non_empty_branch(self):
1017
# in a non-empty branch we also decode the response properly
1018
revid = u'\xc8'.encode('utf8')
1019
transport = MemoryTransport()
1020
client = FakeClient(transport.base)
1021
client.add_expected_call(
1022
'Branch.get_stacked_on_url', ('kwaak/',),
1023
'error', ('NotStacked',))
1024
client.add_expected_call(
1025
'Branch.last_revision_info', ('kwaak/',),
1026
'success', ('ok', '2', revid))
1027
transport.mkdir('kwaak')
1028
transport = transport.clone('kwaak')
1029
branch = self.make_remote_branch(transport, client)
1030
result = branch.last_revision_info()
1031
self.assertEqual((2, revid), result)
1034
class TestBranch_get_stacked_on_url(TestRemote):
1035
"""Test Branch._get_stacked_on_url rpc"""
1037
def test_get_stacked_on_invalid_url(self):
1038
# test that asking for a stacked on url the server can't access works.
1039
# This isn't perfect, but then as we're in the same process there
1040
# really isn't anything we can do to be 100% sure that the server
1041
# doesn't just open in - this test probably needs to be rewritten using
1042
# a spawn()ed server.
1043
stacked_branch = self.make_branch('stacked', format='1.9')
1044
memory_branch = self.make_branch('base', format='1.9')
1045
vfs_url = self.get_vfs_only_url('base')
1046
stacked_branch.set_stacked_on_url(vfs_url)
1047
transport = stacked_branch.bzrdir.root_transport
1048
client = FakeClient(transport.base)
1049
client.add_expected_call(
1050
'Branch.get_stacked_on_url', ('stacked/',),
1051
'success', ('ok', vfs_url))
1052
# XXX: Multiple calls are bad, this second call documents what is
1054
client.add_expected_call(
1055
'Branch.get_stacked_on_url', ('stacked/',),
1056
'success', ('ok', vfs_url))
1057
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1059
repo_fmt = remote.RemoteRepositoryFormat()
1060
repo_fmt._custom_format = stacked_branch.repository._format
1061
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1063
result = branch.get_stacked_on_url()
1064
self.assertEqual(vfs_url, result)
1066
def test_backwards_compatible(self):
1067
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1068
base_branch = self.make_branch('base', format='1.6')
1069
stacked_branch = self.make_branch('stacked', format='1.6')
1070
stacked_branch.set_stacked_on_url('../base')
1071
client = FakeClient(self.get_url())
1072
branch_network_name = self.get_branch_format().network_name()
1073
client.add_expected_call(
1074
'BzrDir.open_branchV2', ('stacked/',),
1075
'success', ('branch', branch_network_name))
1076
client.add_expected_call(
1077
'BzrDir.find_repositoryV3', ('stacked/',),
1078
'success', ('ok', '', 'no', 'no', 'yes',
1079
stacked_branch.repository._format.network_name()))
1080
# called twice, once from constructor and then again by us
1081
client.add_expected_call(
1082
'Branch.get_stacked_on_url', ('stacked/',),
1083
'unknown', ('Branch.get_stacked_on_url',))
1084
client.add_expected_call(
1085
'Branch.get_stacked_on_url', ('stacked/',),
1086
'unknown', ('Branch.get_stacked_on_url',))
1087
# this will also do vfs access, but that goes direct to the transport
1088
# and isn't seen by the FakeClient.
1089
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1090
remote.RemoteBzrDirFormat(), _client=client)
1091
branch = bzrdir.open_branch()
1092
result = branch.get_stacked_on_url()
1093
self.assertEqual('../base', result)
1094
self.assertFinished(client)
1095
# it's in the fallback list both for the RemoteRepository and its vfs
1097
self.assertEqual(1, len(branch.repository._fallback_repositories))
1099
len(branch.repository._real_repository._fallback_repositories))
1101
def test_get_stacked_on_real_branch(self):
1102
base_branch = self.make_branch('base', format='1.6')
1103
stacked_branch = self.make_branch('stacked', format='1.6')
1104
stacked_branch.set_stacked_on_url('../base')
1105
reference_format = self.get_repo_format()
1106
network_name = reference_format.network_name()
1107
client = FakeClient(self.get_url())
1108
branch_network_name = self.get_branch_format().network_name()
1109
client.add_expected_call(
1110
'BzrDir.open_branchV2', ('stacked/',),
1111
'success', ('branch', branch_network_name))
1112
client.add_expected_call(
1113
'BzrDir.find_repositoryV3', ('stacked/',),
1114
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1115
# called twice, once from constructor and then again by us
1116
client.add_expected_call(
1117
'Branch.get_stacked_on_url', ('stacked/',),
1118
'success', ('ok', '../base'))
1119
client.add_expected_call(
1120
'Branch.get_stacked_on_url', ('stacked/',),
1121
'success', ('ok', '../base'))
1122
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1123
remote.RemoteBzrDirFormat(), _client=client)
1124
branch = bzrdir.open_branch()
1125
result = branch.get_stacked_on_url()
1126
self.assertEqual('../base', result)
1127
self.assertFinished(client)
1128
# it's in the fallback list both for the RemoteRepository.
1129
self.assertEqual(1, len(branch.repository._fallback_repositories))
1130
# And we haven't had to construct a real repository.
1131
self.assertEqual(None, branch.repository._real_repository)
1134
class TestBranchSetLastRevision(RemoteBranchTestCase):
1136
def test_set_empty(self):
1137
# set_revision_history([]) is translated to calling
1138
# Branch.set_last_revision(path, '') on the wire.
1139
transport = MemoryTransport()
1140
transport.mkdir('branch')
1141
transport = transport.clone('branch')
1143
client = FakeClient(transport.base)
1144
client.add_expected_call(
1145
'Branch.get_stacked_on_url', ('branch/',),
1146
'error', ('NotStacked',))
1147
client.add_expected_call(
1148
'Branch.lock_write', ('branch/', '', ''),
1149
'success', ('ok', 'branch token', 'repo token'))
1150
client.add_expected_call(
1151
'Branch.last_revision_info',
1153
'success', ('ok', '0', 'null:'))
1154
client.add_expected_call(
1155
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1157
client.add_expected_call(
1158
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1160
branch = self.make_remote_branch(transport, client)
1161
# This is a hack to work around the problem that RemoteBranch currently
1162
# unnecessarily invokes _ensure_real upon a call to lock_write.
1163
branch._ensure_real = lambda: None
1165
result = branch.set_revision_history([])
1167
self.assertEqual(None, result)
1168
self.assertFinished(client)
1170
def test_set_nonempty(self):
1171
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1172
# Branch.set_last_revision(path, rev-idN) on the wire.
1173
transport = MemoryTransport()
1174
transport.mkdir('branch')
1175
transport = transport.clone('branch')
1177
client = FakeClient(transport.base)
1178
client.add_expected_call(
1179
'Branch.get_stacked_on_url', ('branch/',),
1180
'error', ('NotStacked',))
1181
client.add_expected_call(
1182
'Branch.lock_write', ('branch/', '', ''),
1183
'success', ('ok', 'branch token', 'repo token'))
1184
client.add_expected_call(
1185
'Branch.last_revision_info',
1187
'success', ('ok', '0', 'null:'))
1189
encoded_body = bz2.compress('\n'.join(lines))
1190
client.add_success_response_with_body(encoded_body, 'ok')
1191
client.add_expected_call(
1192
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1194
client.add_expected_call(
1195
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1197
branch = self.make_remote_branch(transport, client)
1198
# This is a hack to work around the problem that RemoteBranch currently
1199
# unnecessarily invokes _ensure_real upon a call to lock_write.
1200
branch._ensure_real = lambda: None
1201
# Lock the branch, reset the record of remote calls.
1203
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1205
self.assertEqual(None, result)
1206
self.assertFinished(client)
1208
def test_no_such_revision(self):
1209
transport = MemoryTransport()
1210
transport.mkdir('branch')
1211
transport = transport.clone('branch')
1212
# A response of 'NoSuchRevision' is translated into an exception.
1213
client = FakeClient(transport.base)
1214
client.add_expected_call(
1215
'Branch.get_stacked_on_url', ('branch/',),
1216
'error', ('NotStacked',))
1217
client.add_expected_call(
1218
'Branch.lock_write', ('branch/', '', ''),
1219
'success', ('ok', 'branch token', 'repo token'))
1220
client.add_expected_call(
1221
'Branch.last_revision_info',
1223
'success', ('ok', '0', 'null:'))
1224
# get_graph calls to construct the revision history, for the set_rh
1227
encoded_body = bz2.compress('\n'.join(lines))
1228
client.add_success_response_with_body(encoded_body, 'ok')
1229
client.add_expected_call(
1230
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1231
'error', ('NoSuchRevision', 'rev-id'))
1232
client.add_expected_call(
1233
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1236
branch = self.make_remote_branch(transport, client)
1239
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1241
self.assertFinished(client)
1243
def test_tip_change_rejected(self):
1244
"""TipChangeRejected responses cause a TipChangeRejected exception to
1247
transport = MemoryTransport()
1248
transport.mkdir('branch')
1249
transport = transport.clone('branch')
1250
client = FakeClient(transport.base)
1251
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1252
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1253
client.add_expected_call(
1254
'Branch.get_stacked_on_url', ('branch/',),
1255
'error', ('NotStacked',))
1256
client.add_expected_call(
1257
'Branch.lock_write', ('branch/', '', ''),
1258
'success', ('ok', 'branch token', 'repo token'))
1259
client.add_expected_call(
1260
'Branch.last_revision_info',
1262
'success', ('ok', '0', 'null:'))
1264
encoded_body = bz2.compress('\n'.join(lines))
1265
client.add_success_response_with_body(encoded_body, 'ok')
1266
client.add_expected_call(
1267
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1268
'error', ('TipChangeRejected', rejection_msg_utf8))
1269
client.add_expected_call(
1270
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1272
branch = self.make_remote_branch(transport, client)
1273
branch._ensure_real = lambda: None
1275
# The 'TipChangeRejected' error response triggered by calling
1276
# set_revision_history causes a TipChangeRejected exception.
1277
err = self.assertRaises(
1278
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1279
# The UTF-8 message from the response has been decoded into a unicode
1281
self.assertIsInstance(err.msg, unicode)
1282
self.assertEqual(rejection_msg_unicode, err.msg)
1284
self.assertFinished(client)
1287
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1289
def test_set_last_revision_info(self):
1290
# set_last_revision_info(num, 'rev-id') is translated to calling
1291
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1292
transport = MemoryTransport()
1293
transport.mkdir('branch')
1294
transport = transport.clone('branch')
1295
client = FakeClient(transport.base)
1296
# get_stacked_on_url
1297
client.add_error_response('NotStacked')
1299
client.add_success_response('ok', 'branch token', 'repo token')
1300
# query the current revision
1301
client.add_success_response('ok', '0', 'null:')
1303
client.add_success_response('ok')
1305
client.add_success_response('ok')
1307
branch = self.make_remote_branch(transport, client)
1308
# Lock the branch, reset the record of remote calls.
1311
result = branch.set_last_revision_info(1234, 'a-revision-id')
1313
[('call', 'Branch.last_revision_info', ('branch/',)),
1314
('call', 'Branch.set_last_revision_info',
1315
('branch/', 'branch token', 'repo token',
1316
'1234', 'a-revision-id'))],
1318
self.assertEqual(None, result)
1320
def test_no_such_revision(self):
1321
# A response of 'NoSuchRevision' is translated into an exception.
1322
transport = MemoryTransport()
1323
transport.mkdir('branch')
1324
transport = transport.clone('branch')
1325
client = FakeClient(transport.base)
1326
# get_stacked_on_url
1327
client.add_error_response('NotStacked')
1329
client.add_success_response('ok', 'branch token', 'repo token')
1331
client.add_error_response('NoSuchRevision', 'revid')
1333
client.add_success_response('ok')
1335
branch = self.make_remote_branch(transport, client)
1336
# Lock the branch, reset the record of remote calls.
1341
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1344
def lock_remote_branch(self, branch):
1345
"""Trick a RemoteBranch into thinking it is locked."""
1346
branch._lock_mode = 'w'
1347
branch._lock_count = 2
1348
branch._lock_token = 'branch token'
1349
branch._repo_lock_token = 'repo token'
1350
branch.repository._lock_mode = 'w'
1351
branch.repository._lock_count = 2
1352
branch.repository._lock_token = 'repo token'
1354
def test_backwards_compatibility(self):
1355
"""If the server does not support the Branch.set_last_revision_info
1356
verb (which is new in 1.4), then the client falls back to VFS methods.
1358
# This test is a little messy. Unlike most tests in this file, it
1359
# doesn't purely test what a Remote* object sends over the wire, and
1360
# how it reacts to responses from the wire. It instead relies partly
1361
# on asserting that the RemoteBranch will call
1362
# self._real_branch.set_last_revision_info(...).
1364
# First, set up our RemoteBranch with a FakeClient that raises
1365
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1366
transport = MemoryTransport()
1367
transport.mkdir('branch')
1368
transport = transport.clone('branch')
1369
client = FakeClient(transport.base)
1370
client.add_expected_call(
1371
'Branch.get_stacked_on_url', ('branch/',),
1372
'error', ('NotStacked',))
1373
client.add_expected_call(
1374
'Branch.last_revision_info',
1376
'success', ('ok', '0', 'null:'))
1377
client.add_expected_call(
1378
'Branch.set_last_revision_info',
1379
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1380
'unknown', 'Branch.set_last_revision_info')
1382
branch = self.make_remote_branch(transport, client)
1383
class StubRealBranch(object):
1386
def set_last_revision_info(self, revno, revision_id):
1388
('set_last_revision_info', revno, revision_id))
1389
def _clear_cached_state(self):
1391
real_branch = StubRealBranch()
1392
branch._real_branch = real_branch
1393
self.lock_remote_branch(branch)
1395
# Call set_last_revision_info, and verify it behaved as expected.
1396
result = branch.set_last_revision_info(1234, 'a-revision-id')
1398
[('set_last_revision_info', 1234, 'a-revision-id')],
1400
self.assertFinished(client)
1402
def test_unexpected_error(self):
1403
# If the server sends an error the client doesn't understand, it gets
1404
# turned into an UnknownErrorFromSmartServer, which is presented as a
1405
# non-internal error to the user.
1406
transport = MemoryTransport()
1407
transport.mkdir('branch')
1408
transport = transport.clone('branch')
1409
client = FakeClient(transport.base)
1410
# get_stacked_on_url
1411
client.add_error_response('NotStacked')
1413
client.add_success_response('ok', 'branch token', 'repo token')
1415
client.add_error_response('UnexpectedError')
1417
client.add_success_response('ok')
1419
branch = self.make_remote_branch(transport, client)
1420
# Lock the branch, reset the record of remote calls.
1424
err = self.assertRaises(
1425
errors.UnknownErrorFromSmartServer,
1426
branch.set_last_revision_info, 123, 'revid')
1427
self.assertEqual(('UnexpectedError',), err.error_tuple)
1430
def test_tip_change_rejected(self):
1431
"""TipChangeRejected responses cause a TipChangeRejected exception to
1434
transport = MemoryTransport()
1435
transport.mkdir('branch')
1436
transport = transport.clone('branch')
1437
client = FakeClient(transport.base)
1438
# get_stacked_on_url
1439
client.add_error_response('NotStacked')
1441
client.add_success_response('ok', 'branch token', 'repo token')
1443
client.add_error_response('TipChangeRejected', 'rejection message')
1445
client.add_success_response('ok')
1447
branch = self.make_remote_branch(transport, client)
1448
# Lock the branch, reset the record of remote calls.
1450
self.addCleanup(branch.unlock)
1453
# The 'TipChangeRejected' error response triggered by calling
1454
# set_last_revision_info causes a TipChangeRejected exception.
1455
err = self.assertRaises(
1456
errors.TipChangeRejected,
1457
branch.set_last_revision_info, 123, 'revid')
1458
self.assertEqual('rejection message', err.msg)
1461
class TestBranchGetSetConfig(RemoteBranchTestCase):
1463
def test_get_branch_conf(self):
1464
# in an empty branch we decode the response properly
1465
client = FakeClient()
1466
client.add_expected_call(
1467
'Branch.get_stacked_on_url', ('memory:///',),
1468
'error', ('NotStacked',),)
1469
client.add_success_response_with_body('# config file body', 'ok')
1470
transport = MemoryTransport()
1471
branch = self.make_remote_branch(transport, client)
1472
config = branch.get_config()
1473
config.has_explicit_nickname()
1475
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1476
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1479
def test_get_multi_line_branch_conf(self):
1480
# Make sure that multiple-line branch.conf files are supported
1482
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1483
client = FakeClient()
1484
client.add_expected_call(
1485
'Branch.get_stacked_on_url', ('memory:///',),
1486
'error', ('NotStacked',),)
1487
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1488
transport = MemoryTransport()
1489
branch = self.make_remote_branch(transport, client)
1490
config = branch.get_config()
1491
self.assertEqual(u'2', config.get_user_option('b'))
1493
def test_set_option(self):
1494
client = FakeClient()
1495
client.add_expected_call(
1496
'Branch.get_stacked_on_url', ('memory:///',),
1497
'error', ('NotStacked',),)
1498
client.add_expected_call(
1499
'Branch.lock_write', ('memory:///', '', ''),
1500
'success', ('ok', 'branch token', 'repo token'))
1501
client.add_expected_call(
1502
'Branch.set_config_option', ('memory:///', 'branch token',
1503
'repo token', 'foo', 'bar', ''),
1505
client.add_expected_call(
1506
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1508
transport = MemoryTransport()
1509
branch = self.make_remote_branch(transport, client)
1511
config = branch._get_config()
1512
config.set_option('foo', 'bar')
1514
self.assertFinished(client)
1516
def test_backwards_compat_set_option(self):
1517
self.setup_smart_server_with_call_log()
1518
branch = self.make_branch('.')
1519
verb = 'Branch.set_config_option'
1520
self.disable_verb(verb)
1522
self.addCleanup(branch.unlock)
1523
self.reset_smart_call_log()
1524
branch._get_config().set_option('value', 'name')
1525
self.assertLength(10, self.hpss_calls)
1526
self.assertEqual('value', branch._get_config().get_option('name'))
1529
class TestBranchLockWrite(RemoteBranchTestCase):
1531
def test_lock_write_unlockable(self):
1532
transport = MemoryTransport()
1533
client = FakeClient(transport.base)
1534
client.add_expected_call(
1535
'Branch.get_stacked_on_url', ('quack/',),
1536
'error', ('NotStacked',),)
1537
client.add_expected_call(
1538
'Branch.lock_write', ('quack/', '', ''),
1539
'error', ('UnlockableTransport',))
1540
transport.mkdir('quack')
1541
transport = transport.clone('quack')
1542
branch = self.make_remote_branch(transport, client)
1543
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1544
self.assertFinished(client)
1547
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1549
def test__get_config(self):
1550
client = FakeClient()
1551
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1552
transport = MemoryTransport()
1553
bzrdir = self.make_remote_bzrdir(transport, client)
1554
config = bzrdir.get_config()
1555
self.assertEqual('/', config.get_default_stack_on())
1557
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1560
def test_set_option_uses_vfs(self):
1561
self.setup_smart_server_with_call_log()
1562
bzrdir = self.make_bzrdir('.')
1563
self.reset_smart_call_log()
1564
config = bzrdir.get_config()
1565
config.set_default_stack_on('/')
1566
self.assertLength(3, self.hpss_calls)
1568
def test_backwards_compat_get_option(self):
1569
self.setup_smart_server_with_call_log()
1570
bzrdir = self.make_bzrdir('.')
1571
verb = 'BzrDir.get_config_file'
1572
self.disable_verb(verb)
1573
self.reset_smart_call_log()
1574
self.assertEqual(None,
1575
bzrdir._get_config().get_option('default_stack_on'))
1576
self.assertLength(3, self.hpss_calls)
1579
class TestTransportIsReadonly(tests.TestCase):
1581
def test_true(self):
1582
client = FakeClient()
1583
client.add_success_response('yes')
1584
transport = RemoteTransport('bzr://example.com/', medium=False,
1586
self.assertEqual(True, transport.is_readonly())
1588
[('call', 'Transport.is_readonly', ())],
1591
def test_false(self):
1592
client = FakeClient()
1593
client.add_success_response('no')
1594
transport = RemoteTransport('bzr://example.com/', medium=False,
1596
self.assertEqual(False, transport.is_readonly())
1598
[('call', 'Transport.is_readonly', ())],
1601
def test_error_from_old_server(self):
1602
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1604
Clients should treat it as a "no" response, because is_readonly is only
1605
advisory anyway (a transport could be read-write, but then the
1606
underlying filesystem could be readonly anyway).
1608
client = FakeClient()
1609
client.add_unknown_method_response('Transport.is_readonly')
1610
transport = RemoteTransport('bzr://example.com/', medium=False,
1612
self.assertEqual(False, transport.is_readonly())
1614
[('call', 'Transport.is_readonly', ())],
1618
class TestTransportMkdir(tests.TestCase):
1620
def test_permissiondenied(self):
1621
client = FakeClient()
1622
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1623
transport = RemoteTransport('bzr://example.com/', medium=False,
1625
exc = self.assertRaises(
1626
errors.PermissionDenied, transport.mkdir, 'client path')
1627
expected_error = errors.PermissionDenied('/client path', 'extra')
1628
self.assertEqual(expected_error, exc)
1631
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1633
def test_defaults_to_none(self):
1634
t = RemoteSSHTransport('bzr+ssh://example.com')
1635
self.assertIs(None, t._get_credentials()[0])
1637
def test_uses_authentication_config(self):
1638
conf = config.AuthenticationConfig()
1639
conf._get_config().update(
1640
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1643
t = RemoteSSHTransport('bzr+ssh://example.com')
1644
self.assertEqual('bar', t._get_credentials()[0])
1647
class TestRemoteRepository(TestRemote):
1648
"""Base for testing RemoteRepository protocol usage.
1650
These tests contain frozen requests and responses. We want any changes to
1651
what is sent or expected to be require a thoughtful update to these tests
1652
because they might break compatibility with different-versioned servers.
1655
def setup_fake_client_and_repository(self, transport_path):
1656
"""Create the fake client and repository for testing with.
1658
There's no real server here; we just have canned responses sent
1661
:param transport_path: Path below the root of the MemoryTransport
1662
where the repository will be created.
1664
transport = MemoryTransport()
1665
transport.mkdir(transport_path)
1666
client = FakeClient(transport.base)
1667
transport = transport.clone(transport_path)
1668
# we do not want bzrdir to make any remote calls
1669
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1671
repo = RemoteRepository(bzrdir, None, _client=client)
1675
class TestRepositoryFormat(TestRemoteRepository):
1677
def test_fast_delta(self):
1678
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1679
true_format = RemoteRepositoryFormat()
1680
true_format._network_name = true_name
1681
self.assertEqual(True, true_format.fast_deltas)
1682
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1683
false_format = RemoteRepositoryFormat()
1684
false_format._network_name = false_name
1685
self.assertEqual(False, false_format.fast_deltas)
1688
class TestRepositoryGatherStats(TestRemoteRepository):
1690
def test_revid_none(self):
1691
# ('ok',), body with revisions and size
1692
transport_path = 'quack'
1693
repo, client = self.setup_fake_client_and_repository(transport_path)
1694
client.add_success_response_with_body(
1695
'revisions: 2\nsize: 18\n', 'ok')
1696
result = repo.gather_stats(None)
1698
[('call_expecting_body', 'Repository.gather_stats',
1699
('quack/','','no'))],
1701
self.assertEqual({'revisions': 2, 'size': 18}, result)
1703
def test_revid_no_committers(self):
1704
# ('ok',), body without committers
1705
body = ('firstrev: 123456.300 3600\n'
1706
'latestrev: 654231.400 0\n'
1709
transport_path = 'quick'
1710
revid = u'\xc8'.encode('utf8')
1711
repo, client = self.setup_fake_client_and_repository(transport_path)
1712
client.add_success_response_with_body(body, 'ok')
1713
result = repo.gather_stats(revid)
1715
[('call_expecting_body', 'Repository.gather_stats',
1716
('quick/', revid, 'no'))],
1718
self.assertEqual({'revisions': 2, 'size': 18,
1719
'firstrev': (123456.300, 3600),
1720
'latestrev': (654231.400, 0),},
1723
def test_revid_with_committers(self):
1724
# ('ok',), body with committers
1725
body = ('committers: 128\n'
1726
'firstrev: 123456.300 3600\n'
1727
'latestrev: 654231.400 0\n'
1730
transport_path = 'buick'
1731
revid = u'\xc8'.encode('utf8')
1732
repo, client = self.setup_fake_client_and_repository(transport_path)
1733
client.add_success_response_with_body(body, 'ok')
1734
result = repo.gather_stats(revid, True)
1736
[('call_expecting_body', 'Repository.gather_stats',
1737
('buick/', revid, 'yes'))],
1739
self.assertEqual({'revisions': 2, 'size': 18,
1741
'firstrev': (123456.300, 3600),
1742
'latestrev': (654231.400, 0),},
1746
class TestRepositoryGetGraph(TestRemoteRepository):
1748
def test_get_graph(self):
1749
# get_graph returns a graph with a custom parents provider.
1750
transport_path = 'quack'
1751
repo, client = self.setup_fake_client_and_repository(transport_path)
1752
graph = repo.get_graph()
1753
self.assertNotEqual(graph._parents_provider, repo)
1756
class TestRepositoryGetParentMap(TestRemoteRepository):
1758
def test_get_parent_map_caching(self):
1759
# get_parent_map returns from cache until unlock()
1760
# setup a reponse with two revisions
1761
r1 = u'\u0e33'.encode('utf8')
1762
r2 = u'\u0dab'.encode('utf8')
1763
lines = [' '.join([r2, r1]), r1]
1764
encoded_body = bz2.compress('\n'.join(lines))
1766
transport_path = 'quack'
1767
repo, client = self.setup_fake_client_and_repository(transport_path)
1768
client.add_success_response_with_body(encoded_body, 'ok')
1769
client.add_success_response_with_body(encoded_body, 'ok')
1771
graph = repo.get_graph()
1772
parents = graph.get_parent_map([r2])
1773
self.assertEqual({r2: (r1,)}, parents)
1774
# locking and unlocking deeper should not reset
1777
parents = graph.get_parent_map([r1])
1778
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1780
[('call_with_body_bytes_expecting_body',
1781
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1785
# now we call again, and it should use the second response.
1787
graph = repo.get_graph()
1788
parents = graph.get_parent_map([r1])
1789
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1791
[('call_with_body_bytes_expecting_body',
1792
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1794
('call_with_body_bytes_expecting_body',
1795
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1801
def test_get_parent_map_reconnects_if_unknown_method(self):
1802
transport_path = 'quack'
1803
rev_id = 'revision-id'
1804
repo, client = self.setup_fake_client_and_repository(transport_path)
1805
client.add_unknown_method_response('Repository.get_parent_map')
1806
client.add_success_response_with_body(rev_id, 'ok')
1807
self.assertFalse(client._medium._is_remote_before((1, 2)))
1808
parents = repo.get_parent_map([rev_id])
1810
[('call_with_body_bytes_expecting_body',
1811
'Repository.get_parent_map', ('quack/', 'include-missing:',
1813
('disconnect medium',),
1814
('call_expecting_body', 'Repository.get_revision_graph',
1817
# The medium is now marked as being connected to an older server
1818
self.assertTrue(client._medium._is_remote_before((1, 2)))
1819
self.assertEqual({rev_id: ('null:',)}, parents)
1821
def test_get_parent_map_fallback_parentless_node(self):
1822
"""get_parent_map falls back to get_revision_graph on old servers. The
1823
results from get_revision_graph are tweaked to match the get_parent_map
1826
Specifically, a {key: ()} result from get_revision_graph means "no
1827
parents" for that key, which in get_parent_map results should be
1828
represented as {key: ('null:',)}.
1830
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1832
rev_id = 'revision-id'
1833
transport_path = 'quack'
1834
repo, client = self.setup_fake_client_and_repository(transport_path)
1835
client.add_success_response_with_body(rev_id, 'ok')
1836
client._medium._remember_remote_is_before((1, 2))
1837
parents = repo.get_parent_map([rev_id])
1839
[('call_expecting_body', 'Repository.get_revision_graph',
1842
self.assertEqual({rev_id: ('null:',)}, parents)
1844
def test_get_parent_map_unexpected_response(self):
1845
repo, client = self.setup_fake_client_and_repository('path')
1846
client.add_success_response('something unexpected!')
1848
errors.UnexpectedSmartServerResponse,
1849
repo.get_parent_map, ['a-revision-id'])
1851
def test_get_parent_map_negative_caches_missing_keys(self):
1852
self.setup_smart_server_with_call_log()
1853
repo = self.make_repository('foo')
1854
self.assertIsInstance(repo, RemoteRepository)
1856
self.addCleanup(repo.unlock)
1857
self.reset_smart_call_log()
1858
graph = repo.get_graph()
1859
self.assertEqual({},
1860
graph.get_parent_map(['some-missing', 'other-missing']))
1861
self.assertLength(1, self.hpss_calls)
1862
# No call if we repeat this
1863
self.reset_smart_call_log()
1864
graph = repo.get_graph()
1865
self.assertEqual({},
1866
graph.get_parent_map(['some-missing', 'other-missing']))
1867
self.assertLength(0, self.hpss_calls)
1868
# Asking for more unknown keys makes a request.
1869
self.reset_smart_call_log()
1870
graph = repo.get_graph()
1871
self.assertEqual({},
1872
graph.get_parent_map(['some-missing', 'other-missing',
1874
self.assertLength(1, self.hpss_calls)
1876
def disableExtraResults(self):
1877
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1878
SmartServerRepositoryGetParentMap.no_extra_results = True
1880
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1881
self.addCleanup(reset_values)
1883
def test_null_cached_missing_and_stop_key(self):
1884
self.setup_smart_server_with_call_log()
1885
# Make a branch with a single revision.
1886
builder = self.make_branch_builder('foo')
1887
builder.start_series()
1888
builder.build_snapshot('first', None, [
1889
('add', ('', 'root-id', 'directory', ''))])
1890
builder.finish_series()
1891
branch = builder.get_branch()
1892
repo = branch.repository
1893
self.assertIsInstance(repo, RemoteRepository)
1894
# Stop the server from sending extra results.
1895
self.disableExtraResults()
1897
self.addCleanup(repo.unlock)
1898
self.reset_smart_call_log()
1899
graph = repo.get_graph()
1900
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1901
# 'first' it will be a candidate for the stop_keys of subsequent
1902
# requests, and because 'null:' was queried but not returned it will be
1903
# cached as missing.
1904
self.assertEqual({'first': ('null:',)},
1905
graph.get_parent_map(['first', 'null:']))
1906
# Now query for another key. This request will pass along a recipe of
1907
# start and stop keys describing the already cached results, and this
1908
# recipe's revision count must be correct (or else it will trigger an
1909
# error from the server).
1910
self.assertEqual({}, graph.get_parent_map(['another-key']))
1911
# This assertion guards against disableExtraResults silently failing to
1912
# work, thus invalidating the test.
1913
self.assertLength(2, self.hpss_calls)
1915
def test_get_parent_map_gets_ghosts_from_result(self):
1916
# asking for a revision should negatively cache close ghosts in its
1918
self.setup_smart_server_with_call_log()
1919
tree = self.make_branch_and_memory_tree('foo')
1922
builder = treebuilder.TreeBuilder()
1923
builder.start_tree(tree)
1925
builder.finish_tree()
1926
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1927
rev_id = tree.commit('')
1931
self.addCleanup(tree.unlock)
1932
repo = tree.branch.repository
1933
self.assertIsInstance(repo, RemoteRepository)
1935
repo.get_parent_map([rev_id])
1936
self.reset_smart_call_log()
1937
# Now asking for rev_id's ghost parent should not make calls
1938
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1939
self.assertLength(0, self.hpss_calls)
1942
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1944
def test_allows_new_revisions(self):
1945
"""get_parent_map's results can be updated by commit."""
1946
smart_server = server.SmartTCPServer_for_testing()
1947
smart_server.setUp()
1948
self.addCleanup(smart_server.tearDown)
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 TestRepositoryInsertStream(TestRemoteRepository):
2217
"""Tests for using Repository.insert_stream verb when the _1.18 variant is
2220
This test case is very similar to TestRepositoryInsertStream_1_18.
2224
TestRemoteRepository.setUp(self)
2225
self.disable_verb('Repository.insert_stream_1.18')
2227
def test_unlocked_repo(self):
2228
transport_path = 'quack'
2229
repo, client = self.setup_fake_client_and_repository(transport_path)
2230
client.add_expected_call(
2231
'Repository.insert_stream_1.18', ('quack/', ''),
2232
'unknown', ('Repository.insert_stream_1.18',))
2233
client.add_expected_call(
2234
'Repository.insert_stream', ('quack/', ''),
2236
client.add_expected_call(
2237
'Repository.insert_stream', ('quack/', ''),
2239
sink = repo._get_sink()
2240
fmt = repository.RepositoryFormat.get_default_format()
2241
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2242
self.assertEqual([], resume_tokens)
2243
self.assertEqual(set(), missing_keys)
2244
self.assertFinished(client)
2246
def test_locked_repo_with_no_lock_token(self):
2247
transport_path = 'quack'
2248
repo, client = self.setup_fake_client_and_repository(transport_path)
2249
client.add_expected_call(
2250
'Repository.lock_write', ('quack/', ''),
2251
'success', ('ok', ''))
2252
client.add_expected_call(
2253
'Repository.insert_stream_1.18', ('quack/', ''),
2254
'unknown', ('Repository.insert_stream_1.18',))
2255
client.add_expected_call(
2256
'Repository.insert_stream', ('quack/', ''),
2258
client.add_expected_call(
2259
'Repository.insert_stream', ('quack/', ''),
2262
sink = repo._get_sink()
2263
fmt = repository.RepositoryFormat.get_default_format()
2264
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2265
self.assertEqual([], resume_tokens)
2266
self.assertEqual(set(), missing_keys)
2267
self.assertFinished(client)
2269
def test_locked_repo_with_lock_token(self):
2270
transport_path = 'quack'
2271
repo, client = self.setup_fake_client_and_repository(transport_path)
2272
client.add_expected_call(
2273
'Repository.lock_write', ('quack/', ''),
2274
'success', ('ok', 'a token'))
2275
client.add_expected_call(
2276
'Repository.insert_stream_1.18', ('quack/', '', 'a token'),
2277
'unknown', ('Repository.insert_stream_1.18',))
2278
client.add_expected_call(
2279
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2281
client.add_expected_call(
2282
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2285
sink = repo._get_sink()
2286
fmt = repository.RepositoryFormat.get_default_format()
2287
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2288
self.assertEqual([], resume_tokens)
2289
self.assertEqual(set(), missing_keys)
2290
self.assertFinished(client)
2292
def test_stream_with_inventory_deltas(self):
2293
"""'inventory-deltas' substreams cannot be sent to the
2294
Repository.insert_stream verb, because not all servers that implement
2295
that verb will accept them. So when one is encountered the RemoteSink
2296
immediately stops using that verb and falls back to VFS insert_stream.
2298
transport_path = 'quack'
2299
repo, client = self.setup_fake_client_and_repository(transport_path)
2300
client.add_expected_call(
2301
'Repository.insert_stream_1.18', ('quack/', ''),
2302
'unknown', ('Repository.insert_stream_1.18',))
2303
client.add_expected_call(
2304
'Repository.insert_stream', ('quack/', ''),
2306
client.add_expected_call(
2307
'Repository.insert_stream', ('quack/', ''),
2309
# Create a fake real repository for insert_stream to fall back on, so
2310
# that we can directly see the records the RemoteSink passes to the
2315
def insert_stream(self, stream, src_format, resume_tokens):
2316
for substream_kind, substream in stream:
2317
self.records.append(
2318
(substream_kind, [record.key for record in substream]))
2319
return ['fake tokens'], ['fake missing keys']
2320
fake_real_sink = FakeRealSink()
2321
class FakeRealRepository:
2322
def _get_sink(self):
2323
return fake_real_sink
2324
repo._real_repository = FakeRealRepository()
2325
sink = repo._get_sink()
2326
fmt = repository.RepositoryFormat.get_default_format()
2327
stream = self.make_stream_with_inv_deltas(fmt)
2328
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2329
# Every record from the first inventory delta should have been sent to
2331
expected_records = [
2332
('inventory-deltas', [('rev2',), ('rev3',)]),
2333
('texts', [('some-rev', 'some-file')])]
2334
self.assertEqual(expected_records, fake_real_sink.records)
2335
# The return values from the real sink's insert_stream are propagated
2336
# back to the original caller.
2337
self.assertEqual(['fake tokens'], resume_tokens)
2338
self.assertEqual(['fake missing keys'], missing_keys)
2339
self.assertFinished(client)
2341
def make_stream_with_inv_deltas(self, fmt):
2342
"""Make a simple stream with an inventory delta followed by more
2343
records and more substreams to test that all records and substreams
2344
from that point on are used.
2346
This sends, in order:
2347
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2349
* texts substream: (some-rev, some-file)
2351
# Define a stream using generators so that it isn't rewindable.
2352
inv = inventory.Inventory(revision_id='rev1')
2353
def stream_with_inv_delta():
2354
yield ('inventories', inventories_substream())
2355
yield ('inventory-deltas', inventory_delta_substream())
2357
versionedfile.FulltextContentFactory(
2358
('some-rev', 'some-file'), (), None, 'content')])
2359
def inventories_substream():
2360
# An empty inventory fulltext. This will be streamed normally.
2361
text = fmt._serializer.write_inventory_to_string(inv)
2362
yield versionedfile.FulltextContentFactory(
2363
('rev1',), (), None, text)
2364
def inventory_delta_substream():
2365
# An inventory delta. This can't be streamed via this verb, so it
2366
# will trigger a fallback to VFS insert_stream.
2367
entry = inv.make_entry(
2368
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2369
entry.revision = 'ghost'
2370
delta = [(None, 'newdir', 'newdir-id', entry)]
2371
serializer = inventory_delta.InventoryDeltaSerializer(
2372
versioned_root=True, tree_references=False)
2373
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2374
yield versionedfile.ChunkedContentFactory(
2375
('rev2',), (('rev1',)), None, lines)
2377
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2378
yield versionedfile.ChunkedContentFactory(
2379
('rev3',), (('rev1',)), None, lines)
2380
return stream_with_inv_delta()
2383
class TestRepositoryInsertStream_1_18(TestRemoteRepository):
2385
def test_unlocked_repo(self):
2386
transport_path = 'quack'
2387
repo, client = self.setup_fake_client_and_repository(transport_path)
2388
client.add_expected_call(
2389
'Repository.insert_stream_1.18', ('quack/', ''),
2391
client.add_expected_call(
2392
'Repository.insert_stream_1.18', ('quack/', ''),
2394
sink = repo._get_sink()
2395
fmt = repository.RepositoryFormat.get_default_format()
2396
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2397
self.assertEqual([], resume_tokens)
2398
self.assertEqual(set(), missing_keys)
2399
self.assertFinished(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.18', ('quack/', ''),
2410
client.add_expected_call(
2411
'Repository.insert_stream_1.18', ('quack/', ''),
2414
sink = repo._get_sink()
2415
fmt = repository.RepositoryFormat.get_default_format()
2416
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2417
self.assertEqual([], resume_tokens)
2418
self.assertEqual(set(), missing_keys)
2419
self.assertFinished(client)
2421
def test_locked_repo_with_lock_token(self):
2422
transport_path = 'quack'
2423
repo, client = self.setup_fake_client_and_repository(transport_path)
2424
client.add_expected_call(
2425
'Repository.lock_write', ('quack/', ''),
2426
'success', ('ok', 'a token'))
2427
client.add_expected_call(
2428
'Repository.insert_stream_1.18', ('quack/', '', 'a token'),
2430
client.add_expected_call(
2431
'Repository.insert_stream_1.18', ('quack/', '', 'a token'),
2434
sink = repo._get_sink()
2435
fmt = repository.RepositoryFormat.get_default_format()
2436
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2437
self.assertEqual([], resume_tokens)
2438
self.assertEqual(set(), missing_keys)
2439
self.assertFinished(client)
2442
class TestRepositoryTarball(TestRemoteRepository):
2444
# This is a canned tarball reponse we can validate against
2446
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2447
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2448
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2449
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2450
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2451
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2452
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2453
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2454
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2455
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2456
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2457
'nWQ7QH/F3JFOFCQ0aSPfA='
2460
def test_repository_tarball(self):
2461
# Test that Repository.tarball generates the right operations
2462
transport_path = 'repo'
2463
expected_calls = [('call_expecting_body', 'Repository.tarball',
2464
('repo/', 'bz2',),),
2466
repo, client = self.setup_fake_client_and_repository(transport_path)
2467
client.add_success_response_with_body(self.tarball_content, 'ok')
2468
# Now actually ask for the tarball
2469
tarball_file = repo._get_tarball('bz2')
2471
self.assertEqual(expected_calls, client._calls)
2472
self.assertEqual(self.tarball_content, tarball_file.read())
2474
tarball_file.close()
2477
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2478
"""RemoteRepository.copy_content_into optimizations"""
2480
def test_copy_content_remote_to_local(self):
2481
self.transport_server = server.SmartTCPServer_for_testing
2482
src_repo = self.make_repository('repo1')
2483
src_repo = repository.Repository.open(self.get_url('repo1'))
2484
# At the moment the tarball-based copy_content_into can't write back
2485
# into a smart server. It would be good if it could upload the
2486
# tarball; once that works we'd have to create repositories of
2487
# different formats. -- mbp 20070410
2488
dest_url = self.get_vfs_only_url('repo2')
2489
dest_bzrdir = BzrDir.create(dest_url)
2490
dest_repo = dest_bzrdir.create_repository()
2491
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2492
self.assertTrue(isinstance(src_repo, RemoteRepository))
2493
src_repo.copy_content_into(dest_repo)
2496
class _StubRealPackRepository(object):
2498
def __init__(self, calls):
2500
self._pack_collection = _StubPackCollection(calls)
2502
def is_in_write_group(self):
2505
def refresh_data(self):
2506
self.calls.append(('pack collection reload_pack_names',))
2509
class _StubPackCollection(object):
2511
def __init__(self, calls):
2515
self.calls.append(('pack collection autopack',))
2518
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2519
"""Tests for RemoteRepository.autopack implementation."""
2522
"""When the server returns 'ok' and there's no _real_repository, then
2523
nothing else happens: the autopack method is done.
2525
transport_path = 'quack'
2526
repo, client = self.setup_fake_client_and_repository(transport_path)
2527
client.add_expected_call(
2528
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2530
self.assertFinished(client)
2532
def test_ok_with_real_repo(self):
2533
"""When the server returns 'ok' and there is a _real_repository, then
2534
the _real_repository's reload_pack_name's method will be called.
2536
transport_path = 'quack'
2537
repo, client = self.setup_fake_client_and_repository(transport_path)
2538
client.add_expected_call(
2539
'PackRepository.autopack', ('quack/',),
2541
repo._real_repository = _StubRealPackRepository(client._calls)
2544
[('call', 'PackRepository.autopack', ('quack/',)),
2545
('pack collection reload_pack_names',)],
2548
def test_backwards_compatibility(self):
2549
"""If the server does not recognise the PackRepository.autopack verb,
2550
fallback to the real_repository's implementation.
2552
transport_path = 'quack'
2553
repo, client = self.setup_fake_client_and_repository(transport_path)
2554
client.add_unknown_method_response('PackRepository.autopack')
2555
def stub_ensure_real():
2556
client._calls.append(('_ensure_real',))
2557
repo._real_repository = _StubRealPackRepository(client._calls)
2558
repo._ensure_real = stub_ensure_real
2561
[('call', 'PackRepository.autopack', ('quack/',)),
2563
('pack collection autopack',)],
2567
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2568
"""Base class for unit tests for bzrlib.remote._translate_error."""
2570
def translateTuple(self, error_tuple, **context):
2571
"""Call _translate_error with an ErrorFromSmartServer built from the
2574
:param error_tuple: A tuple of a smart server response, as would be
2575
passed to an ErrorFromSmartServer.
2576
:kwargs context: context items to call _translate_error with.
2578
:returns: The error raised by _translate_error.
2580
# Raise the ErrorFromSmartServer before passing it as an argument,
2581
# because _translate_error may need to re-raise it with a bare 'raise'
2583
server_error = errors.ErrorFromSmartServer(error_tuple)
2584
translated_error = self.translateErrorFromSmartServer(
2585
server_error, **context)
2586
return translated_error
2588
def translateErrorFromSmartServer(self, error_object, **context):
2589
"""Like translateTuple, but takes an already constructed
2590
ErrorFromSmartServer rather than a tuple.
2594
except errors.ErrorFromSmartServer, server_error:
2595
translated_error = self.assertRaises(
2596
errors.BzrError, remote._translate_error, server_error,
2598
return translated_error
2601
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2602
"""Unit tests for bzrlib.remote._translate_error.
2604
Given an ErrorFromSmartServer (which has an error tuple from a smart
2605
server) and some context, _translate_error raises more specific errors from
2608
This test case covers the cases where _translate_error succeeds in
2609
translating an ErrorFromSmartServer to something better. See
2610
TestErrorTranslationRobustness for other cases.
2613
def test_NoSuchRevision(self):
2614
branch = self.make_branch('')
2616
translated_error = self.translateTuple(
2617
('NoSuchRevision', revid), branch=branch)
2618
expected_error = errors.NoSuchRevision(branch, revid)
2619
self.assertEqual(expected_error, translated_error)
2621
def test_nosuchrevision(self):
2622
repository = self.make_repository('')
2624
translated_error = self.translateTuple(
2625
('nosuchrevision', revid), repository=repository)
2626
expected_error = errors.NoSuchRevision(repository, revid)
2627
self.assertEqual(expected_error, translated_error)
2629
def test_nobranch(self):
2630
bzrdir = self.make_bzrdir('')
2631
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2632
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2633
self.assertEqual(expected_error, translated_error)
2635
def test_LockContention(self):
2636
translated_error = self.translateTuple(('LockContention',))
2637
expected_error = errors.LockContention('(remote lock)')
2638
self.assertEqual(expected_error, translated_error)
2640
def test_UnlockableTransport(self):
2641
bzrdir = self.make_bzrdir('')
2642
translated_error = self.translateTuple(
2643
('UnlockableTransport',), bzrdir=bzrdir)
2644
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2645
self.assertEqual(expected_error, translated_error)
2647
def test_LockFailed(self):
2648
lock = 'str() of a server lock'
2649
why = 'str() of why'
2650
translated_error = self.translateTuple(('LockFailed', lock, why))
2651
expected_error = errors.LockFailed(lock, why)
2652
self.assertEqual(expected_error, translated_error)
2654
def test_TokenMismatch(self):
2655
token = 'a lock token'
2656
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2657
expected_error = errors.TokenMismatch(token, '(remote token)')
2658
self.assertEqual(expected_error, translated_error)
2660
def test_Diverged(self):
2661
branch = self.make_branch('a')
2662
other_branch = self.make_branch('b')
2663
translated_error = self.translateTuple(
2664
('Diverged',), branch=branch, other_branch=other_branch)
2665
expected_error = errors.DivergedBranches(branch, other_branch)
2666
self.assertEqual(expected_error, translated_error)
2668
def test_ReadError_no_args(self):
2670
translated_error = self.translateTuple(('ReadError',), path=path)
2671
expected_error = errors.ReadError(path)
2672
self.assertEqual(expected_error, translated_error)
2674
def test_ReadError(self):
2676
translated_error = self.translateTuple(('ReadError', path))
2677
expected_error = errors.ReadError(path)
2678
self.assertEqual(expected_error, translated_error)
2680
def test_PermissionDenied_no_args(self):
2682
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2683
expected_error = errors.PermissionDenied(path)
2684
self.assertEqual(expected_error, translated_error)
2686
def test_PermissionDenied_one_arg(self):
2688
translated_error = self.translateTuple(('PermissionDenied', path))
2689
expected_error = errors.PermissionDenied(path)
2690
self.assertEqual(expected_error, translated_error)
2692
def test_PermissionDenied_one_arg_and_context(self):
2693
"""Given a choice between a path from the local context and a path on
2694
the wire, _translate_error prefers the path from the local context.
2696
local_path = 'local path'
2697
remote_path = 'remote path'
2698
translated_error = self.translateTuple(
2699
('PermissionDenied', remote_path), path=local_path)
2700
expected_error = errors.PermissionDenied(local_path)
2701
self.assertEqual(expected_error, translated_error)
2703
def test_PermissionDenied_two_args(self):
2705
extra = 'a string with extra info'
2706
translated_error = self.translateTuple(
2707
('PermissionDenied', path, extra))
2708
expected_error = errors.PermissionDenied(path, extra)
2709
self.assertEqual(expected_error, translated_error)
2712
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2713
"""Unit tests for bzrlib.remote._translate_error's robustness.
2715
TestErrorTranslationSuccess is for cases where _translate_error can
2716
translate successfully. This class about how _translate_err behaves when
2717
it fails to translate: it re-raises the original error.
2720
def test_unrecognised_server_error(self):
2721
"""If the error code from the server is not recognised, the original
2722
ErrorFromSmartServer is propagated unmodified.
2724
error_tuple = ('An unknown error tuple',)
2725
server_error = errors.ErrorFromSmartServer(error_tuple)
2726
translated_error = self.translateErrorFromSmartServer(server_error)
2727
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2728
self.assertEqual(expected_error, translated_error)
2730
def test_context_missing_a_key(self):
2731
"""In case of a bug in the client, or perhaps an unexpected response
2732
from a server, _translate_error returns the original error tuple from
2733
the server and mutters a warning.
2735
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2736
# in the context dict. So let's give it an empty context dict instead
2737
# to exercise its error recovery.
2739
error_tuple = ('NoSuchRevision', 'revid')
2740
server_error = errors.ErrorFromSmartServer(error_tuple)
2741
translated_error = self.translateErrorFromSmartServer(server_error)
2742
self.assertEqual(server_error, translated_error)
2743
# In addition to re-raising ErrorFromSmartServer, some debug info has
2744
# been muttered to the log file for developer to look at.
2745
self.assertContainsRe(
2746
self._get_log(keep_log_file=True),
2747
"Missing key 'branch' in context")
2749
def test_path_missing(self):
2750
"""Some translations (PermissionDenied, ReadError) can determine the
2751
'path' variable from either the wire or the local context. If neither
2752
has it, then an error is raised.
2754
error_tuple = ('ReadError',)
2755
server_error = errors.ErrorFromSmartServer(error_tuple)
2756
translated_error = self.translateErrorFromSmartServer(server_error)
2757
self.assertEqual(server_error, translated_error)
2758
# In addition to re-raising ErrorFromSmartServer, some debug info has
2759
# been muttered to the log file for developer to look at.
2760
self.assertContainsRe(
2761
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2764
class TestStacking(tests.TestCaseWithTransport):
2765
"""Tests for operations on stacked remote repositories.
2767
The underlying format type must support stacking.
2770
def test_access_stacked_remote(self):
2771
# based on <http://launchpad.net/bugs/261315>
2772
# make a branch stacked on another repository containing an empty
2773
# revision, then open it over hpss - we should be able to see that
2775
base_transport = self.get_transport()
2776
base_builder = self.make_branch_builder('base', format='1.9')
2777
base_builder.start_series()
2778
base_revid = base_builder.build_snapshot('rev-id', None,
2779
[('add', ('', None, 'directory', None))],
2781
base_builder.finish_series()
2782
stacked_branch = self.make_branch('stacked', format='1.9')
2783
stacked_branch.set_stacked_on_url('../base')
2784
# start a server looking at this
2785
smart_server = server.SmartTCPServer_for_testing()
2786
smart_server.setUp()
2787
self.addCleanup(smart_server.tearDown)
2788
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2789
# can get its branch and repository
2790
remote_branch = remote_bzrdir.open_branch()
2791
remote_repo = remote_branch.repository
2792
remote_repo.lock_read()
2794
# it should have an appropriate fallback repository, which should also
2795
# be a RemoteRepository
2796
self.assertLength(1, remote_repo._fallback_repositories)
2797
self.assertIsInstance(remote_repo._fallback_repositories[0],
2799
# and it has the revision committed to the underlying repository;
2800
# these have varying implementations so we try several of them
2801
self.assertTrue(remote_repo.has_revisions([base_revid]))
2802
self.assertTrue(remote_repo.has_revision(base_revid))
2803
self.assertEqual(remote_repo.get_revision(base_revid).message,
2806
remote_repo.unlock()
2808
def prepare_stacked_remote_branch(self):
2809
"""Get stacked_upon and stacked branches with content in each."""
2810
self.setup_smart_server_with_call_log()
2811
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2812
tree1.commit('rev1', rev_id='rev1')
2813
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2814
).open_workingtree()
2815
tree2.commit('local changes make me feel good.')
2816
branch2 = Branch.open(self.get_url('tree2'))
2818
self.addCleanup(branch2.unlock)
2819
return tree1.branch, branch2
2821
def test_stacked_get_parent_map(self):
2822
# the public implementation of get_parent_map obeys stacking
2823
_, branch = self.prepare_stacked_remote_branch()
2824
repo = branch.repository
2825
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2827
def test_unstacked_get_parent_map(self):
2828
# _unstacked_provider.get_parent_map ignores stacking
2829
_, branch = self.prepare_stacked_remote_branch()
2830
provider = branch.repository._unstacked_provider
2831
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2833
def fetch_stream_to_rev_order(self, stream):
2835
for kind, substream in stream:
2836
if not kind == 'revisions':
2839
for content in substream:
2840
result.append(content.key[-1])
2843
def get_ordered_revs(self, format, order, branch_factory=None):
2844
"""Get a list of the revisions in a stream to format format.
2846
:param format: The format of the target.
2847
:param order: the order that target should have requested.
2848
:param branch_factory: A callable to create a trunk and stacked branch
2849
to fetch from. If none, self.prepare_stacked_remote_branch is used.
2850
:result: The revision ids in the stream, in the order seen,
2851
the topological order of revisions in the source.
2853
unordered_format = bzrdir.format_registry.get(format)()
2854
target_repository_format = unordered_format.repository_format
2856
self.assertEqual(order, target_repository_format._fetch_order)
2857
if branch_factory is None:
2858
branch_factory = self.prepare_stacked_remote_branch
2859
_, stacked = branch_factory()
2860
source = stacked.repository._get_source(target_repository_format)
2861
tip = stacked.last_revision()
2862
revs = stacked.repository.get_ancestry(tip)
2863
search = graph.PendingAncestryResult([tip], stacked.repository)
2864
self.reset_smart_call_log()
2865
stream = source.get_stream(search)
2868
# We trust that if a revision is in the stream the rest of the new
2869
# content for it is too, as per our main fetch tests; here we are
2870
# checking that the revisions are actually included at all, and their
2872
return self.fetch_stream_to_rev_order(stream), revs
2874
def test_stacked_get_stream_unordered(self):
2875
# Repository._get_source.get_stream() from a stacked repository with
2876
# unordered yields the full data from both stacked and stacked upon
2878
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2879
self.assertEqual(set(expected_revs), set(rev_ord))
2880
# Getting unordered results should have made a streaming data request
2881
# from the server, then one from the backing branch.
2882
self.assertLength(2, self.hpss_calls)
2884
def test_stacked_on_stacked_get_stream_unordered(self):
2885
# Repository._get_source.get_stream() from a stacked repository which
2886
# is itself stacked yields the full data from all three sources.
2887
def make_stacked_stacked():
2888
_, stacked = self.prepare_stacked_remote_branch()
2889
tree = stacked.bzrdir.sprout('tree3', stacked=True
2890
).open_workingtree()
2891
tree.commit('more local changes are better')
2892
branch = Branch.open(self.get_url('tree3'))
2895
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
2896
branch_factory=make_stacked_stacked)
2897
self.assertEqual(set(expected_revs), set(rev_ord))
2898
# Getting unordered results should have made a streaming data request
2899
# from the server, and one from each backing repo
2900
self.assertLength(3, self.hpss_calls)
2902
def test_stacked_get_stream_topological(self):
2903
# Repository._get_source.get_stream() from a stacked repository with
2904
# topological sorting yields the full data from both stacked and
2905
# stacked upon sources in topological order.
2906
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2907
self.assertEqual(expected_revs, rev_ord)
2908
# Getting topological sort requires VFS calls still
2909
self.assertLength(12, self.hpss_calls)
2911
def test_stacked_get_stream_groupcompress(self):
2912
# Repository._get_source.get_stream() from a stacked repository with
2913
# groupcompress sorting yields the full data from both stacked and
2914
# stacked upon sources in groupcompress order.
2915
raise tests.TestSkipped('No groupcompress ordered format available')
2916
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2917
self.assertEqual(expected_revs, reversed(rev_ord))
2918
# Getting unordered results should have made a streaming data request
2919
# from the backing branch, and one from the stacked on branch.
2920
self.assertLength(2, self.hpss_calls)
2922
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
2923
# When pulling some fixed amount of content that is more than the
2924
# source has (because some is coming from a fallback branch, no error
2925
# should be received. This was reported as bug 360791.
2926
# Need three branches: a trunk, a stacked branch, and a preexisting
2927
# branch pulling content from stacked and trunk.
2928
self.setup_smart_server_with_call_log()
2929
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
2930
r1 = trunk.commit('start')
2931
stacked_branch = trunk.branch.create_clone_on_transport(
2932
self.get_transport('stacked'), stacked_on=trunk.branch.base)
2933
local = self.make_branch('local', format='1.9-rich-root')
2934
local.repository.fetch(stacked_branch.repository,
2935
stacked_branch.last_revision())
2938
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2941
super(TestRemoteBranchEffort, self).setUp()
2942
# Create a smart server that publishes whatever the backing VFS server
2944
self.smart_server = server.SmartTCPServer_for_testing()
2945
self.smart_server.setUp(self.get_server())
2946
self.addCleanup(self.smart_server.tearDown)
2947
# Log all HPSS calls into self.hpss_calls.
2948
_SmartClient.hooks.install_named_hook(
2949
'call', self.capture_hpss_call, None)
2950
self.hpss_calls = []
2952
def capture_hpss_call(self, params):
2953
self.hpss_calls.append(params.method)
2955
def test_copy_content_into_avoids_revision_history(self):
2956
local = self.make_branch('local')
2957
remote_backing_tree = self.make_branch_and_tree('remote')
2958
remote_backing_tree.commit("Commit.")
2959
remote_branch_url = self.smart_server.get_url() + 'remote'
2960
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2961
local.repository.fetch(remote_branch.repository)
2962
self.hpss_calls = []
2963
remote_branch.copy_content_into(local)
2964
self.assertFalse('Branch.revision_history' in self.hpss_calls)