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(self, method, args, body):
284
self._check_call(method, args)
285
self._calls.append(('call_with_body_bytes', method, args, body))
286
result = self._get_next_response()
287
return result[1], FakeProtocol(result[2], self)
289
def call_with_body_bytes_expecting_body(self, method, args, body):
290
self._check_call(method, args)
291
self._calls.append(('call_with_body_bytes_expecting_body', method,
293
result = self._get_next_response()
294
self.expecting_body = True
295
return result[1], FakeProtocol(result[2], self)
297
def call_with_body_stream(self, args, stream):
298
# Explicitly consume the stream before checking for an error, because
299
# that's what happens a real medium.
300
stream = list(stream)
301
self._check_call(args[0], args[1:])
302
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
303
result = self._get_next_response()
304
# The second value returned from call_with_body_stream is supposed to
305
# be a response_handler object, but so far no tests depend on that.
306
response_handler = None
307
return result[1], response_handler
310
class FakeMedium(medium.SmartClientMedium):
312
def __init__(self, client_calls, base):
313
medium.SmartClientMedium.__init__(self, base)
314
self._client_calls = client_calls
316
def disconnect(self):
317
self._client_calls.append(('disconnect medium',))
320
class TestVfsHas(tests.TestCase):
322
def test_unicode_path(self):
323
client = FakeClient('/')
324
client.add_success_response('yes',)
325
transport = RemoteTransport('bzr://localhost/', _client=client)
326
filename = u'/hell\u00d8'.encode('utf8')
327
result = transport.has(filename)
329
[('call', 'has', (filename,))],
331
self.assertTrue(result)
334
class TestRemote(tests.TestCaseWithMemoryTransport):
336
def get_branch_format(self):
337
reference_bzrdir_format = bzrdir.format_registry.get('default')()
338
return reference_bzrdir_format.get_branch_format()
340
def get_repo_format(self):
341
reference_bzrdir_format = bzrdir.format_registry.get('default')()
342
return reference_bzrdir_format.repository_format
344
def assertFinished(self, fake_client):
345
"""Assert that all of a FakeClient's expected calls have occurred."""
346
fake_client.finished_test()
349
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
350
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
352
def assertRemotePath(self, expected, client_base, transport_base):
353
"""Assert that the result of
354
SmartClientMedium.remote_path_from_transport is the expected value for
355
a given client_base and transport_base.
357
client_medium = medium.SmartClientMedium(client_base)
358
transport = get_transport(transport_base)
359
result = client_medium.remote_path_from_transport(transport)
360
self.assertEqual(expected, result)
362
def test_remote_path_from_transport(self):
363
"""SmartClientMedium.remote_path_from_transport calculates a URL for
364
the given transport relative to the root of the client base URL.
366
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
367
self.assertRemotePath(
368
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
370
def assertRemotePathHTTP(self, expected, transport_base, relpath):
371
"""Assert that the result of
372
HttpTransportBase.remote_path_from_transport is the expected value for
373
a given transport_base and relpath of that transport. (Note that
374
HttpTransportBase is a subclass of SmartClientMedium)
376
base_transport = get_transport(transport_base)
377
client_medium = base_transport.get_smart_medium()
378
cloned_transport = base_transport.clone(relpath)
379
result = client_medium.remote_path_from_transport(cloned_transport)
380
self.assertEqual(expected, result)
382
def test_remote_path_from_transport_http(self):
383
"""Remote paths for HTTP transports are calculated differently to other
384
transports. They are just relative to the client base, not the root
385
directory of the host.
387
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
388
self.assertRemotePathHTTP(
389
'../xyz/', scheme + '//host/path', '../xyz/')
390
self.assertRemotePathHTTP(
391
'xyz/', scheme + '//host/path', 'xyz/')
394
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
395
"""Tests for the behaviour of client_medium.remote_is_at_least."""
397
def test_initially_unlimited(self):
398
"""A fresh medium assumes that the remote side supports all
401
client_medium = medium.SmartClientMedium('dummy base')
402
self.assertFalse(client_medium._is_remote_before((99, 99)))
404
def test__remember_remote_is_before(self):
405
"""Calling _remember_remote_is_before ratchets down the known remote
408
client_medium = medium.SmartClientMedium('dummy base')
409
# Mark the remote side as being less than 1.6. The remote side may
411
client_medium._remember_remote_is_before((1, 6))
412
self.assertTrue(client_medium._is_remote_before((1, 6)))
413
self.assertFalse(client_medium._is_remote_before((1, 5)))
414
# Calling _remember_remote_is_before again with a lower value works.
415
client_medium._remember_remote_is_before((1, 5))
416
self.assertTrue(client_medium._is_remote_before((1, 5)))
417
# You cannot call _remember_remote_is_before with a larger value.
419
AssertionError, client_medium._remember_remote_is_before, (1, 9))
422
class TestBzrDirCloningMetaDir(TestRemote):
424
def test_backwards_compat(self):
425
self.setup_smart_server_with_call_log()
426
a_dir = self.make_bzrdir('.')
427
self.reset_smart_call_log()
428
verb = 'BzrDir.cloning_metadir'
429
self.disable_verb(verb)
430
format = a_dir.cloning_metadir()
431
call_count = len([call for call in self.hpss_calls if
432
call.call.method == verb])
433
self.assertEqual(1, call_count)
435
def test_branch_reference(self):
436
transport = self.get_transport('quack')
437
referenced = self.make_branch('referenced')
438
expected = referenced.bzrdir.cloning_metadir()
439
client = FakeClient(transport.base)
440
client.add_expected_call(
441
'BzrDir.cloning_metadir', ('quack/', 'False'),
442
'error', ('BranchReference',)),
443
client.add_expected_call(
444
'BzrDir.open_branchV2', ('quack/',),
445
'success', ('ref', self.get_url('referenced'))),
446
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
448
result = a_bzrdir.cloning_metadir()
449
# We should have got a control dir matching the referenced branch.
450
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
451
self.assertEqual(expected._repository_format, result._repository_format)
452
self.assertEqual(expected._branch_format, result._branch_format)
453
self.assertFinished(client)
455
def test_current_server(self):
456
transport = self.get_transport('.')
457
transport = transport.clone('quack')
458
self.make_bzrdir('quack')
459
client = FakeClient(transport.base)
460
reference_bzrdir_format = bzrdir.format_registry.get('default')()
461
control_name = reference_bzrdir_format.network_name()
462
client.add_expected_call(
463
'BzrDir.cloning_metadir', ('quack/', 'False'),
464
'success', (control_name, '', ('branch', ''))),
465
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
467
result = a_bzrdir.cloning_metadir()
468
# We should have got a reference control dir with default branch and
469
# repository formats.
470
# This pokes a little, just to be sure.
471
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
472
self.assertEqual(None, result._repository_format)
473
self.assertEqual(None, result._branch_format)
474
self.assertFinished(client)
477
class TestBzrDirOpenBranch(TestRemote):
479
def test_backwards_compat(self):
480
self.setup_smart_server_with_call_log()
481
self.make_branch('.')
482
a_dir = BzrDir.open(self.get_url('.'))
483
self.reset_smart_call_log()
484
verb = 'BzrDir.open_branchV2'
485
self.disable_verb(verb)
486
format = a_dir.open_branch()
487
call_count = len([call for call in self.hpss_calls if
488
call.call.method == verb])
489
self.assertEqual(1, call_count)
491
def test_branch_present(self):
492
reference_format = self.get_repo_format()
493
network_name = reference_format.network_name()
494
branch_network_name = self.get_branch_format().network_name()
495
transport = MemoryTransport()
496
transport.mkdir('quack')
497
transport = transport.clone('quack')
498
client = FakeClient(transport.base)
499
client.add_expected_call(
500
'BzrDir.open_branchV2', ('quack/',),
501
'success', ('branch', branch_network_name))
502
client.add_expected_call(
503
'BzrDir.find_repositoryV3', ('quack/',),
504
'success', ('ok', '', 'no', 'no', 'no', network_name))
505
client.add_expected_call(
506
'Branch.get_stacked_on_url', ('quack/',),
507
'error', ('NotStacked',))
508
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
510
result = bzrdir.open_branch()
511
self.assertIsInstance(result, RemoteBranch)
512
self.assertEqual(bzrdir, result.bzrdir)
513
self.assertFinished(client)
515
def test_branch_missing(self):
516
transport = MemoryTransport()
517
transport.mkdir('quack')
518
transport = transport.clone('quack')
519
client = FakeClient(transport.base)
520
client.add_error_response('nobranch')
521
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
523
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
525
[('call', 'BzrDir.open_branchV2', ('quack/',))],
528
def test__get_tree_branch(self):
529
# _get_tree_branch is a form of open_branch, but it should only ask for
530
# branch opening, not any other network requests.
533
calls.append("Called")
535
transport = MemoryTransport()
536
# no requests on the network - catches other api calls being made.
537
client = FakeClient(transport.base)
538
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
540
# patch the open_branch call to record that it was called.
541
bzrdir.open_branch = open_branch
542
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
543
self.assertEqual(["Called"], calls)
544
self.assertEqual([], client._calls)
546
def test_url_quoting_of_path(self):
547
# Relpaths on the wire should not be URL-escaped. So "~" should be
548
# transmitted as "~", not "%7E".
549
transport = RemoteTCPTransport('bzr://localhost/~hello/')
550
client = FakeClient(transport.base)
551
reference_format = self.get_repo_format()
552
network_name = reference_format.network_name()
553
branch_network_name = self.get_branch_format().network_name()
554
client.add_expected_call(
555
'BzrDir.open_branchV2', ('~hello/',),
556
'success', ('branch', branch_network_name))
557
client.add_expected_call(
558
'BzrDir.find_repositoryV3', ('~hello/',),
559
'success', ('ok', '', 'no', 'no', 'no', network_name))
560
client.add_expected_call(
561
'Branch.get_stacked_on_url', ('~hello/',),
562
'error', ('NotStacked',))
563
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
565
result = bzrdir.open_branch()
566
self.assertFinished(client)
568
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
569
reference_format = self.get_repo_format()
570
network_name = reference_format.network_name()
571
transport = MemoryTransport()
572
transport.mkdir('quack')
573
transport = transport.clone('quack')
575
rich_response = 'yes'
579
subtree_response = 'yes'
581
subtree_response = 'no'
582
client = FakeClient(transport.base)
583
client.add_success_response(
584
'ok', '', rich_response, subtree_response, external_lookup,
586
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
588
result = bzrdir.open_repository()
590
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
592
self.assertIsInstance(result, RemoteRepository)
593
self.assertEqual(bzrdir, result.bzrdir)
594
self.assertEqual(rich_root, result._format.rich_root_data)
595
self.assertEqual(subtrees, result._format.supports_tree_reference)
597
def test_open_repository_sets_format_attributes(self):
598
self.check_open_repository(True, True)
599
self.check_open_repository(False, True)
600
self.check_open_repository(True, False)
601
self.check_open_repository(False, False)
602
self.check_open_repository(False, False, 'yes')
604
def test_old_server(self):
605
"""RemoteBzrDirFormat should fail to probe if the server version is too
608
self.assertRaises(errors.NotBranchError,
609
RemoteBzrDirFormat.probe_transport, OldServerTransport())
612
class TestBzrDirCreateBranch(TestRemote):
614
def test_backwards_compat(self):
615
self.setup_smart_server_with_call_log()
616
repo = self.make_repository('.')
617
self.reset_smart_call_log()
618
self.disable_verb('BzrDir.create_branch')
619
branch = repo.bzrdir.create_branch()
620
create_branch_call_count = len([call for call in self.hpss_calls if
621
call.call.method == 'BzrDir.create_branch'])
622
self.assertEqual(1, create_branch_call_count)
624
def test_current_server(self):
625
transport = self.get_transport('.')
626
transport = transport.clone('quack')
627
self.make_repository('quack')
628
client = FakeClient(transport.base)
629
reference_bzrdir_format = bzrdir.format_registry.get('default')()
630
reference_format = reference_bzrdir_format.get_branch_format()
631
network_name = reference_format.network_name()
632
reference_repo_fmt = reference_bzrdir_format.repository_format
633
reference_repo_name = reference_repo_fmt.network_name()
634
client.add_expected_call(
635
'BzrDir.create_branch', ('quack/', network_name),
636
'success', ('ok', network_name, '', 'no', 'no', 'yes',
637
reference_repo_name))
638
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
640
branch = a_bzrdir.create_branch()
641
# We should have got a remote branch
642
self.assertIsInstance(branch, remote.RemoteBranch)
643
# its format should have the settings from the response
644
format = branch._format
645
self.assertEqual(network_name, format.network_name())
648
class TestBzrDirCreateRepository(TestRemote):
650
def test_backwards_compat(self):
651
self.setup_smart_server_with_call_log()
652
bzrdir = self.make_bzrdir('.')
653
self.reset_smart_call_log()
654
self.disable_verb('BzrDir.create_repository')
655
repo = bzrdir.create_repository()
656
create_repo_call_count = len([call for call in self.hpss_calls if
657
call.call.method == 'BzrDir.create_repository'])
658
self.assertEqual(1, create_repo_call_count)
660
def test_current_server(self):
661
transport = self.get_transport('.')
662
transport = transport.clone('quack')
663
self.make_bzrdir('quack')
664
client = FakeClient(transport.base)
665
reference_bzrdir_format = bzrdir.format_registry.get('default')()
666
reference_format = reference_bzrdir_format.repository_format
667
network_name = reference_format.network_name()
668
client.add_expected_call(
669
'BzrDir.create_repository', ('quack/',
670
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
672
'success', ('ok', 'yes', 'yes', 'yes', network_name))
673
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
675
repo = a_bzrdir.create_repository()
676
# We should have got a remote repository
677
self.assertIsInstance(repo, remote.RemoteRepository)
678
# its format should have the settings from the response
679
format = repo._format
680
self.assertTrue(format.rich_root_data)
681
self.assertTrue(format.supports_tree_reference)
682
self.assertTrue(format.supports_external_lookups)
683
self.assertEqual(network_name, format.network_name())
686
class TestBzrDirOpenRepository(TestRemote):
688
def test_backwards_compat_1_2_3(self):
689
# fallback all the way to the first version.
690
reference_format = self.get_repo_format()
691
network_name = reference_format.network_name()
692
server_url = 'bzr://example.com/'
693
self.permit_url(server_url)
694
client = FakeClient(server_url)
695
client.add_unknown_method_response('BzrDir.find_repositoryV3')
696
client.add_unknown_method_response('BzrDir.find_repositoryV2')
697
client.add_success_response('ok', '', 'no', 'no')
698
# A real repository instance will be created to determine the network
700
client.add_success_response_with_body(
701
"Bazaar-NG meta directory, format 1\n", 'ok')
702
client.add_success_response_with_body(
703
reference_format.get_format_string(), 'ok')
704
# PackRepository wants to do a stat
705
client.add_success_response('stat', '0', '65535')
706
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
708
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
710
repo = bzrdir.open_repository()
712
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
713
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
714
('call', 'BzrDir.find_repository', ('quack/',)),
715
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
716
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
717
('call', 'stat', ('/quack/.bzr/repository',)),
720
self.assertEqual(network_name, repo._format.network_name())
722
def test_backwards_compat_2(self):
723
# fallback to find_repositoryV2
724
reference_format = self.get_repo_format()
725
network_name = reference_format.network_name()
726
server_url = 'bzr://example.com/'
727
self.permit_url(server_url)
728
client = FakeClient(server_url)
729
client.add_unknown_method_response('BzrDir.find_repositoryV3')
730
client.add_success_response('ok', '', 'no', 'no', 'no')
731
# A real repository instance will be created to determine the network
733
client.add_success_response_with_body(
734
"Bazaar-NG meta directory, format 1\n", 'ok')
735
client.add_success_response_with_body(
736
reference_format.get_format_string(), 'ok')
737
# PackRepository wants to do a stat
738
client.add_success_response('stat', '0', '65535')
739
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
741
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
743
repo = bzrdir.open_repository()
745
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
746
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
747
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
748
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
749
('call', 'stat', ('/quack/.bzr/repository',)),
752
self.assertEqual(network_name, repo._format.network_name())
754
def test_current_server(self):
755
reference_format = self.get_repo_format()
756
network_name = reference_format.network_name()
757
transport = MemoryTransport()
758
transport.mkdir('quack')
759
transport = transport.clone('quack')
760
client = FakeClient(transport.base)
761
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
762
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
764
repo = bzrdir.open_repository()
766
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
768
self.assertEqual(network_name, repo._format.network_name())
771
class TestBzrDirFormatInitializeEx(TestRemote):
773
def test_success(self):
774
"""Simple test for typical successful call."""
775
fmt = bzrdir.RemoteBzrDirFormat()
776
default_format_name = BzrDirFormat.get_default_format().network_name()
777
transport = self.get_transport()
778
client = FakeClient(transport.base)
779
client.add_expected_call(
780
'BzrDirFormat.initialize_ex_1.16',
781
(default_format_name, 'path', 'False', 'False', 'False', '',
782
'', '', '', 'False'),
784
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
785
'bzrdir fmt', 'False', '', '', 'repo lock token'))
786
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
787
# it's currently hard to test that without supplying a real remote
788
# transport connected to a real server.
789
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
790
transport, False, False, False, None, None, None, None, False)
791
self.assertFinished(client)
793
def test_error(self):
794
"""Error responses are translated, e.g. 'PermissionDenied' raises the
795
corresponding error from the client.
797
fmt = bzrdir.RemoteBzrDirFormat()
798
default_format_name = BzrDirFormat.get_default_format().network_name()
799
transport = self.get_transport()
800
client = FakeClient(transport.base)
801
client.add_expected_call(
802
'BzrDirFormat.initialize_ex_1.16',
803
(default_format_name, 'path', 'False', 'False', 'False', '',
804
'', '', '', 'False'),
806
('PermissionDenied', 'path', 'extra info'))
807
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
808
# it's currently hard to test that without supplying a real remote
809
# transport connected to a real server.
810
err = self.assertRaises(errors.PermissionDenied,
811
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
812
False, False, False, None, None, None, None, False)
813
self.assertEqual('path', err.path)
814
self.assertEqual(': extra info', err.extra)
815
self.assertFinished(client)
817
def test_error_from_real_server(self):
818
"""Integration test for error translation."""
819
transport = self.make_smart_server('foo')
820
transport = transport.clone('no-such-path')
821
fmt = bzrdir.RemoteBzrDirFormat()
822
err = self.assertRaises(errors.NoSuchFile,
823
fmt.initialize_on_transport_ex, transport, create_prefix=False)
826
class OldSmartClient(object):
827
"""A fake smart client for test_old_version that just returns a version one
828
response to the 'hello' (query version) command.
831
def get_request(self):
832
input_file = StringIO('ok\x011\n')
833
output_file = StringIO()
834
client_medium = medium.SmartSimplePipesClientMedium(
835
input_file, output_file)
836
return medium.SmartClientStreamMediumRequest(client_medium)
838
def protocol_version(self):
842
class OldServerTransport(object):
843
"""A fake transport for test_old_server that reports it's smart server
844
protocol version as version one.
850
def get_smart_client(self):
851
return OldSmartClient()
854
class RemoteBzrDirTestCase(TestRemote):
856
def make_remote_bzrdir(self, transport, client):
857
"""Make a RemotebzrDir using 'client' as the _client."""
858
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
862
class RemoteBranchTestCase(RemoteBzrDirTestCase):
864
def lock_remote_branch(self, branch):
865
"""Trick a RemoteBranch into thinking it is locked."""
866
branch._lock_mode = 'w'
867
branch._lock_count = 2
868
branch._lock_token = 'branch token'
869
branch._repo_lock_token = 'repo token'
870
branch.repository._lock_mode = 'w'
871
branch.repository._lock_count = 2
872
branch.repository._lock_token = 'repo token'
874
def make_remote_branch(self, transport, client):
875
"""Make a RemoteBranch using 'client' as its _SmartClient.
877
A RemoteBzrDir and RemoteRepository will also be created to fill out
878
the RemoteBranch, albeit with stub values for some of their attributes.
880
# we do not want bzrdir to make any remote calls, so use False as its
881
# _client. If it tries to make a remote call, this will fail
883
bzrdir = self.make_remote_bzrdir(transport, False)
884
repo = RemoteRepository(bzrdir, None, _client=client)
885
branch_format = self.get_branch_format()
886
format = RemoteBranchFormat(network_name=branch_format.network_name())
887
return RemoteBranch(bzrdir, repo, _client=client, format=format)
890
class TestBranchGetParent(RemoteBranchTestCase):
892
def test_no_parent(self):
893
# in an empty branch we decode the response properly
894
transport = MemoryTransport()
895
client = FakeClient(transport.base)
896
client.add_expected_call(
897
'Branch.get_stacked_on_url', ('quack/',),
898
'error', ('NotStacked',))
899
client.add_expected_call(
900
'Branch.get_parent', ('quack/',),
902
transport.mkdir('quack')
903
transport = transport.clone('quack')
904
branch = self.make_remote_branch(transport, client)
905
result = branch.get_parent()
906
self.assertFinished(client)
907
self.assertEqual(None, result)
909
def test_parent_relative(self):
910
transport = MemoryTransport()
911
client = FakeClient(transport.base)
912
client.add_expected_call(
913
'Branch.get_stacked_on_url', ('kwaak/',),
914
'error', ('NotStacked',))
915
client.add_expected_call(
916
'Branch.get_parent', ('kwaak/',),
917
'success', ('../foo/',))
918
transport.mkdir('kwaak')
919
transport = transport.clone('kwaak')
920
branch = self.make_remote_branch(transport, client)
921
result = branch.get_parent()
922
self.assertEqual(transport.clone('../foo').base, result)
924
def test_parent_absolute(self):
925
transport = MemoryTransport()
926
client = FakeClient(transport.base)
927
client.add_expected_call(
928
'Branch.get_stacked_on_url', ('kwaak/',),
929
'error', ('NotStacked',))
930
client.add_expected_call(
931
'Branch.get_parent', ('kwaak/',),
932
'success', ('http://foo/',))
933
transport.mkdir('kwaak')
934
transport = transport.clone('kwaak')
935
branch = self.make_remote_branch(transport, client)
936
result = branch.get_parent()
937
self.assertEqual('http://foo/', result)
938
self.assertFinished(client)
941
class TestBranchSetParentLocation(RemoteBranchTestCase):
943
def test_no_parent(self):
944
# We call the verb when setting parent to None
945
transport = MemoryTransport()
946
client = FakeClient(transport.base)
947
client.add_expected_call(
948
'Branch.get_stacked_on_url', ('quack/',),
949
'error', ('NotStacked',))
950
client.add_expected_call(
951
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
953
transport.mkdir('quack')
954
transport = transport.clone('quack')
955
branch = self.make_remote_branch(transport, client)
956
branch._lock_token = 'b'
957
branch._repo_lock_token = 'r'
958
branch._set_parent_location(None)
959
self.assertFinished(client)
961
def test_parent(self):
962
transport = MemoryTransport()
963
client = FakeClient(transport.base)
964
client.add_expected_call(
965
'Branch.get_stacked_on_url', ('kwaak/',),
966
'error', ('NotStacked',))
967
client.add_expected_call(
968
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
970
transport.mkdir('kwaak')
971
transport = transport.clone('kwaak')
972
branch = self.make_remote_branch(transport, client)
973
branch._lock_token = 'b'
974
branch._repo_lock_token = 'r'
975
branch._set_parent_location('foo')
976
self.assertFinished(client)
978
def test_backwards_compat(self):
979
self.setup_smart_server_with_call_log()
980
branch = self.make_branch('.')
981
self.reset_smart_call_log()
982
verb = 'Branch.set_parent_location'
983
self.disable_verb(verb)
984
branch.set_parent('http://foo/')
985
self.assertLength(12, self.hpss_calls)
988
class TestBranchGetTagsBytes(RemoteBranchTestCase):
990
def test_backwards_compat(self):
991
self.setup_smart_server_with_call_log()
992
branch = self.make_branch('.')
993
self.reset_smart_call_log()
994
verb = 'Branch.get_tags_bytes'
995
self.disable_verb(verb)
996
branch.tags.get_tag_dict()
997
call_count = len([call for call in self.hpss_calls if
998
call.call.method == verb])
999
self.assertEqual(1, call_count)
1001
def test_trivial(self):
1002
transport = MemoryTransport()
1003
client = FakeClient(transport.base)
1004
client.add_expected_call(
1005
'Branch.get_stacked_on_url', ('quack/',),
1006
'error', ('NotStacked',))
1007
client.add_expected_call(
1008
'Branch.get_tags_bytes', ('quack/',),
1010
transport.mkdir('quack')
1011
transport = transport.clone('quack')
1012
branch = self.make_remote_branch(transport, client)
1013
result = branch.tags.get_tag_dict()
1014
self.assertFinished(client)
1015
self.assertEqual({}, result)
1018
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1020
def test_trivial(self):
1021
transport = MemoryTransport()
1022
client = FakeClient(transport.base)
1023
client.add_expected_call(
1024
'Branch.get_stacked_on_url', ('quack/',),
1025
'error', ('NotStacked',))
1026
client.add_expected_call(
1027
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1029
transport.mkdir('quack')
1030
transport = transport.clone('quack')
1031
branch = self.make_remote_branch(transport, client)
1032
self.lock_remote_branch(branch)
1033
branch._set_tags_bytes('tags bytes')
1034
self.assertFinished(client)
1035
self.assertEqual('tags bytes', client._calls[-1][-1])
1037
def test_backwards_compatible(self):
1038
transport = MemoryTransport()
1039
client = FakeClient(transport.base)
1040
client.add_expected_call(
1041
'Branch.get_stacked_on_url', ('quack/',),
1042
'error', ('NotStacked',))
1043
client.add_expected_call(
1044
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1045
'unknown', ('Branch.set_tags_bytes',))
1046
transport.mkdir('quack')
1047
transport = transport.clone('quack')
1048
branch = self.make_remote_branch(transport, client)
1049
self.lock_remote_branch(branch)
1050
class StubRealBranch(object):
1053
def _set_tags_bytes(self, bytes):
1054
self.calls.append(('set_tags_bytes', bytes))
1055
real_branch = StubRealBranch()
1056
branch._real_branch = real_branch
1057
branch._set_tags_bytes('tags bytes')
1058
# Call a second time, to exercise the 'remote version already inferred'
1060
branch._set_tags_bytes('tags bytes')
1061
self.assertFinished(client)
1063
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1066
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1068
def test_empty_branch(self):
1069
# in an empty branch we decode the response properly
1070
transport = MemoryTransport()
1071
client = FakeClient(transport.base)
1072
client.add_expected_call(
1073
'Branch.get_stacked_on_url', ('quack/',),
1074
'error', ('NotStacked',))
1075
client.add_expected_call(
1076
'Branch.last_revision_info', ('quack/',),
1077
'success', ('ok', '0', 'null:'))
1078
transport.mkdir('quack')
1079
transport = transport.clone('quack')
1080
branch = self.make_remote_branch(transport, client)
1081
result = branch.last_revision_info()
1082
self.assertFinished(client)
1083
self.assertEqual((0, NULL_REVISION), result)
1085
def test_non_empty_branch(self):
1086
# in a non-empty branch we also decode the response properly
1087
revid = u'\xc8'.encode('utf8')
1088
transport = MemoryTransport()
1089
client = FakeClient(transport.base)
1090
client.add_expected_call(
1091
'Branch.get_stacked_on_url', ('kwaak/',),
1092
'error', ('NotStacked',))
1093
client.add_expected_call(
1094
'Branch.last_revision_info', ('kwaak/',),
1095
'success', ('ok', '2', revid))
1096
transport.mkdir('kwaak')
1097
transport = transport.clone('kwaak')
1098
branch = self.make_remote_branch(transport, client)
1099
result = branch.last_revision_info()
1100
self.assertEqual((2, revid), result)
1103
class TestBranch_get_stacked_on_url(TestRemote):
1104
"""Test Branch._get_stacked_on_url rpc"""
1106
def test_get_stacked_on_invalid_url(self):
1107
# test that asking for a stacked on url the server can't access works.
1108
# This isn't perfect, but then as we're in the same process there
1109
# really isn't anything we can do to be 100% sure that the server
1110
# doesn't just open in - this test probably needs to be rewritten using
1111
# a spawn()ed server.
1112
stacked_branch = self.make_branch('stacked', format='1.9')
1113
memory_branch = self.make_branch('base', format='1.9')
1114
vfs_url = self.get_vfs_only_url('base')
1115
stacked_branch.set_stacked_on_url(vfs_url)
1116
transport = stacked_branch.bzrdir.root_transport
1117
client = FakeClient(transport.base)
1118
client.add_expected_call(
1119
'Branch.get_stacked_on_url', ('stacked/',),
1120
'success', ('ok', vfs_url))
1121
# XXX: Multiple calls are bad, this second call documents what is
1123
client.add_expected_call(
1124
'Branch.get_stacked_on_url', ('stacked/',),
1125
'success', ('ok', vfs_url))
1126
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1128
repo_fmt = remote.RemoteRepositoryFormat()
1129
repo_fmt._custom_format = stacked_branch.repository._format
1130
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1132
result = branch.get_stacked_on_url()
1133
self.assertEqual(vfs_url, result)
1135
def test_backwards_compatible(self):
1136
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1137
base_branch = self.make_branch('base', format='1.6')
1138
stacked_branch = self.make_branch('stacked', format='1.6')
1139
stacked_branch.set_stacked_on_url('../base')
1140
client = FakeClient(self.get_url())
1141
branch_network_name = self.get_branch_format().network_name()
1142
client.add_expected_call(
1143
'BzrDir.open_branchV2', ('stacked/',),
1144
'success', ('branch', branch_network_name))
1145
client.add_expected_call(
1146
'BzrDir.find_repositoryV3', ('stacked/',),
1147
'success', ('ok', '', 'no', 'no', 'yes',
1148
stacked_branch.repository._format.network_name()))
1149
# called twice, once from constructor and then again by us
1150
client.add_expected_call(
1151
'Branch.get_stacked_on_url', ('stacked/',),
1152
'unknown', ('Branch.get_stacked_on_url',))
1153
client.add_expected_call(
1154
'Branch.get_stacked_on_url', ('stacked/',),
1155
'unknown', ('Branch.get_stacked_on_url',))
1156
# this will also do vfs access, but that goes direct to the transport
1157
# and isn't seen by the FakeClient.
1158
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1159
remote.RemoteBzrDirFormat(), _client=client)
1160
branch = bzrdir.open_branch()
1161
result = branch.get_stacked_on_url()
1162
self.assertEqual('../base', result)
1163
self.assertFinished(client)
1164
# it's in the fallback list both for the RemoteRepository and its vfs
1166
self.assertEqual(1, len(branch.repository._fallback_repositories))
1168
len(branch.repository._real_repository._fallback_repositories))
1170
def test_get_stacked_on_real_branch(self):
1171
base_branch = self.make_branch('base', format='1.6')
1172
stacked_branch = self.make_branch('stacked', format='1.6')
1173
stacked_branch.set_stacked_on_url('../base')
1174
reference_format = self.get_repo_format()
1175
network_name = reference_format.network_name()
1176
client = FakeClient(self.get_url())
1177
branch_network_name = self.get_branch_format().network_name()
1178
client.add_expected_call(
1179
'BzrDir.open_branchV2', ('stacked/',),
1180
'success', ('branch', branch_network_name))
1181
client.add_expected_call(
1182
'BzrDir.find_repositoryV3', ('stacked/',),
1183
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1184
# called twice, once from constructor and then again by us
1185
client.add_expected_call(
1186
'Branch.get_stacked_on_url', ('stacked/',),
1187
'success', ('ok', '../base'))
1188
client.add_expected_call(
1189
'Branch.get_stacked_on_url', ('stacked/',),
1190
'success', ('ok', '../base'))
1191
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1192
remote.RemoteBzrDirFormat(), _client=client)
1193
branch = bzrdir.open_branch()
1194
result = branch.get_stacked_on_url()
1195
self.assertEqual('../base', result)
1196
self.assertFinished(client)
1197
# it's in the fallback list both for the RemoteRepository.
1198
self.assertEqual(1, len(branch.repository._fallback_repositories))
1199
# And we haven't had to construct a real repository.
1200
self.assertEqual(None, branch.repository._real_repository)
1203
class TestBranchSetLastRevision(RemoteBranchTestCase):
1205
def test_set_empty(self):
1206
# set_revision_history([]) is translated to calling
1207
# Branch.set_last_revision(path, '') on the wire.
1208
transport = MemoryTransport()
1209
transport.mkdir('branch')
1210
transport = transport.clone('branch')
1212
client = FakeClient(transport.base)
1213
client.add_expected_call(
1214
'Branch.get_stacked_on_url', ('branch/',),
1215
'error', ('NotStacked',))
1216
client.add_expected_call(
1217
'Branch.lock_write', ('branch/', '', ''),
1218
'success', ('ok', 'branch token', 'repo token'))
1219
client.add_expected_call(
1220
'Branch.last_revision_info',
1222
'success', ('ok', '0', 'null:'))
1223
client.add_expected_call(
1224
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1226
client.add_expected_call(
1227
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1229
branch = self.make_remote_branch(transport, client)
1230
# This is a hack to work around the problem that RemoteBranch currently
1231
# unnecessarily invokes _ensure_real upon a call to lock_write.
1232
branch._ensure_real = lambda: None
1234
result = branch.set_revision_history([])
1236
self.assertEqual(None, result)
1237
self.assertFinished(client)
1239
def test_set_nonempty(self):
1240
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1241
# Branch.set_last_revision(path, rev-idN) on the wire.
1242
transport = MemoryTransport()
1243
transport.mkdir('branch')
1244
transport = transport.clone('branch')
1246
client = FakeClient(transport.base)
1247
client.add_expected_call(
1248
'Branch.get_stacked_on_url', ('branch/',),
1249
'error', ('NotStacked',))
1250
client.add_expected_call(
1251
'Branch.lock_write', ('branch/', '', ''),
1252
'success', ('ok', 'branch token', 'repo token'))
1253
client.add_expected_call(
1254
'Branch.last_revision_info',
1256
'success', ('ok', '0', 'null:'))
1258
encoded_body = bz2.compress('\n'.join(lines))
1259
client.add_success_response_with_body(encoded_body, 'ok')
1260
client.add_expected_call(
1261
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1263
client.add_expected_call(
1264
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1266
branch = self.make_remote_branch(transport, client)
1267
# This is a hack to work around the problem that RemoteBranch currently
1268
# unnecessarily invokes _ensure_real upon a call to lock_write.
1269
branch._ensure_real = lambda: None
1270
# Lock the branch, reset the record of remote calls.
1272
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1274
self.assertEqual(None, result)
1275
self.assertFinished(client)
1277
def test_no_such_revision(self):
1278
transport = MemoryTransport()
1279
transport.mkdir('branch')
1280
transport = transport.clone('branch')
1281
# A response of 'NoSuchRevision' is translated into an exception.
1282
client = FakeClient(transport.base)
1283
client.add_expected_call(
1284
'Branch.get_stacked_on_url', ('branch/',),
1285
'error', ('NotStacked',))
1286
client.add_expected_call(
1287
'Branch.lock_write', ('branch/', '', ''),
1288
'success', ('ok', 'branch token', 'repo token'))
1289
client.add_expected_call(
1290
'Branch.last_revision_info',
1292
'success', ('ok', '0', 'null:'))
1293
# get_graph calls to construct the revision history, for the set_rh
1296
encoded_body = bz2.compress('\n'.join(lines))
1297
client.add_success_response_with_body(encoded_body, 'ok')
1298
client.add_expected_call(
1299
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1300
'error', ('NoSuchRevision', 'rev-id'))
1301
client.add_expected_call(
1302
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1305
branch = self.make_remote_branch(transport, client)
1308
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1310
self.assertFinished(client)
1312
def test_tip_change_rejected(self):
1313
"""TipChangeRejected responses cause a TipChangeRejected exception to
1316
transport = MemoryTransport()
1317
transport.mkdir('branch')
1318
transport = transport.clone('branch')
1319
client = FakeClient(transport.base)
1320
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1321
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1322
client.add_expected_call(
1323
'Branch.get_stacked_on_url', ('branch/',),
1324
'error', ('NotStacked',))
1325
client.add_expected_call(
1326
'Branch.lock_write', ('branch/', '', ''),
1327
'success', ('ok', 'branch token', 'repo token'))
1328
client.add_expected_call(
1329
'Branch.last_revision_info',
1331
'success', ('ok', '0', 'null:'))
1333
encoded_body = bz2.compress('\n'.join(lines))
1334
client.add_success_response_with_body(encoded_body, 'ok')
1335
client.add_expected_call(
1336
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1337
'error', ('TipChangeRejected', rejection_msg_utf8))
1338
client.add_expected_call(
1339
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1341
branch = self.make_remote_branch(transport, client)
1342
branch._ensure_real = lambda: None
1344
# The 'TipChangeRejected' error response triggered by calling
1345
# set_revision_history causes a TipChangeRejected exception.
1346
err = self.assertRaises(
1347
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1348
# The UTF-8 message from the response has been decoded into a unicode
1350
self.assertIsInstance(err.msg, unicode)
1351
self.assertEqual(rejection_msg_unicode, err.msg)
1353
self.assertFinished(client)
1356
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1358
def test_set_last_revision_info(self):
1359
# set_last_revision_info(num, 'rev-id') is translated to calling
1360
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1361
transport = MemoryTransport()
1362
transport.mkdir('branch')
1363
transport = transport.clone('branch')
1364
client = FakeClient(transport.base)
1365
# get_stacked_on_url
1366
client.add_error_response('NotStacked')
1368
client.add_success_response('ok', 'branch token', 'repo token')
1369
# query the current revision
1370
client.add_success_response('ok', '0', 'null:')
1372
client.add_success_response('ok')
1374
client.add_success_response('ok')
1376
branch = self.make_remote_branch(transport, client)
1377
# Lock the branch, reset the record of remote calls.
1380
result = branch.set_last_revision_info(1234, 'a-revision-id')
1382
[('call', 'Branch.last_revision_info', ('branch/',)),
1383
('call', 'Branch.set_last_revision_info',
1384
('branch/', 'branch token', 'repo token',
1385
'1234', 'a-revision-id'))],
1387
self.assertEqual(None, result)
1389
def test_no_such_revision(self):
1390
# A response of 'NoSuchRevision' is translated into an exception.
1391
transport = MemoryTransport()
1392
transport.mkdir('branch')
1393
transport = transport.clone('branch')
1394
client = FakeClient(transport.base)
1395
# get_stacked_on_url
1396
client.add_error_response('NotStacked')
1398
client.add_success_response('ok', 'branch token', 'repo token')
1400
client.add_error_response('NoSuchRevision', 'revid')
1402
client.add_success_response('ok')
1404
branch = self.make_remote_branch(transport, client)
1405
# Lock the branch, reset the record of remote calls.
1410
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1413
def test_backwards_compatibility(self):
1414
"""If the server does not support the Branch.set_last_revision_info
1415
verb (which is new in 1.4), then the client falls back to VFS methods.
1417
# This test is a little messy. Unlike most tests in this file, it
1418
# doesn't purely test what a Remote* object sends over the wire, and
1419
# how it reacts to responses from the wire. It instead relies partly
1420
# on asserting that the RemoteBranch will call
1421
# self._real_branch.set_last_revision_info(...).
1423
# First, set up our RemoteBranch with a FakeClient that raises
1424
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1425
transport = MemoryTransport()
1426
transport.mkdir('branch')
1427
transport = transport.clone('branch')
1428
client = FakeClient(transport.base)
1429
client.add_expected_call(
1430
'Branch.get_stacked_on_url', ('branch/',),
1431
'error', ('NotStacked',))
1432
client.add_expected_call(
1433
'Branch.last_revision_info',
1435
'success', ('ok', '0', 'null:'))
1436
client.add_expected_call(
1437
'Branch.set_last_revision_info',
1438
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1439
'unknown', 'Branch.set_last_revision_info')
1441
branch = self.make_remote_branch(transport, client)
1442
class StubRealBranch(object):
1445
def set_last_revision_info(self, revno, revision_id):
1447
('set_last_revision_info', revno, revision_id))
1448
def _clear_cached_state(self):
1450
real_branch = StubRealBranch()
1451
branch._real_branch = real_branch
1452
self.lock_remote_branch(branch)
1454
# Call set_last_revision_info, and verify it behaved as expected.
1455
result = branch.set_last_revision_info(1234, 'a-revision-id')
1457
[('set_last_revision_info', 1234, 'a-revision-id')],
1459
self.assertFinished(client)
1461
def test_unexpected_error(self):
1462
# If the server sends an error the client doesn't understand, it gets
1463
# turned into an UnknownErrorFromSmartServer, which is presented as a
1464
# non-internal error to the user.
1465
transport = MemoryTransport()
1466
transport.mkdir('branch')
1467
transport = transport.clone('branch')
1468
client = FakeClient(transport.base)
1469
# get_stacked_on_url
1470
client.add_error_response('NotStacked')
1472
client.add_success_response('ok', 'branch token', 'repo token')
1474
client.add_error_response('UnexpectedError')
1476
client.add_success_response('ok')
1478
branch = self.make_remote_branch(transport, client)
1479
# Lock the branch, reset the record of remote calls.
1483
err = self.assertRaises(
1484
errors.UnknownErrorFromSmartServer,
1485
branch.set_last_revision_info, 123, 'revid')
1486
self.assertEqual(('UnexpectedError',), err.error_tuple)
1489
def test_tip_change_rejected(self):
1490
"""TipChangeRejected responses cause a TipChangeRejected exception to
1493
transport = MemoryTransport()
1494
transport.mkdir('branch')
1495
transport = transport.clone('branch')
1496
client = FakeClient(transport.base)
1497
# get_stacked_on_url
1498
client.add_error_response('NotStacked')
1500
client.add_success_response('ok', 'branch token', 'repo token')
1502
client.add_error_response('TipChangeRejected', 'rejection message')
1504
client.add_success_response('ok')
1506
branch = self.make_remote_branch(transport, client)
1507
# Lock the branch, reset the record of remote calls.
1509
self.addCleanup(branch.unlock)
1512
# The 'TipChangeRejected' error response triggered by calling
1513
# set_last_revision_info causes a TipChangeRejected exception.
1514
err = self.assertRaises(
1515
errors.TipChangeRejected,
1516
branch.set_last_revision_info, 123, 'revid')
1517
self.assertEqual('rejection message', err.msg)
1520
class TestBranchGetSetConfig(RemoteBranchTestCase):
1522
def test_get_branch_conf(self):
1523
# in an empty branch we decode the response properly
1524
client = FakeClient()
1525
client.add_expected_call(
1526
'Branch.get_stacked_on_url', ('memory:///',),
1527
'error', ('NotStacked',),)
1528
client.add_success_response_with_body('# config file body', 'ok')
1529
transport = MemoryTransport()
1530
branch = self.make_remote_branch(transport, client)
1531
config = branch.get_config()
1532
config.has_explicit_nickname()
1534
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1535
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1538
def test_get_multi_line_branch_conf(self):
1539
# Make sure that multiple-line branch.conf files are supported
1541
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1542
client = FakeClient()
1543
client.add_expected_call(
1544
'Branch.get_stacked_on_url', ('memory:///',),
1545
'error', ('NotStacked',),)
1546
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1547
transport = MemoryTransport()
1548
branch = self.make_remote_branch(transport, client)
1549
config = branch.get_config()
1550
self.assertEqual(u'2', config.get_user_option('b'))
1552
def test_set_option(self):
1553
client = FakeClient()
1554
client.add_expected_call(
1555
'Branch.get_stacked_on_url', ('memory:///',),
1556
'error', ('NotStacked',),)
1557
client.add_expected_call(
1558
'Branch.lock_write', ('memory:///', '', ''),
1559
'success', ('ok', 'branch token', 'repo token'))
1560
client.add_expected_call(
1561
'Branch.set_config_option', ('memory:///', 'branch token',
1562
'repo token', 'foo', 'bar', ''),
1564
client.add_expected_call(
1565
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1567
transport = MemoryTransport()
1568
branch = self.make_remote_branch(transport, client)
1570
config = branch._get_config()
1571
config.set_option('foo', 'bar')
1573
self.assertFinished(client)
1575
def test_backwards_compat_set_option(self):
1576
self.setup_smart_server_with_call_log()
1577
branch = self.make_branch('.')
1578
verb = 'Branch.set_config_option'
1579
self.disable_verb(verb)
1581
self.addCleanup(branch.unlock)
1582
self.reset_smart_call_log()
1583
branch._get_config().set_option('value', 'name')
1584
self.assertLength(10, self.hpss_calls)
1585
self.assertEqual('value', branch._get_config().get_option('name'))
1588
class TestBranchLockWrite(RemoteBranchTestCase):
1590
def test_lock_write_unlockable(self):
1591
transport = MemoryTransport()
1592
client = FakeClient(transport.base)
1593
client.add_expected_call(
1594
'Branch.get_stacked_on_url', ('quack/',),
1595
'error', ('NotStacked',),)
1596
client.add_expected_call(
1597
'Branch.lock_write', ('quack/', '', ''),
1598
'error', ('UnlockableTransport',))
1599
transport.mkdir('quack')
1600
transport = transport.clone('quack')
1601
branch = self.make_remote_branch(transport, client)
1602
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1603
self.assertFinished(client)
1606
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1608
def test__get_config(self):
1609
client = FakeClient()
1610
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1611
transport = MemoryTransport()
1612
bzrdir = self.make_remote_bzrdir(transport, client)
1613
config = bzrdir.get_config()
1614
self.assertEqual('/', config.get_default_stack_on())
1616
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1619
def test_set_option_uses_vfs(self):
1620
self.setup_smart_server_with_call_log()
1621
bzrdir = self.make_bzrdir('.')
1622
self.reset_smart_call_log()
1623
config = bzrdir.get_config()
1624
config.set_default_stack_on('/')
1625
self.assertLength(3, self.hpss_calls)
1627
def test_backwards_compat_get_option(self):
1628
self.setup_smart_server_with_call_log()
1629
bzrdir = self.make_bzrdir('.')
1630
verb = 'BzrDir.get_config_file'
1631
self.disable_verb(verb)
1632
self.reset_smart_call_log()
1633
self.assertEqual(None,
1634
bzrdir._get_config().get_option('default_stack_on'))
1635
self.assertLength(3, self.hpss_calls)
1638
class TestTransportIsReadonly(tests.TestCase):
1640
def test_true(self):
1641
client = FakeClient()
1642
client.add_success_response('yes')
1643
transport = RemoteTransport('bzr://example.com/', medium=False,
1645
self.assertEqual(True, transport.is_readonly())
1647
[('call', 'Transport.is_readonly', ())],
1650
def test_false(self):
1651
client = FakeClient()
1652
client.add_success_response('no')
1653
transport = RemoteTransport('bzr://example.com/', medium=False,
1655
self.assertEqual(False, transport.is_readonly())
1657
[('call', 'Transport.is_readonly', ())],
1660
def test_error_from_old_server(self):
1661
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1663
Clients should treat it as a "no" response, because is_readonly is only
1664
advisory anyway (a transport could be read-write, but then the
1665
underlying filesystem could be readonly anyway).
1667
client = FakeClient()
1668
client.add_unknown_method_response('Transport.is_readonly')
1669
transport = RemoteTransport('bzr://example.com/', medium=False,
1671
self.assertEqual(False, transport.is_readonly())
1673
[('call', 'Transport.is_readonly', ())],
1677
class TestTransportMkdir(tests.TestCase):
1679
def test_permissiondenied(self):
1680
client = FakeClient()
1681
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1682
transport = RemoteTransport('bzr://example.com/', medium=False,
1684
exc = self.assertRaises(
1685
errors.PermissionDenied, transport.mkdir, 'client path')
1686
expected_error = errors.PermissionDenied('/client path', 'extra')
1687
self.assertEqual(expected_error, exc)
1690
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1692
def test_defaults_to_none(self):
1693
t = RemoteSSHTransport('bzr+ssh://example.com')
1694
self.assertIs(None, t._get_credentials()[0])
1696
def test_uses_authentication_config(self):
1697
conf = config.AuthenticationConfig()
1698
conf._get_config().update(
1699
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1702
t = RemoteSSHTransport('bzr+ssh://example.com')
1703
self.assertEqual('bar', t._get_credentials()[0])
1706
class TestRemoteRepository(TestRemote):
1707
"""Base for testing RemoteRepository protocol usage.
1709
These tests contain frozen requests and responses. We want any changes to
1710
what is sent or expected to be require a thoughtful update to these tests
1711
because they might break compatibility with different-versioned servers.
1714
def setup_fake_client_and_repository(self, transport_path):
1715
"""Create the fake client and repository for testing with.
1717
There's no real server here; we just have canned responses sent
1720
:param transport_path: Path below the root of the MemoryTransport
1721
where the repository will be created.
1723
transport = MemoryTransport()
1724
transport.mkdir(transport_path)
1725
client = FakeClient(transport.base)
1726
transport = transport.clone(transport_path)
1727
# we do not want bzrdir to make any remote calls
1728
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1730
repo = RemoteRepository(bzrdir, None, _client=client)
1734
class TestRepositoryFormat(TestRemoteRepository):
1736
def test_fast_delta(self):
1737
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1738
true_format = RemoteRepositoryFormat()
1739
true_format._network_name = true_name
1740
self.assertEqual(True, true_format.fast_deltas)
1741
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1742
false_format = RemoteRepositoryFormat()
1743
false_format._network_name = false_name
1744
self.assertEqual(False, false_format.fast_deltas)
1747
class TestRepositoryGatherStats(TestRemoteRepository):
1749
def test_revid_none(self):
1750
# ('ok',), body with revisions and size
1751
transport_path = 'quack'
1752
repo, client = self.setup_fake_client_and_repository(transport_path)
1753
client.add_success_response_with_body(
1754
'revisions: 2\nsize: 18\n', 'ok')
1755
result = repo.gather_stats(None)
1757
[('call_expecting_body', 'Repository.gather_stats',
1758
('quack/','','no'))],
1760
self.assertEqual({'revisions': 2, 'size': 18}, result)
1762
def test_revid_no_committers(self):
1763
# ('ok',), body without committers
1764
body = ('firstrev: 123456.300 3600\n'
1765
'latestrev: 654231.400 0\n'
1768
transport_path = 'quick'
1769
revid = u'\xc8'.encode('utf8')
1770
repo, client = self.setup_fake_client_and_repository(transport_path)
1771
client.add_success_response_with_body(body, 'ok')
1772
result = repo.gather_stats(revid)
1774
[('call_expecting_body', 'Repository.gather_stats',
1775
('quick/', revid, 'no'))],
1777
self.assertEqual({'revisions': 2, 'size': 18,
1778
'firstrev': (123456.300, 3600),
1779
'latestrev': (654231.400, 0),},
1782
def test_revid_with_committers(self):
1783
# ('ok',), body with committers
1784
body = ('committers: 128\n'
1785
'firstrev: 123456.300 3600\n'
1786
'latestrev: 654231.400 0\n'
1789
transport_path = 'buick'
1790
revid = u'\xc8'.encode('utf8')
1791
repo, client = self.setup_fake_client_and_repository(transport_path)
1792
client.add_success_response_with_body(body, 'ok')
1793
result = repo.gather_stats(revid, True)
1795
[('call_expecting_body', 'Repository.gather_stats',
1796
('buick/', revid, 'yes'))],
1798
self.assertEqual({'revisions': 2, 'size': 18,
1800
'firstrev': (123456.300, 3600),
1801
'latestrev': (654231.400, 0),},
1805
class TestRepositoryGetGraph(TestRemoteRepository):
1807
def test_get_graph(self):
1808
# get_graph returns a graph with a custom parents provider.
1809
transport_path = 'quack'
1810
repo, client = self.setup_fake_client_and_repository(transport_path)
1811
graph = repo.get_graph()
1812
self.assertNotEqual(graph._parents_provider, repo)
1815
class TestRepositoryGetParentMap(TestRemoteRepository):
1817
def test_get_parent_map_caching(self):
1818
# get_parent_map returns from cache until unlock()
1819
# setup a reponse with two revisions
1820
r1 = u'\u0e33'.encode('utf8')
1821
r2 = u'\u0dab'.encode('utf8')
1822
lines = [' '.join([r2, r1]), r1]
1823
encoded_body = bz2.compress('\n'.join(lines))
1825
transport_path = 'quack'
1826
repo, client = self.setup_fake_client_and_repository(transport_path)
1827
client.add_success_response_with_body(encoded_body, 'ok')
1828
client.add_success_response_with_body(encoded_body, 'ok')
1830
graph = repo.get_graph()
1831
parents = graph.get_parent_map([r2])
1832
self.assertEqual({r2: (r1,)}, parents)
1833
# locking and unlocking deeper should not reset
1836
parents = graph.get_parent_map([r1])
1837
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1839
[('call_with_body_bytes_expecting_body',
1840
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1844
# now we call again, and it should use the second response.
1846
graph = repo.get_graph()
1847
parents = graph.get_parent_map([r1])
1848
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1850
[('call_with_body_bytes_expecting_body',
1851
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1853
('call_with_body_bytes_expecting_body',
1854
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1860
def test_get_parent_map_reconnects_if_unknown_method(self):
1861
transport_path = 'quack'
1862
rev_id = 'revision-id'
1863
repo, client = self.setup_fake_client_and_repository(transport_path)
1864
client.add_unknown_method_response('Repository.get_parent_map')
1865
client.add_success_response_with_body(rev_id, 'ok')
1866
self.assertFalse(client._medium._is_remote_before((1, 2)))
1867
parents = repo.get_parent_map([rev_id])
1869
[('call_with_body_bytes_expecting_body',
1870
'Repository.get_parent_map', ('quack/', 'include-missing:',
1872
('disconnect medium',),
1873
('call_expecting_body', 'Repository.get_revision_graph',
1876
# The medium is now marked as being connected to an older server
1877
self.assertTrue(client._medium._is_remote_before((1, 2)))
1878
self.assertEqual({rev_id: ('null:',)}, parents)
1880
def test_get_parent_map_fallback_parentless_node(self):
1881
"""get_parent_map falls back to get_revision_graph on old servers. The
1882
results from get_revision_graph are tweaked to match the get_parent_map
1885
Specifically, a {key: ()} result from get_revision_graph means "no
1886
parents" for that key, which in get_parent_map results should be
1887
represented as {key: ('null:',)}.
1889
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1891
rev_id = 'revision-id'
1892
transport_path = 'quack'
1893
repo, client = self.setup_fake_client_and_repository(transport_path)
1894
client.add_success_response_with_body(rev_id, 'ok')
1895
client._medium._remember_remote_is_before((1, 2))
1896
parents = repo.get_parent_map([rev_id])
1898
[('call_expecting_body', 'Repository.get_revision_graph',
1901
self.assertEqual({rev_id: ('null:',)}, parents)
1903
def test_get_parent_map_unexpected_response(self):
1904
repo, client = self.setup_fake_client_and_repository('path')
1905
client.add_success_response('something unexpected!')
1907
errors.UnexpectedSmartServerResponse,
1908
repo.get_parent_map, ['a-revision-id'])
1910
def test_get_parent_map_negative_caches_missing_keys(self):
1911
self.setup_smart_server_with_call_log()
1912
repo = self.make_repository('foo')
1913
self.assertIsInstance(repo, RemoteRepository)
1915
self.addCleanup(repo.unlock)
1916
self.reset_smart_call_log()
1917
graph = repo.get_graph()
1918
self.assertEqual({},
1919
graph.get_parent_map(['some-missing', 'other-missing']))
1920
self.assertLength(1, self.hpss_calls)
1921
# No call if we repeat this
1922
self.reset_smart_call_log()
1923
graph = repo.get_graph()
1924
self.assertEqual({},
1925
graph.get_parent_map(['some-missing', 'other-missing']))
1926
self.assertLength(0, self.hpss_calls)
1927
# Asking for more unknown keys makes a request.
1928
self.reset_smart_call_log()
1929
graph = repo.get_graph()
1930
self.assertEqual({},
1931
graph.get_parent_map(['some-missing', 'other-missing',
1933
self.assertLength(1, self.hpss_calls)
1935
def disableExtraResults(self):
1936
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1937
SmartServerRepositoryGetParentMap.no_extra_results = True
1939
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1940
self.addCleanup(reset_values)
1942
def test_null_cached_missing_and_stop_key(self):
1943
self.setup_smart_server_with_call_log()
1944
# Make a branch with a single revision.
1945
builder = self.make_branch_builder('foo')
1946
builder.start_series()
1947
builder.build_snapshot('first', None, [
1948
('add', ('', 'root-id', 'directory', ''))])
1949
builder.finish_series()
1950
branch = builder.get_branch()
1951
repo = branch.repository
1952
self.assertIsInstance(repo, RemoteRepository)
1953
# Stop the server from sending extra results.
1954
self.disableExtraResults()
1956
self.addCleanup(repo.unlock)
1957
self.reset_smart_call_log()
1958
graph = repo.get_graph()
1959
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1960
# 'first' it will be a candidate for the stop_keys of subsequent
1961
# requests, and because 'null:' was queried but not returned it will be
1962
# cached as missing.
1963
self.assertEqual({'first': ('null:',)},
1964
graph.get_parent_map(['first', 'null:']))
1965
# Now query for another key. This request will pass along a recipe of
1966
# start and stop keys describing the already cached results, and this
1967
# recipe's revision count must be correct (or else it will trigger an
1968
# error from the server).
1969
self.assertEqual({}, graph.get_parent_map(['another-key']))
1970
# This assertion guards against disableExtraResults silently failing to
1971
# work, thus invalidating the test.
1972
self.assertLength(2, self.hpss_calls)
1974
def test_get_parent_map_gets_ghosts_from_result(self):
1975
# asking for a revision should negatively cache close ghosts in its
1977
self.setup_smart_server_with_call_log()
1978
tree = self.make_branch_and_memory_tree('foo')
1981
builder = treebuilder.TreeBuilder()
1982
builder.start_tree(tree)
1984
builder.finish_tree()
1985
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1986
rev_id = tree.commit('')
1990
self.addCleanup(tree.unlock)
1991
repo = tree.branch.repository
1992
self.assertIsInstance(repo, RemoteRepository)
1994
repo.get_parent_map([rev_id])
1995
self.reset_smart_call_log()
1996
# Now asking for rev_id's ghost parent should not make calls
1997
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1998
self.assertLength(0, self.hpss_calls)
2001
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2003
def test_allows_new_revisions(self):
2004
"""get_parent_map's results can be updated by commit."""
2005
smart_server = server.SmartTCPServer_for_testing()
2006
self.start_server(smart_server)
2007
self.make_branch('branch')
2008
branch = Branch.open(smart_server.get_url() + '/branch')
2009
tree = branch.create_checkout('tree', lightweight=True)
2011
self.addCleanup(tree.unlock)
2012
graph = tree.branch.repository.get_graph()
2013
# This provides an opportunity for the missing rev-id to be cached.
2014
self.assertEqual({}, graph.get_parent_map(['rev1']))
2015
tree.commit('message', rev_id='rev1')
2016
graph = tree.branch.repository.get_graph()
2017
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2020
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2022
def test_null_revision(self):
2023
# a null revision has the predictable result {}, we should have no wire
2024
# traffic when calling it with this argument
2025
transport_path = 'empty'
2026
repo, client = self.setup_fake_client_and_repository(transport_path)
2027
client.add_success_response('notused')
2028
# actual RemoteRepository.get_revision_graph is gone, but there's an
2029
# equivalent private method for testing
2030
result = repo._get_revision_graph(NULL_REVISION)
2031
self.assertEqual([], client._calls)
2032
self.assertEqual({}, result)
2034
def test_none_revision(self):
2035
# with none we want the entire graph
2036
r1 = u'\u0e33'.encode('utf8')
2037
r2 = u'\u0dab'.encode('utf8')
2038
lines = [' '.join([r2, r1]), r1]
2039
encoded_body = '\n'.join(lines)
2041
transport_path = 'sinhala'
2042
repo, client = self.setup_fake_client_and_repository(transport_path)
2043
client.add_success_response_with_body(encoded_body, 'ok')
2044
# actual RemoteRepository.get_revision_graph is gone, but there's an
2045
# equivalent private method for testing
2046
result = repo._get_revision_graph(None)
2048
[('call_expecting_body', 'Repository.get_revision_graph',
2051
self.assertEqual({r1: (), r2: (r1, )}, result)
2053
def test_specific_revision(self):
2054
# with a specific revision we want the graph for that
2055
# with none we want the entire graph
2056
r11 = u'\u0e33'.encode('utf8')
2057
r12 = u'\xc9'.encode('utf8')
2058
r2 = u'\u0dab'.encode('utf8')
2059
lines = [' '.join([r2, r11, r12]), r11, r12]
2060
encoded_body = '\n'.join(lines)
2062
transport_path = 'sinhala'
2063
repo, client = self.setup_fake_client_and_repository(transport_path)
2064
client.add_success_response_with_body(encoded_body, 'ok')
2065
result = repo._get_revision_graph(r2)
2067
[('call_expecting_body', 'Repository.get_revision_graph',
2070
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2072
def test_no_such_revision(self):
2074
transport_path = 'sinhala'
2075
repo, client = self.setup_fake_client_and_repository(transport_path)
2076
client.add_error_response('nosuchrevision', revid)
2077
# also check that the right revision is reported in the error
2078
self.assertRaises(errors.NoSuchRevision,
2079
repo._get_revision_graph, revid)
2081
[('call_expecting_body', 'Repository.get_revision_graph',
2082
('sinhala/', revid))],
2085
def test_unexpected_error(self):
2087
transport_path = 'sinhala'
2088
repo, client = self.setup_fake_client_and_repository(transport_path)
2089
client.add_error_response('AnUnexpectedError')
2090
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2091
repo._get_revision_graph, revid)
2092
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2095
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2098
repo, client = self.setup_fake_client_and_repository('quack')
2099
client.add_expected_call(
2100
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2101
'success', ('ok', 'rev-five'))
2102
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2103
self.assertEqual((True, 'rev-five'), result)
2104
self.assertFinished(client)
2106
def test_history_incomplete(self):
2107
repo, client = self.setup_fake_client_and_repository('quack')
2108
client.add_expected_call(
2109
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2110
'success', ('history-incomplete', 10, 'rev-ten'))
2111
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2112
self.assertEqual((False, (10, 'rev-ten')), result)
2113
self.assertFinished(client)
2115
def test_history_incomplete_with_fallback(self):
2116
"""A 'history-incomplete' response causes the fallback repository to be
2117
queried too, if one is set.
2119
# Make a repo with a fallback repo, both using a FakeClient.
2120
format = remote.response_tuple_to_repo_format(
2121
('yes', 'no', 'yes', 'fake-network-name'))
2122
repo, client = self.setup_fake_client_and_repository('quack')
2123
repo._format = format
2124
fallback_repo, ignored = self.setup_fake_client_and_repository(
2126
fallback_repo._client = client
2127
repo.add_fallback_repository(fallback_repo)
2128
# First the client should ask the primary repo
2129
client.add_expected_call(
2130
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2131
'success', ('history-incomplete', 2, 'rev-two'))
2132
# Then it should ask the fallback, using revno/revid from the
2133
# history-incomplete response as the known revno/revid.
2134
client.add_expected_call(
2135
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2136
'success', ('ok', 'rev-one'))
2137
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2138
self.assertEqual((True, 'rev-one'), result)
2139
self.assertFinished(client)
2141
def test_nosuchrevision(self):
2142
# 'nosuchrevision' is returned when the known-revid is not found in the
2143
# remote repo. The client translates that response to NoSuchRevision.
2144
repo, client = self.setup_fake_client_and_repository('quack')
2145
client.add_expected_call(
2146
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2147
'error', ('nosuchrevision', 'rev-foo'))
2149
errors.NoSuchRevision,
2150
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2151
self.assertFinished(client)
2154
class TestRepositoryIsShared(TestRemoteRepository):
2156
def test_is_shared(self):
2157
# ('yes', ) for Repository.is_shared -> 'True'.
2158
transport_path = 'quack'
2159
repo, client = self.setup_fake_client_and_repository(transport_path)
2160
client.add_success_response('yes')
2161
result = repo.is_shared()
2163
[('call', 'Repository.is_shared', ('quack/',))],
2165
self.assertEqual(True, result)
2167
def test_is_not_shared(self):
2168
# ('no', ) for Repository.is_shared -> 'False'.
2169
transport_path = 'qwack'
2170
repo, client = self.setup_fake_client_and_repository(transport_path)
2171
client.add_success_response('no')
2172
result = repo.is_shared()
2174
[('call', 'Repository.is_shared', ('qwack/',))],
2176
self.assertEqual(False, result)
2179
class TestRepositoryLockWrite(TestRemoteRepository):
2181
def test_lock_write(self):
2182
transport_path = 'quack'
2183
repo, client = self.setup_fake_client_and_repository(transport_path)
2184
client.add_success_response('ok', 'a token')
2185
result = repo.lock_write()
2187
[('call', 'Repository.lock_write', ('quack/', ''))],
2189
self.assertEqual('a token', result)
2191
def test_lock_write_already_locked(self):
2192
transport_path = 'quack'
2193
repo, client = self.setup_fake_client_and_repository(transport_path)
2194
client.add_error_response('LockContention')
2195
self.assertRaises(errors.LockContention, repo.lock_write)
2197
[('call', 'Repository.lock_write', ('quack/', ''))],
2200
def test_lock_write_unlockable(self):
2201
transport_path = 'quack'
2202
repo, client = self.setup_fake_client_and_repository(transport_path)
2203
client.add_error_response('UnlockableTransport')
2204
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2206
[('call', 'Repository.lock_write', ('quack/', ''))],
2210
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2212
def test_backwards_compat(self):
2213
self.setup_smart_server_with_call_log()
2214
repo = self.make_repository('.')
2215
self.reset_smart_call_log()
2216
verb = 'Repository.set_make_working_trees'
2217
self.disable_verb(verb)
2218
repo.set_make_working_trees(True)
2219
call_count = len([call for call in self.hpss_calls if
2220
call.call.method == verb])
2221
self.assertEqual(1, call_count)
2223
def test_current(self):
2224
transport_path = 'quack'
2225
repo, client = self.setup_fake_client_and_repository(transport_path)
2226
client.add_expected_call(
2227
'Repository.set_make_working_trees', ('quack/', 'True'),
2229
client.add_expected_call(
2230
'Repository.set_make_working_trees', ('quack/', 'False'),
2232
repo.set_make_working_trees(True)
2233
repo.set_make_working_trees(False)
2236
class TestRepositoryUnlock(TestRemoteRepository):
2238
def test_unlock(self):
2239
transport_path = 'quack'
2240
repo, client = self.setup_fake_client_and_repository(transport_path)
2241
client.add_success_response('ok', 'a token')
2242
client.add_success_response('ok')
2246
[('call', 'Repository.lock_write', ('quack/', '')),
2247
('call', 'Repository.unlock', ('quack/', 'a token'))],
2250
def test_unlock_wrong_token(self):
2251
# If somehow the token is wrong, unlock will raise TokenMismatch.
2252
transport_path = 'quack'
2253
repo, client = self.setup_fake_client_and_repository(transport_path)
2254
client.add_success_response('ok', 'a token')
2255
client.add_error_response('TokenMismatch')
2257
self.assertRaises(errors.TokenMismatch, repo.unlock)
2260
class TestRepositoryHasRevision(TestRemoteRepository):
2262
def test_none(self):
2263
# repo.has_revision(None) should not cause any traffic.
2264
transport_path = 'quack'
2265
repo, client = self.setup_fake_client_and_repository(transport_path)
2267
# The null revision is always there, so has_revision(None) == True.
2268
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2270
# The remote repo shouldn't be accessed.
2271
self.assertEqual([], client._calls)
2274
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2275
"""Base class for Repository.insert_stream and .insert_stream_1.19
2279
def checkInsertEmptyStream(self, repo, client):
2280
"""Insert an empty stream, checking the result.
2282
This checks that there are no resume_tokens or missing_keys, and that
2283
the client is finished.
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)
2293
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2294
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2297
This test case is very similar to TestRepositoryInsertStream_1_19.
2301
TestRemoteRepository.setUp(self)
2302
self.disable_verb('Repository.insert_stream_1.19')
2304
def test_unlocked_repo(self):
2305
transport_path = 'quack'
2306
repo, client = self.setup_fake_client_and_repository(transport_path)
2307
client.add_expected_call(
2308
'Repository.insert_stream_1.19', ('quack/', ''),
2309
'unknown', ('Repository.insert_stream_1.19',))
2310
client.add_expected_call(
2311
'Repository.insert_stream', ('quack/', ''),
2313
client.add_expected_call(
2314
'Repository.insert_stream', ('quack/', ''),
2316
self.checkInsertEmptyStream(repo, client)
2318
def test_locked_repo_with_no_lock_token(self):
2319
transport_path = 'quack'
2320
repo, client = self.setup_fake_client_and_repository(transport_path)
2321
client.add_expected_call(
2322
'Repository.lock_write', ('quack/', ''),
2323
'success', ('ok', ''))
2324
client.add_expected_call(
2325
'Repository.insert_stream_1.19', ('quack/', ''),
2326
'unknown', ('Repository.insert_stream_1.19',))
2327
client.add_expected_call(
2328
'Repository.insert_stream', ('quack/', ''),
2330
client.add_expected_call(
2331
'Repository.insert_stream', ('quack/', ''),
2334
self.checkInsertEmptyStream(repo, client)
2336
def test_locked_repo_with_lock_token(self):
2337
transport_path = 'quack'
2338
repo, client = self.setup_fake_client_and_repository(transport_path)
2339
client.add_expected_call(
2340
'Repository.lock_write', ('quack/', ''),
2341
'success', ('ok', 'a token'))
2342
client.add_expected_call(
2343
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2344
'unknown', ('Repository.insert_stream_1.19',))
2345
client.add_expected_call(
2346
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2348
client.add_expected_call(
2349
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2352
self.checkInsertEmptyStream(repo, client)
2354
def test_stream_with_inventory_deltas(self):
2355
"""'inventory-deltas' substreams cannot be sent to the
2356
Repository.insert_stream verb, because not all servers that implement
2357
that verb will accept them. So when one is encountered the RemoteSink
2358
immediately stops using that verb and falls back to VFS insert_stream.
2360
transport_path = 'quack'
2361
repo, client = self.setup_fake_client_and_repository(transport_path)
2362
client.add_expected_call(
2363
'Repository.insert_stream_1.19', ('quack/', ''),
2364
'unknown', ('Repository.insert_stream_1.19',))
2365
client.add_expected_call(
2366
'Repository.insert_stream', ('quack/', ''),
2368
client.add_expected_call(
2369
'Repository.insert_stream', ('quack/', ''),
2371
# Create a fake real repository for insert_stream to fall back on, so
2372
# that we can directly see the records the RemoteSink passes to the
2377
def insert_stream(self, stream, src_format, resume_tokens):
2378
for substream_kind, substream in stream:
2379
self.records.append(
2380
(substream_kind, [record.key for record in substream]))
2381
return ['fake tokens'], ['fake missing keys']
2382
fake_real_sink = FakeRealSink()
2383
class FakeRealRepository:
2384
def _get_sink(self):
2385
return fake_real_sink
2386
def is_in_write_group(self):
2388
def refresh_data(self):
2390
repo._real_repository = FakeRealRepository()
2391
sink = repo._get_sink()
2392
fmt = repository.RepositoryFormat.get_default_format()
2393
stream = self.make_stream_with_inv_deltas(fmt)
2394
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2395
# Every record from the first inventory delta should have been sent to
2397
expected_records = [
2398
('inventory-deltas', [('rev2',), ('rev3',)]),
2399
('texts', [('some-rev', 'some-file')])]
2400
self.assertEqual(expected_records, fake_real_sink.records)
2401
# The return values from the real sink's insert_stream are propagated
2402
# back to the original caller.
2403
self.assertEqual(['fake tokens'], resume_tokens)
2404
self.assertEqual(['fake missing keys'], missing_keys)
2405
self.assertFinished(client)
2407
def make_stream_with_inv_deltas(self, fmt):
2408
"""Make a simple stream with an inventory delta followed by more
2409
records and more substreams to test that all records and substreams
2410
from that point on are used.
2412
This sends, in order:
2413
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2415
* texts substream: (some-rev, some-file)
2417
# Define a stream using generators so that it isn't rewindable.
2418
inv = inventory.Inventory(revision_id='rev1')
2419
inv.root.revision = 'rev1'
2420
def stream_with_inv_delta():
2421
yield ('inventories', inventories_substream())
2422
yield ('inventory-deltas', inventory_delta_substream())
2424
versionedfile.FulltextContentFactory(
2425
('some-rev', 'some-file'), (), None, 'content')])
2426
def inventories_substream():
2427
# An empty inventory fulltext. This will be streamed normally.
2428
text = fmt._serializer.write_inventory_to_string(inv)
2429
yield versionedfile.FulltextContentFactory(
2430
('rev1',), (), None, text)
2431
def inventory_delta_substream():
2432
# An inventory delta. This can't be streamed via this verb, so it
2433
# will trigger a fallback to VFS insert_stream.
2434
entry = inv.make_entry(
2435
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2436
entry.revision = 'ghost'
2437
delta = [(None, 'newdir', 'newdir-id', entry)]
2438
serializer = inventory_delta.InventoryDeltaSerializer(
2439
versioned_root=True, tree_references=False)
2440
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2441
yield versionedfile.ChunkedContentFactory(
2442
('rev2',), (('rev1',)), None, lines)
2444
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2445
yield versionedfile.ChunkedContentFactory(
2446
('rev3',), (('rev1',)), None, lines)
2447
return stream_with_inv_delta()
2450
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2452
def test_unlocked_repo(self):
2453
transport_path = 'quack'
2454
repo, client = self.setup_fake_client_and_repository(transport_path)
2455
client.add_expected_call(
2456
'Repository.insert_stream_1.19', ('quack/', ''),
2458
client.add_expected_call(
2459
'Repository.insert_stream_1.19', ('quack/', ''),
2461
self.checkInsertEmptyStream(repo, client)
2463
def test_locked_repo_with_no_lock_token(self):
2464
transport_path = 'quack'
2465
repo, client = self.setup_fake_client_and_repository(transport_path)
2466
client.add_expected_call(
2467
'Repository.lock_write', ('quack/', ''),
2468
'success', ('ok', ''))
2469
client.add_expected_call(
2470
'Repository.insert_stream_1.19', ('quack/', ''),
2472
client.add_expected_call(
2473
'Repository.insert_stream_1.19', ('quack/', ''),
2476
self.checkInsertEmptyStream(repo, client)
2478
def test_locked_repo_with_lock_token(self):
2479
transport_path = 'quack'
2480
repo, client = self.setup_fake_client_and_repository(transport_path)
2481
client.add_expected_call(
2482
'Repository.lock_write', ('quack/', ''),
2483
'success', ('ok', 'a token'))
2484
client.add_expected_call(
2485
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2487
client.add_expected_call(
2488
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2491
self.checkInsertEmptyStream(repo, client)
2494
class TestRepositoryTarball(TestRemoteRepository):
2496
# This is a canned tarball reponse we can validate against
2498
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2499
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2500
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2501
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2502
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2503
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2504
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2505
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2506
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2507
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2508
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2509
'nWQ7QH/F3JFOFCQ0aSPfA='
2512
def test_repository_tarball(self):
2513
# Test that Repository.tarball generates the right operations
2514
transport_path = 'repo'
2515
expected_calls = [('call_expecting_body', 'Repository.tarball',
2516
('repo/', 'bz2',),),
2518
repo, client = self.setup_fake_client_and_repository(transport_path)
2519
client.add_success_response_with_body(self.tarball_content, 'ok')
2520
# Now actually ask for the tarball
2521
tarball_file = repo._get_tarball('bz2')
2523
self.assertEqual(expected_calls, client._calls)
2524
self.assertEqual(self.tarball_content, tarball_file.read())
2526
tarball_file.close()
2529
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2530
"""RemoteRepository.copy_content_into optimizations"""
2532
def test_copy_content_remote_to_local(self):
2533
self.transport_server = server.SmartTCPServer_for_testing
2534
src_repo = self.make_repository('repo1')
2535
src_repo = repository.Repository.open(self.get_url('repo1'))
2536
# At the moment the tarball-based copy_content_into can't write back
2537
# into a smart server. It would be good if it could upload the
2538
# tarball; once that works we'd have to create repositories of
2539
# different formats. -- mbp 20070410
2540
dest_url = self.get_vfs_only_url('repo2')
2541
dest_bzrdir = BzrDir.create(dest_url)
2542
dest_repo = dest_bzrdir.create_repository()
2543
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2544
self.assertTrue(isinstance(src_repo, RemoteRepository))
2545
src_repo.copy_content_into(dest_repo)
2548
class _StubRealPackRepository(object):
2550
def __init__(self, calls):
2552
self._pack_collection = _StubPackCollection(calls)
2554
def is_in_write_group(self):
2557
def refresh_data(self):
2558
self.calls.append(('pack collection reload_pack_names',))
2561
class _StubPackCollection(object):
2563
def __init__(self, calls):
2567
self.calls.append(('pack collection autopack',))
2570
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2571
"""Tests for RemoteRepository.autopack implementation."""
2574
"""When the server returns 'ok' and there's no _real_repository, then
2575
nothing else happens: the autopack method is done.
2577
transport_path = 'quack'
2578
repo, client = self.setup_fake_client_and_repository(transport_path)
2579
client.add_expected_call(
2580
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2582
self.assertFinished(client)
2584
def test_ok_with_real_repo(self):
2585
"""When the server returns 'ok' and there is a _real_repository, then
2586
the _real_repository's reload_pack_name's method will be called.
2588
transport_path = 'quack'
2589
repo, client = self.setup_fake_client_and_repository(transport_path)
2590
client.add_expected_call(
2591
'PackRepository.autopack', ('quack/',),
2593
repo._real_repository = _StubRealPackRepository(client._calls)
2596
[('call', 'PackRepository.autopack', ('quack/',)),
2597
('pack collection reload_pack_names',)],
2600
def test_backwards_compatibility(self):
2601
"""If the server does not recognise the PackRepository.autopack verb,
2602
fallback to the real_repository's implementation.
2604
transport_path = 'quack'
2605
repo, client = self.setup_fake_client_and_repository(transport_path)
2606
client.add_unknown_method_response('PackRepository.autopack')
2607
def stub_ensure_real():
2608
client._calls.append(('_ensure_real',))
2609
repo._real_repository = _StubRealPackRepository(client._calls)
2610
repo._ensure_real = stub_ensure_real
2613
[('call', 'PackRepository.autopack', ('quack/',)),
2615
('pack collection autopack',)],
2619
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2620
"""Base class for unit tests for bzrlib.remote._translate_error."""
2622
def translateTuple(self, error_tuple, **context):
2623
"""Call _translate_error with an ErrorFromSmartServer built from the
2626
:param error_tuple: A tuple of a smart server response, as would be
2627
passed to an ErrorFromSmartServer.
2628
:kwargs context: context items to call _translate_error with.
2630
:returns: The error raised by _translate_error.
2632
# Raise the ErrorFromSmartServer before passing it as an argument,
2633
# because _translate_error may need to re-raise it with a bare 'raise'
2635
server_error = errors.ErrorFromSmartServer(error_tuple)
2636
translated_error = self.translateErrorFromSmartServer(
2637
server_error, **context)
2638
return translated_error
2640
def translateErrorFromSmartServer(self, error_object, **context):
2641
"""Like translateTuple, but takes an already constructed
2642
ErrorFromSmartServer rather than a tuple.
2646
except errors.ErrorFromSmartServer, server_error:
2647
translated_error = self.assertRaises(
2648
errors.BzrError, remote._translate_error, server_error,
2650
return translated_error
2653
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2654
"""Unit tests for bzrlib.remote._translate_error.
2656
Given an ErrorFromSmartServer (which has an error tuple from a smart
2657
server) and some context, _translate_error raises more specific errors from
2660
This test case covers the cases where _translate_error succeeds in
2661
translating an ErrorFromSmartServer to something better. See
2662
TestErrorTranslationRobustness for other cases.
2665
def test_NoSuchRevision(self):
2666
branch = self.make_branch('')
2668
translated_error = self.translateTuple(
2669
('NoSuchRevision', revid), branch=branch)
2670
expected_error = errors.NoSuchRevision(branch, revid)
2671
self.assertEqual(expected_error, translated_error)
2673
def test_nosuchrevision(self):
2674
repository = self.make_repository('')
2676
translated_error = self.translateTuple(
2677
('nosuchrevision', revid), repository=repository)
2678
expected_error = errors.NoSuchRevision(repository, revid)
2679
self.assertEqual(expected_error, translated_error)
2681
def test_nobranch(self):
2682
bzrdir = self.make_bzrdir('')
2683
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2684
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2685
self.assertEqual(expected_error, translated_error)
2687
def test_LockContention(self):
2688
translated_error = self.translateTuple(('LockContention',))
2689
expected_error = errors.LockContention('(remote lock)')
2690
self.assertEqual(expected_error, translated_error)
2692
def test_UnlockableTransport(self):
2693
bzrdir = self.make_bzrdir('')
2694
translated_error = self.translateTuple(
2695
('UnlockableTransport',), bzrdir=bzrdir)
2696
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2697
self.assertEqual(expected_error, translated_error)
2699
def test_LockFailed(self):
2700
lock = 'str() of a server lock'
2701
why = 'str() of why'
2702
translated_error = self.translateTuple(('LockFailed', lock, why))
2703
expected_error = errors.LockFailed(lock, why)
2704
self.assertEqual(expected_error, translated_error)
2706
def test_TokenMismatch(self):
2707
token = 'a lock token'
2708
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2709
expected_error = errors.TokenMismatch(token, '(remote token)')
2710
self.assertEqual(expected_error, translated_error)
2712
def test_Diverged(self):
2713
branch = self.make_branch('a')
2714
other_branch = self.make_branch('b')
2715
translated_error = self.translateTuple(
2716
('Diverged',), branch=branch, other_branch=other_branch)
2717
expected_error = errors.DivergedBranches(branch, other_branch)
2718
self.assertEqual(expected_error, translated_error)
2720
def test_ReadError_no_args(self):
2722
translated_error = self.translateTuple(('ReadError',), path=path)
2723
expected_error = errors.ReadError(path)
2724
self.assertEqual(expected_error, translated_error)
2726
def test_ReadError(self):
2728
translated_error = self.translateTuple(('ReadError', path))
2729
expected_error = errors.ReadError(path)
2730
self.assertEqual(expected_error, translated_error)
2732
def test_IncompatibleRepositories(self):
2733
translated_error = self.translateTuple(('IncompatibleRepositories',
2734
"repo1", "repo2", "details here"))
2735
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
2737
self.assertEqual(expected_error, translated_error)
2739
def test_PermissionDenied_no_args(self):
2741
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2742
expected_error = errors.PermissionDenied(path)
2743
self.assertEqual(expected_error, translated_error)
2745
def test_PermissionDenied_one_arg(self):
2747
translated_error = self.translateTuple(('PermissionDenied', path))
2748
expected_error = errors.PermissionDenied(path)
2749
self.assertEqual(expected_error, translated_error)
2751
def test_PermissionDenied_one_arg_and_context(self):
2752
"""Given a choice between a path from the local context and a path on
2753
the wire, _translate_error prefers the path from the local context.
2755
local_path = 'local path'
2756
remote_path = 'remote path'
2757
translated_error = self.translateTuple(
2758
('PermissionDenied', remote_path), path=local_path)
2759
expected_error = errors.PermissionDenied(local_path)
2760
self.assertEqual(expected_error, translated_error)
2762
def test_PermissionDenied_two_args(self):
2764
extra = 'a string with extra info'
2765
translated_error = self.translateTuple(
2766
('PermissionDenied', path, extra))
2767
expected_error = errors.PermissionDenied(path, extra)
2768
self.assertEqual(expected_error, translated_error)
2771
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2772
"""Unit tests for bzrlib.remote._translate_error's robustness.
2774
TestErrorTranslationSuccess is for cases where _translate_error can
2775
translate successfully. This class about how _translate_err behaves when
2776
it fails to translate: it re-raises the original error.
2779
def test_unrecognised_server_error(self):
2780
"""If the error code from the server is not recognised, the original
2781
ErrorFromSmartServer is propagated unmodified.
2783
error_tuple = ('An unknown error tuple',)
2784
server_error = errors.ErrorFromSmartServer(error_tuple)
2785
translated_error = self.translateErrorFromSmartServer(server_error)
2786
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2787
self.assertEqual(expected_error, translated_error)
2789
def test_context_missing_a_key(self):
2790
"""In case of a bug in the client, or perhaps an unexpected response
2791
from a server, _translate_error returns the original error tuple from
2792
the server and mutters a warning.
2794
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2795
# in the context dict. So let's give it an empty context dict instead
2796
# to exercise its error recovery.
2798
error_tuple = ('NoSuchRevision', 'revid')
2799
server_error = errors.ErrorFromSmartServer(error_tuple)
2800
translated_error = self.translateErrorFromSmartServer(server_error)
2801
self.assertEqual(server_error, translated_error)
2802
# In addition to re-raising ErrorFromSmartServer, some debug info has
2803
# been muttered to the log file for developer to look at.
2804
self.assertContainsRe(
2805
self._get_log(keep_log_file=True),
2806
"Missing key 'branch' in context")
2808
def test_path_missing(self):
2809
"""Some translations (PermissionDenied, ReadError) can determine the
2810
'path' variable from either the wire or the local context. If neither
2811
has it, then an error is raised.
2813
error_tuple = ('ReadError',)
2814
server_error = errors.ErrorFromSmartServer(error_tuple)
2815
translated_error = self.translateErrorFromSmartServer(server_error)
2816
self.assertEqual(server_error, translated_error)
2817
# In addition to re-raising ErrorFromSmartServer, some debug info has
2818
# been muttered to the log file for developer to look at.
2819
self.assertContainsRe(
2820
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2823
class TestStacking(tests.TestCaseWithTransport):
2824
"""Tests for operations on stacked remote repositories.
2826
The underlying format type must support stacking.
2829
def test_access_stacked_remote(self):
2830
# based on <http://launchpad.net/bugs/261315>
2831
# make a branch stacked on another repository containing an empty
2832
# revision, then open it over hpss - we should be able to see that
2834
base_transport = self.get_transport()
2835
base_builder = self.make_branch_builder('base', format='1.9')
2836
base_builder.start_series()
2837
base_revid = base_builder.build_snapshot('rev-id', None,
2838
[('add', ('', None, 'directory', None))],
2840
base_builder.finish_series()
2841
stacked_branch = self.make_branch('stacked', format='1.9')
2842
stacked_branch.set_stacked_on_url('../base')
2843
# start a server looking at this
2844
smart_server = server.SmartTCPServer_for_testing()
2845
self.start_server(smart_server)
2846
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2847
# can get its branch and repository
2848
remote_branch = remote_bzrdir.open_branch()
2849
remote_repo = remote_branch.repository
2850
remote_repo.lock_read()
2852
# it should have an appropriate fallback repository, which should also
2853
# be a RemoteRepository
2854
self.assertLength(1, remote_repo._fallback_repositories)
2855
self.assertIsInstance(remote_repo._fallback_repositories[0],
2857
# and it has the revision committed to the underlying repository;
2858
# these have varying implementations so we try several of them
2859
self.assertTrue(remote_repo.has_revisions([base_revid]))
2860
self.assertTrue(remote_repo.has_revision(base_revid))
2861
self.assertEqual(remote_repo.get_revision(base_revid).message,
2864
remote_repo.unlock()
2866
def prepare_stacked_remote_branch(self):
2867
"""Get stacked_upon and stacked branches with content in each."""
2868
self.setup_smart_server_with_call_log()
2869
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2870
tree1.commit('rev1', rev_id='rev1')
2871
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2872
).open_workingtree()
2873
local_tree = tree2.branch.create_checkout('local')
2874
local_tree.commit('local changes make me feel good.')
2875
branch2 = Branch.open(self.get_url('tree2'))
2877
self.addCleanup(branch2.unlock)
2878
return tree1.branch, branch2
2880
def test_stacked_get_parent_map(self):
2881
# the public implementation of get_parent_map obeys stacking
2882
_, branch = self.prepare_stacked_remote_branch()
2883
repo = branch.repository
2884
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2886
def test_unstacked_get_parent_map(self):
2887
# _unstacked_provider.get_parent_map ignores stacking
2888
_, branch = self.prepare_stacked_remote_branch()
2889
provider = branch.repository._unstacked_provider
2890
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2892
def fetch_stream_to_rev_order(self, stream):
2894
for kind, substream in stream:
2895
if not kind == 'revisions':
2898
for content in substream:
2899
result.append(content.key[-1])
2902
def get_ordered_revs(self, format, order, branch_factory=None):
2903
"""Get a list of the revisions in a stream to format format.
2905
:param format: The format of the target.
2906
:param order: the order that target should have requested.
2907
:param branch_factory: A callable to create a trunk and stacked branch
2908
to fetch from. If none, self.prepare_stacked_remote_branch is used.
2909
:result: The revision ids in the stream, in the order seen,
2910
the topological order of revisions in the source.
2912
unordered_format = bzrdir.format_registry.get(format)()
2913
target_repository_format = unordered_format.repository_format
2915
self.assertEqual(order, target_repository_format._fetch_order)
2916
if branch_factory is None:
2917
branch_factory = self.prepare_stacked_remote_branch
2918
_, stacked = branch_factory()
2919
source = stacked.repository._get_source(target_repository_format)
2920
tip = stacked.last_revision()
2921
revs = stacked.repository.get_ancestry(tip)
2922
search = graph.PendingAncestryResult([tip], stacked.repository)
2923
self.reset_smart_call_log()
2924
stream = source.get_stream(search)
2927
# We trust that if a revision is in the stream the rest of the new
2928
# content for it is too, as per our main fetch tests; here we are
2929
# checking that the revisions are actually included at all, and their
2931
return self.fetch_stream_to_rev_order(stream), revs
2933
def test_stacked_get_stream_unordered(self):
2934
# Repository._get_source.get_stream() from a stacked repository with
2935
# unordered yields the full data from both stacked and stacked upon
2937
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2938
self.assertEqual(set(expected_revs), set(rev_ord))
2939
# Getting unordered results should have made a streaming data request
2940
# from the server, then one from the backing branch.
2941
self.assertLength(2, self.hpss_calls)
2943
def test_stacked_on_stacked_get_stream_unordered(self):
2944
# Repository._get_source.get_stream() from a stacked repository which
2945
# is itself stacked yields the full data from all three sources.
2946
def make_stacked_stacked():
2947
_, stacked = self.prepare_stacked_remote_branch()
2948
tree = stacked.bzrdir.sprout('tree3', stacked=True
2949
).open_workingtree()
2950
local_tree = tree.branch.create_checkout('local-tree3')
2951
local_tree.commit('more local changes are better')
2952
branch = Branch.open(self.get_url('tree3'))
2955
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
2956
branch_factory=make_stacked_stacked)
2957
self.assertEqual(set(expected_revs), set(rev_ord))
2958
# Getting unordered results should have made a streaming data request
2959
# from the server, and one from each backing repo
2960
self.assertLength(3, self.hpss_calls)
2962
def test_stacked_get_stream_topological(self):
2963
# Repository._get_source.get_stream() from a stacked repository with
2964
# topological sorting yields the full data from both stacked and
2965
# stacked upon sources in topological order.
2966
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2967
self.assertEqual(expected_revs, rev_ord)
2968
# Getting topological sort requires VFS calls still - one of which is
2969
# pushing up from the bound branch.
2970
self.assertLength(13, self.hpss_calls)
2972
def test_stacked_get_stream_groupcompress(self):
2973
# Repository._get_source.get_stream() from a stacked repository with
2974
# groupcompress sorting yields the full data from both stacked and
2975
# stacked upon sources in groupcompress order.
2976
raise tests.TestSkipped('No groupcompress ordered format available')
2977
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2978
self.assertEqual(expected_revs, reversed(rev_ord))
2979
# Getting unordered results should have made a streaming data request
2980
# from the backing branch, and one from the stacked on branch.
2981
self.assertLength(2, self.hpss_calls)
2983
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
2984
# When pulling some fixed amount of content that is more than the
2985
# source has (because some is coming from a fallback branch, no error
2986
# should be received. This was reported as bug 360791.
2987
# Need three branches: a trunk, a stacked branch, and a preexisting
2988
# branch pulling content from stacked and trunk.
2989
self.setup_smart_server_with_call_log()
2990
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
2991
r1 = trunk.commit('start')
2992
stacked_branch = trunk.branch.create_clone_on_transport(
2993
self.get_transport('stacked'), stacked_on=trunk.branch.base)
2994
local = self.make_branch('local', format='1.9-rich-root')
2995
local.repository.fetch(stacked_branch.repository,
2996
stacked_branch.last_revision())
2999
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3002
super(TestRemoteBranchEffort, self).setUp()
3003
# Create a smart server that publishes whatever the backing VFS server
3005
self.smart_server = server.SmartTCPServer_for_testing()
3006
self.start_server(self.smart_server, self.get_server())
3007
# Log all HPSS calls into self.hpss_calls.
3008
_SmartClient.hooks.install_named_hook(
3009
'call', self.capture_hpss_call, None)
3010
self.hpss_calls = []
3012
def capture_hpss_call(self, params):
3013
self.hpss_calls.append(params.method)
3015
def test_copy_content_into_avoids_revision_history(self):
3016
local = self.make_branch('local')
3017
remote_backing_tree = self.make_branch_and_tree('remote')
3018
remote_backing_tree.commit("Commit.")
3019
remote_branch_url = self.smart_server.get_url() + 'remote'
3020
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3021
local.repository.fetch(remote_branch.repository)
3022
self.hpss_calls = []
3023
remote_branch.copy_content_into(local)
3024
self.assertFalse('Branch.revision_history' in self.hpss_calls)