1
# Copyright (C) 2006-2010 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 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
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': test_server.SmartTCPServer_for_testing_v2_only}),
81
{'transport_server': test_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_bzrdir_repr(self):
139
b = BzrDir.open_from_transport(self.transport)
140
self.assertStartsWith(str(b), 'RemoteBzrDir(')
142
def test_remote_branch_format_supports_stacking(self):
144
self.make_branch('unstackable', format='pack-0.92')
145
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
146
self.assertFalse(b._format.supports_stacking())
147
self.make_branch('stackable', format='1.9')
148
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
149
self.assertTrue(b._format.supports_stacking())
151
def test_remote_repo_format_supports_external_references(self):
153
bd = self.make_bzrdir('unstackable', format='pack-0.92')
154
r = bd.create_repository()
155
self.assertFalse(r._format.supports_external_lookups)
156
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
157
self.assertFalse(r._format.supports_external_lookups)
158
bd = self.make_bzrdir('stackable', format='1.9')
159
r = bd.create_repository()
160
self.assertTrue(r._format.supports_external_lookups)
161
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
162
self.assertTrue(r._format.supports_external_lookups)
164
def test_remote_branch_set_append_revisions_only(self):
165
# Make a format 1.9 branch, which supports append_revisions_only
166
branch = self.make_branch('branch', format='1.9')
167
config = branch.get_config()
168
branch.set_append_revisions_only(True)
170
'True', config.get_user_option('append_revisions_only'))
171
branch.set_append_revisions_only(False)
173
'False', config.get_user_option('append_revisions_only'))
175
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
176
branch = self.make_branch('branch', format='knit')
177
config = branch.get_config()
179
errors.UpgradeRequired, branch.set_append_revisions_only, True)
182
class FakeProtocol(object):
183
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
185
def __init__(self, body, fake_client):
187
self._body_buffer = None
188
self._fake_client = fake_client
190
def read_body_bytes(self, count=-1):
191
if self._body_buffer is None:
192
self._body_buffer = StringIO(self.body)
193
bytes = self._body_buffer.read(count)
194
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
195
self._fake_client.expecting_body = False
198
def cancel_read_body(self):
199
self._fake_client.expecting_body = False
201
def read_streamed_body(self):
205
class FakeClient(_SmartClient):
206
"""Lookalike for _SmartClient allowing testing."""
208
def __init__(self, fake_medium_base='fake base'):
209
"""Create a FakeClient."""
212
self.expecting_body = False
213
# if non-None, this is the list of expected calls, with only the
214
# method name and arguments included. the body might be hard to
215
# compute so is not included. If a call is None, that call can
217
self._expected_calls = None
218
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
220
def add_expected_call(self, call_name, call_args, response_type,
221
response_args, response_body=None):
222
if self._expected_calls is None:
223
self._expected_calls = []
224
self._expected_calls.append((call_name, call_args))
225
self.responses.append((response_type, response_args, response_body))
227
def add_success_response(self, *args):
228
self.responses.append(('success', args, None))
230
def add_success_response_with_body(self, body, *args):
231
self.responses.append(('success', args, body))
232
if self._expected_calls is not None:
233
self._expected_calls.append(None)
235
def add_error_response(self, *args):
236
self.responses.append(('error', args))
238
def add_unknown_method_response(self, verb):
239
self.responses.append(('unknown', verb))
241
def finished_test(self):
242
if self._expected_calls:
243
raise AssertionError("%r finished but was still expecting %r"
244
% (self, self._expected_calls[0]))
246
def _get_next_response(self):
248
response_tuple = self.responses.pop(0)
249
except IndexError, e:
250
raise AssertionError("%r didn't expect any more calls"
252
if response_tuple[0] == 'unknown':
253
raise errors.UnknownSmartMethod(response_tuple[1])
254
elif response_tuple[0] == 'error':
255
raise errors.ErrorFromSmartServer(response_tuple[1])
256
return response_tuple
258
def _check_call(self, method, args):
259
if self._expected_calls is None:
260
# the test should be updated to say what it expects
263
next_call = self._expected_calls.pop(0)
265
raise AssertionError("%r didn't expect any more calls "
267
% (self, method, args,))
268
if next_call is None:
270
if method != next_call[0] or args != next_call[1]:
271
raise AssertionError("%r expected %r%r "
273
% (self, next_call[0], next_call[1], method, args,))
275
def call(self, method, *args):
276
self._check_call(method, args)
277
self._calls.append(('call', method, args))
278
return self._get_next_response()[1]
280
def call_expecting_body(self, method, *args):
281
self._check_call(method, args)
282
self._calls.append(('call_expecting_body', method, args))
283
result = self._get_next_response()
284
self.expecting_body = True
285
return result[1], FakeProtocol(result[2], self)
287
def call_with_body_bytes(self, method, args, body):
288
self._check_call(method, args)
289
self._calls.append(('call_with_body_bytes', method, args, body))
290
result = self._get_next_response()
291
return result[1], FakeProtocol(result[2], self)
293
def call_with_body_bytes_expecting_body(self, method, args, body):
294
self._check_call(method, args)
295
self._calls.append(('call_with_body_bytes_expecting_body', method,
297
result = self._get_next_response()
298
self.expecting_body = True
299
return result[1], FakeProtocol(result[2], self)
301
def call_with_body_stream(self, args, stream):
302
# Explicitly consume the stream before checking for an error, because
303
# that's what happens a real medium.
304
stream = list(stream)
305
self._check_call(args[0], args[1:])
306
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
307
result = self._get_next_response()
308
# The second value returned from call_with_body_stream is supposed to
309
# be a response_handler object, but so far no tests depend on that.
310
response_handler = None
311
return result[1], response_handler
314
class FakeMedium(medium.SmartClientMedium):
316
def __init__(self, client_calls, base):
317
medium.SmartClientMedium.__init__(self, base)
318
self._client_calls = client_calls
320
def disconnect(self):
321
self._client_calls.append(('disconnect medium',))
324
class TestVfsHas(tests.TestCase):
326
def test_unicode_path(self):
327
client = FakeClient('/')
328
client.add_success_response('yes',)
329
transport = RemoteTransport('bzr://localhost/', _client=client)
330
filename = u'/hell\u00d8'.encode('utf8')
331
result = transport.has(filename)
333
[('call', 'has', (filename,))],
335
self.assertTrue(result)
338
class TestRemote(tests.TestCaseWithMemoryTransport):
340
def get_branch_format(self):
341
reference_bzrdir_format = bzrdir.format_registry.get('default')()
342
return reference_bzrdir_format.get_branch_format()
344
def get_repo_format(self):
345
reference_bzrdir_format = bzrdir.format_registry.get('default')()
346
return reference_bzrdir_format.repository_format
348
def assertFinished(self, fake_client):
349
"""Assert that all of a FakeClient's expected calls have occurred."""
350
fake_client.finished_test()
353
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
354
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
356
def assertRemotePath(self, expected, client_base, transport_base):
357
"""Assert that the result of
358
SmartClientMedium.remote_path_from_transport is the expected value for
359
a given client_base and transport_base.
361
client_medium = medium.SmartClientMedium(client_base)
362
transport = get_transport(transport_base)
363
result = client_medium.remote_path_from_transport(transport)
364
self.assertEqual(expected, result)
366
def test_remote_path_from_transport(self):
367
"""SmartClientMedium.remote_path_from_transport calculates a URL for
368
the given transport relative to the root of the client base URL.
370
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
371
self.assertRemotePath(
372
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
374
def assertRemotePathHTTP(self, expected, transport_base, relpath):
375
"""Assert that the result of
376
HttpTransportBase.remote_path_from_transport is the expected value for
377
a given transport_base and relpath of that transport. (Note that
378
HttpTransportBase is a subclass of SmartClientMedium)
380
base_transport = get_transport(transport_base)
381
client_medium = base_transport.get_smart_medium()
382
cloned_transport = base_transport.clone(relpath)
383
result = client_medium.remote_path_from_transport(cloned_transport)
384
self.assertEqual(expected, result)
386
def test_remote_path_from_transport_http(self):
387
"""Remote paths for HTTP transports are calculated differently to other
388
transports. They are just relative to the client base, not the root
389
directory of the host.
391
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
392
self.assertRemotePathHTTP(
393
'../xyz/', scheme + '//host/path', '../xyz/')
394
self.assertRemotePathHTTP(
395
'xyz/', scheme + '//host/path', 'xyz/')
398
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
399
"""Tests for the behaviour of client_medium.remote_is_at_least."""
401
def test_initially_unlimited(self):
402
"""A fresh medium assumes that the remote side supports all
405
client_medium = medium.SmartClientMedium('dummy base')
406
self.assertFalse(client_medium._is_remote_before((99, 99)))
408
def test__remember_remote_is_before(self):
409
"""Calling _remember_remote_is_before ratchets down the known remote
412
client_medium = medium.SmartClientMedium('dummy base')
413
# Mark the remote side as being less than 1.6. The remote side may
415
client_medium._remember_remote_is_before((1, 6))
416
self.assertTrue(client_medium._is_remote_before((1, 6)))
417
self.assertFalse(client_medium._is_remote_before((1, 5)))
418
# Calling _remember_remote_is_before again with a lower value works.
419
client_medium._remember_remote_is_before((1, 5))
420
self.assertTrue(client_medium._is_remote_before((1, 5)))
421
# You cannot call _remember_remote_is_before with a larger value.
423
AssertionError, client_medium._remember_remote_is_before, (1, 9))
426
class TestBzrDirCloningMetaDir(TestRemote):
428
def test_backwards_compat(self):
429
self.setup_smart_server_with_call_log()
430
a_dir = self.make_bzrdir('.')
431
self.reset_smart_call_log()
432
verb = 'BzrDir.cloning_metadir'
433
self.disable_verb(verb)
434
format = a_dir.cloning_metadir()
435
call_count = len([call for call in self.hpss_calls if
436
call.call.method == verb])
437
self.assertEqual(1, call_count)
439
def test_branch_reference(self):
440
transport = self.get_transport('quack')
441
referenced = self.make_branch('referenced')
442
expected = referenced.bzrdir.cloning_metadir()
443
client = FakeClient(transport.base)
444
client.add_expected_call(
445
'BzrDir.cloning_metadir', ('quack/', 'False'),
446
'error', ('BranchReference',)),
447
client.add_expected_call(
448
'BzrDir.open_branchV3', ('quack/',),
449
'success', ('ref', self.get_url('referenced'))),
450
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
452
result = a_bzrdir.cloning_metadir()
453
# We should have got a control dir matching the referenced branch.
454
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
455
self.assertEqual(expected._repository_format, result._repository_format)
456
self.assertEqual(expected._branch_format, result._branch_format)
457
self.assertFinished(client)
459
def test_current_server(self):
460
transport = self.get_transport('.')
461
transport = transport.clone('quack')
462
self.make_bzrdir('quack')
463
client = FakeClient(transport.base)
464
reference_bzrdir_format = bzrdir.format_registry.get('default')()
465
control_name = reference_bzrdir_format.network_name()
466
client.add_expected_call(
467
'BzrDir.cloning_metadir', ('quack/', 'False'),
468
'success', (control_name, '', ('branch', ''))),
469
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
471
result = a_bzrdir.cloning_metadir()
472
# We should have got a reference control dir with default branch and
473
# repository formats.
474
# This pokes a little, just to be sure.
475
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
476
self.assertEqual(None, result._repository_format)
477
self.assertEqual(None, result._branch_format)
478
self.assertFinished(client)
481
class TestBzrDirOpen(TestRemote):
483
def make_fake_client_and_transport(self, path='quack'):
484
transport = MemoryTransport()
485
transport.mkdir(path)
486
transport = transport.clone(path)
487
client = FakeClient(transport.base)
488
return client, transport
490
def test_absent(self):
491
client, transport = self.make_fake_client_and_transport()
492
client.add_expected_call(
493
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
494
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
495
remote.RemoteBzrDirFormat(), _client=client, _force_probe=True)
496
self.assertFinished(client)
498
def test_present_without_workingtree(self):
499
client, transport = self.make_fake_client_and_transport()
500
client.add_expected_call(
501
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
502
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
503
_client=client, _force_probe=True)
504
self.assertIsInstance(bd, RemoteBzrDir)
505
self.assertFalse(bd.has_workingtree())
506
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
507
self.assertFinished(client)
509
def test_present_with_workingtree(self):
510
client, transport = self.make_fake_client_and_transport()
511
client.add_expected_call(
512
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
513
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
514
_client=client, _force_probe=True)
515
self.assertIsInstance(bd, RemoteBzrDir)
516
self.assertTrue(bd.has_workingtree())
517
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
518
self.assertFinished(client)
520
def test_backwards_compat(self):
521
client, transport = self.make_fake_client_and_transport()
522
client.add_expected_call(
523
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
524
client.add_expected_call(
525
'BzrDir.open', ('quack/',), 'success', ('yes',))
526
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
527
_client=client, _force_probe=True)
528
self.assertIsInstance(bd, RemoteBzrDir)
529
self.assertFinished(client)
532
class TestBzrDirOpenBranch(TestRemote):
534
def test_backwards_compat(self):
535
self.setup_smart_server_with_call_log()
536
self.make_branch('.')
537
a_dir = BzrDir.open(self.get_url('.'))
538
self.reset_smart_call_log()
539
verb = 'BzrDir.open_branchV3'
540
self.disable_verb(verb)
541
format = a_dir.open_branch()
542
call_count = len([call for call in self.hpss_calls if
543
call.call.method == verb])
544
self.assertEqual(1, call_count)
546
def test_branch_present(self):
547
reference_format = self.get_repo_format()
548
network_name = reference_format.network_name()
549
branch_network_name = self.get_branch_format().network_name()
550
transport = MemoryTransport()
551
transport.mkdir('quack')
552
transport = transport.clone('quack')
553
client = FakeClient(transport.base)
554
client.add_expected_call(
555
'BzrDir.open_branchV3', ('quack/',),
556
'success', ('branch', branch_network_name))
557
client.add_expected_call(
558
'BzrDir.find_repositoryV3', ('quack/',),
559
'success', ('ok', '', 'no', 'no', 'no', network_name))
560
client.add_expected_call(
561
'Branch.get_stacked_on_url', ('quack/',),
562
'error', ('NotStacked',))
563
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
565
result = bzrdir.open_branch()
566
self.assertIsInstance(result, RemoteBranch)
567
self.assertEqual(bzrdir, result.bzrdir)
568
self.assertFinished(client)
570
def test_branch_missing(self):
571
transport = MemoryTransport()
572
transport.mkdir('quack')
573
transport = transport.clone('quack')
574
client = FakeClient(transport.base)
575
client.add_error_response('nobranch')
576
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
578
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
580
[('call', 'BzrDir.open_branchV3', ('quack/',))],
583
def test__get_tree_branch(self):
584
# _get_tree_branch is a form of open_branch, but it should only ask for
585
# branch opening, not any other network requests.
588
calls.append("Called")
590
transport = MemoryTransport()
591
# no requests on the network - catches other api calls being made.
592
client = FakeClient(transport.base)
593
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
595
# patch the open_branch call to record that it was called.
596
bzrdir.open_branch = open_branch
597
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
598
self.assertEqual(["Called"], calls)
599
self.assertEqual([], client._calls)
601
def test_url_quoting_of_path(self):
602
# Relpaths on the wire should not be URL-escaped. So "~" should be
603
# transmitted as "~", not "%7E".
604
transport = RemoteTCPTransport('bzr://localhost/~hello/')
605
client = FakeClient(transport.base)
606
reference_format = self.get_repo_format()
607
network_name = reference_format.network_name()
608
branch_network_name = self.get_branch_format().network_name()
609
client.add_expected_call(
610
'BzrDir.open_branchV3', ('~hello/',),
611
'success', ('branch', branch_network_name))
612
client.add_expected_call(
613
'BzrDir.find_repositoryV3', ('~hello/',),
614
'success', ('ok', '', 'no', 'no', 'no', network_name))
615
client.add_expected_call(
616
'Branch.get_stacked_on_url', ('~hello/',),
617
'error', ('NotStacked',))
618
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
620
result = bzrdir.open_branch()
621
self.assertFinished(client)
623
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
624
reference_format = self.get_repo_format()
625
network_name = reference_format.network_name()
626
transport = MemoryTransport()
627
transport.mkdir('quack')
628
transport = transport.clone('quack')
630
rich_response = 'yes'
634
subtree_response = 'yes'
636
subtree_response = 'no'
637
client = FakeClient(transport.base)
638
client.add_success_response(
639
'ok', '', rich_response, subtree_response, external_lookup,
641
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
643
result = bzrdir.open_repository()
645
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
647
self.assertIsInstance(result, RemoteRepository)
648
self.assertEqual(bzrdir, result.bzrdir)
649
self.assertEqual(rich_root, result._format.rich_root_data)
650
self.assertEqual(subtrees, result._format.supports_tree_reference)
652
def test_open_repository_sets_format_attributes(self):
653
self.check_open_repository(True, True)
654
self.check_open_repository(False, True)
655
self.check_open_repository(True, False)
656
self.check_open_repository(False, False)
657
self.check_open_repository(False, False, 'yes')
659
def test_old_server(self):
660
"""RemoteBzrDirFormat should fail to probe if the server version is too
663
self.assertRaises(errors.NotBranchError,
664
RemoteBzrDirFormat.probe_transport, OldServerTransport())
667
class TestBzrDirCreateBranch(TestRemote):
669
def test_backwards_compat(self):
670
self.setup_smart_server_with_call_log()
671
repo = self.make_repository('.')
672
self.reset_smart_call_log()
673
self.disable_verb('BzrDir.create_branch')
674
branch = repo.bzrdir.create_branch()
675
create_branch_call_count = len([call for call in self.hpss_calls if
676
call.call.method == 'BzrDir.create_branch'])
677
self.assertEqual(1, create_branch_call_count)
679
def test_current_server(self):
680
transport = self.get_transport('.')
681
transport = transport.clone('quack')
682
self.make_repository('quack')
683
client = FakeClient(transport.base)
684
reference_bzrdir_format = bzrdir.format_registry.get('default')()
685
reference_format = reference_bzrdir_format.get_branch_format()
686
network_name = reference_format.network_name()
687
reference_repo_fmt = reference_bzrdir_format.repository_format
688
reference_repo_name = reference_repo_fmt.network_name()
689
client.add_expected_call(
690
'BzrDir.create_branch', ('quack/', network_name),
691
'success', ('ok', network_name, '', 'no', 'no', 'yes',
692
reference_repo_name))
693
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
695
branch = a_bzrdir.create_branch()
696
# We should have got a remote branch
697
self.assertIsInstance(branch, remote.RemoteBranch)
698
# its format should have the settings from the response
699
format = branch._format
700
self.assertEqual(network_name, format.network_name())
703
class TestBzrDirCreateRepository(TestRemote):
705
def test_backwards_compat(self):
706
self.setup_smart_server_with_call_log()
707
bzrdir = self.make_bzrdir('.')
708
self.reset_smart_call_log()
709
self.disable_verb('BzrDir.create_repository')
710
repo = bzrdir.create_repository()
711
create_repo_call_count = len([call for call in self.hpss_calls if
712
call.call.method == 'BzrDir.create_repository'])
713
self.assertEqual(1, create_repo_call_count)
715
def test_current_server(self):
716
transport = self.get_transport('.')
717
transport = transport.clone('quack')
718
self.make_bzrdir('quack')
719
client = FakeClient(transport.base)
720
reference_bzrdir_format = bzrdir.format_registry.get('default')()
721
reference_format = reference_bzrdir_format.repository_format
722
network_name = reference_format.network_name()
723
client.add_expected_call(
724
'BzrDir.create_repository', ('quack/',
725
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
727
'success', ('ok', 'yes', 'yes', 'yes', network_name))
728
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
730
repo = a_bzrdir.create_repository()
731
# We should have got a remote repository
732
self.assertIsInstance(repo, remote.RemoteRepository)
733
# its format should have the settings from the response
734
format = repo._format
735
self.assertTrue(format.rich_root_data)
736
self.assertTrue(format.supports_tree_reference)
737
self.assertTrue(format.supports_external_lookups)
738
self.assertEqual(network_name, format.network_name())
741
class TestBzrDirOpenRepository(TestRemote):
743
def test_backwards_compat_1_2_3(self):
744
# fallback all the way to the first version.
745
reference_format = self.get_repo_format()
746
network_name = reference_format.network_name()
747
server_url = 'bzr://example.com/'
748
self.permit_url(server_url)
749
client = FakeClient(server_url)
750
client.add_unknown_method_response('BzrDir.find_repositoryV3')
751
client.add_unknown_method_response('BzrDir.find_repositoryV2')
752
client.add_success_response('ok', '', 'no', 'no')
753
# A real repository instance will be created to determine the network
755
client.add_success_response_with_body(
756
"Bazaar-NG meta directory, format 1\n", 'ok')
757
client.add_success_response_with_body(
758
reference_format.get_format_string(), 'ok')
759
# PackRepository wants to do a stat
760
client.add_success_response('stat', '0', '65535')
761
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
763
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
765
repo = bzrdir.open_repository()
767
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
768
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
769
('call', 'BzrDir.find_repository', ('quack/',)),
770
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
771
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
772
('call', 'stat', ('/quack/.bzr/repository',)),
775
self.assertEqual(network_name, repo._format.network_name())
777
def test_backwards_compat_2(self):
778
# fallback to find_repositoryV2
779
reference_format = self.get_repo_format()
780
network_name = reference_format.network_name()
781
server_url = 'bzr://example.com/'
782
self.permit_url(server_url)
783
client = FakeClient(server_url)
784
client.add_unknown_method_response('BzrDir.find_repositoryV3')
785
client.add_success_response('ok', '', 'no', 'no', 'no')
786
# A real repository instance will be created to determine the network
788
client.add_success_response_with_body(
789
"Bazaar-NG meta directory, format 1\n", 'ok')
790
client.add_success_response_with_body(
791
reference_format.get_format_string(), 'ok')
792
# PackRepository wants to do a stat
793
client.add_success_response('stat', '0', '65535')
794
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
796
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
798
repo = bzrdir.open_repository()
800
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
801
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
802
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
803
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
804
('call', 'stat', ('/quack/.bzr/repository',)),
807
self.assertEqual(network_name, repo._format.network_name())
809
def test_current_server(self):
810
reference_format = self.get_repo_format()
811
network_name = reference_format.network_name()
812
transport = MemoryTransport()
813
transport.mkdir('quack')
814
transport = transport.clone('quack')
815
client = FakeClient(transport.base)
816
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
817
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
819
repo = bzrdir.open_repository()
821
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
823
self.assertEqual(network_name, repo._format.network_name())
826
class TestBzrDirFormatInitializeEx(TestRemote):
828
def test_success(self):
829
"""Simple test for typical successful call."""
830
fmt = bzrdir.RemoteBzrDirFormat()
831
default_format_name = BzrDirFormat.get_default_format().network_name()
832
transport = self.get_transport()
833
client = FakeClient(transport.base)
834
client.add_expected_call(
835
'BzrDirFormat.initialize_ex_1.16',
836
(default_format_name, 'path', 'False', 'False', 'False', '',
837
'', '', '', 'False'),
839
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
840
'bzrdir fmt', 'False', '', '', 'repo lock token'))
841
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
842
# it's currently hard to test that without supplying a real remote
843
# transport connected to a real server.
844
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
845
transport, False, False, False, None, None, None, None, False)
846
self.assertFinished(client)
848
def test_error(self):
849
"""Error responses are translated, e.g. 'PermissionDenied' raises the
850
corresponding error from the client.
852
fmt = bzrdir.RemoteBzrDirFormat()
853
default_format_name = BzrDirFormat.get_default_format().network_name()
854
transport = self.get_transport()
855
client = FakeClient(transport.base)
856
client.add_expected_call(
857
'BzrDirFormat.initialize_ex_1.16',
858
(default_format_name, 'path', 'False', 'False', 'False', '',
859
'', '', '', 'False'),
861
('PermissionDenied', 'path', 'extra info'))
862
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
863
# it's currently hard to test that without supplying a real remote
864
# transport connected to a real server.
865
err = self.assertRaises(errors.PermissionDenied,
866
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
867
False, False, False, None, None, None, None, False)
868
self.assertEqual('path', err.path)
869
self.assertEqual(': extra info', err.extra)
870
self.assertFinished(client)
872
def test_error_from_real_server(self):
873
"""Integration test for error translation."""
874
transport = self.make_smart_server('foo')
875
transport = transport.clone('no-such-path')
876
fmt = bzrdir.RemoteBzrDirFormat()
877
err = self.assertRaises(errors.NoSuchFile,
878
fmt.initialize_on_transport_ex, transport, create_prefix=False)
881
class OldSmartClient(object):
882
"""A fake smart client for test_old_version that just returns a version one
883
response to the 'hello' (query version) command.
886
def get_request(self):
887
input_file = StringIO('ok\x011\n')
888
output_file = StringIO()
889
client_medium = medium.SmartSimplePipesClientMedium(
890
input_file, output_file)
891
return medium.SmartClientStreamMediumRequest(client_medium)
893
def protocol_version(self):
897
class OldServerTransport(object):
898
"""A fake transport for test_old_server that reports it's smart server
899
protocol version as version one.
905
def get_smart_client(self):
906
return OldSmartClient()
909
class RemoteBzrDirTestCase(TestRemote):
911
def make_remote_bzrdir(self, transport, client):
912
"""Make a RemotebzrDir using 'client' as the _client."""
913
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
917
class RemoteBranchTestCase(RemoteBzrDirTestCase):
919
def lock_remote_branch(self, branch):
920
"""Trick a RemoteBranch into thinking it is locked."""
921
branch._lock_mode = 'w'
922
branch._lock_count = 2
923
branch._lock_token = 'branch token'
924
branch._repo_lock_token = 'repo token'
925
branch.repository._lock_mode = 'w'
926
branch.repository._lock_count = 2
927
branch.repository._lock_token = 'repo token'
929
def make_remote_branch(self, transport, client):
930
"""Make a RemoteBranch using 'client' as its _SmartClient.
932
A RemoteBzrDir and RemoteRepository will also be created to fill out
933
the RemoteBranch, albeit with stub values for some of their attributes.
935
# we do not want bzrdir to make any remote calls, so use False as its
936
# _client. If it tries to make a remote call, this will fail
938
bzrdir = self.make_remote_bzrdir(transport, False)
939
repo = RemoteRepository(bzrdir, None, _client=client)
940
branch_format = self.get_branch_format()
941
format = RemoteBranchFormat(network_name=branch_format.network_name())
942
return RemoteBranch(bzrdir, repo, _client=client, format=format)
945
class TestBranchGetParent(RemoteBranchTestCase):
947
def test_no_parent(self):
948
# in an empty branch we decode the response properly
949
transport = MemoryTransport()
950
client = FakeClient(transport.base)
951
client.add_expected_call(
952
'Branch.get_stacked_on_url', ('quack/',),
953
'error', ('NotStacked',))
954
client.add_expected_call(
955
'Branch.get_parent', ('quack/',),
957
transport.mkdir('quack')
958
transport = transport.clone('quack')
959
branch = self.make_remote_branch(transport, client)
960
result = branch.get_parent()
961
self.assertFinished(client)
962
self.assertEqual(None, result)
964
def test_parent_relative(self):
965
transport = MemoryTransport()
966
client = FakeClient(transport.base)
967
client.add_expected_call(
968
'Branch.get_stacked_on_url', ('kwaak/',),
969
'error', ('NotStacked',))
970
client.add_expected_call(
971
'Branch.get_parent', ('kwaak/',),
972
'success', ('../foo/',))
973
transport.mkdir('kwaak')
974
transport = transport.clone('kwaak')
975
branch = self.make_remote_branch(transport, client)
976
result = branch.get_parent()
977
self.assertEqual(transport.clone('../foo').base, result)
979
def test_parent_absolute(self):
980
transport = MemoryTransport()
981
client = FakeClient(transport.base)
982
client.add_expected_call(
983
'Branch.get_stacked_on_url', ('kwaak/',),
984
'error', ('NotStacked',))
985
client.add_expected_call(
986
'Branch.get_parent', ('kwaak/',),
987
'success', ('http://foo/',))
988
transport.mkdir('kwaak')
989
transport = transport.clone('kwaak')
990
branch = self.make_remote_branch(transport, client)
991
result = branch.get_parent()
992
self.assertEqual('http://foo/', result)
993
self.assertFinished(client)
996
class TestBranchSetParentLocation(RemoteBranchTestCase):
998
def test_no_parent(self):
999
# We call the verb when setting parent to None
1000
transport = MemoryTransport()
1001
client = FakeClient(transport.base)
1002
client.add_expected_call(
1003
'Branch.get_stacked_on_url', ('quack/',),
1004
'error', ('NotStacked',))
1005
client.add_expected_call(
1006
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1008
transport.mkdir('quack')
1009
transport = transport.clone('quack')
1010
branch = self.make_remote_branch(transport, client)
1011
branch._lock_token = 'b'
1012
branch._repo_lock_token = 'r'
1013
branch._set_parent_location(None)
1014
self.assertFinished(client)
1016
def test_parent(self):
1017
transport = MemoryTransport()
1018
client = FakeClient(transport.base)
1019
client.add_expected_call(
1020
'Branch.get_stacked_on_url', ('kwaak/',),
1021
'error', ('NotStacked',))
1022
client.add_expected_call(
1023
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1025
transport.mkdir('kwaak')
1026
transport = transport.clone('kwaak')
1027
branch = self.make_remote_branch(transport, client)
1028
branch._lock_token = 'b'
1029
branch._repo_lock_token = 'r'
1030
branch._set_parent_location('foo')
1031
self.assertFinished(client)
1033
def test_backwards_compat(self):
1034
self.setup_smart_server_with_call_log()
1035
branch = self.make_branch('.')
1036
self.reset_smart_call_log()
1037
verb = 'Branch.set_parent_location'
1038
self.disable_verb(verb)
1039
branch.set_parent('http://foo/')
1040
self.assertLength(12, self.hpss_calls)
1043
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1045
def test_backwards_compat(self):
1046
self.setup_smart_server_with_call_log()
1047
branch = self.make_branch('.')
1048
self.reset_smart_call_log()
1049
verb = 'Branch.get_tags_bytes'
1050
self.disable_verb(verb)
1051
branch.tags.get_tag_dict()
1052
call_count = len([call for call in self.hpss_calls if
1053
call.call.method == verb])
1054
self.assertEqual(1, call_count)
1056
def test_trivial(self):
1057
transport = MemoryTransport()
1058
client = FakeClient(transport.base)
1059
client.add_expected_call(
1060
'Branch.get_stacked_on_url', ('quack/',),
1061
'error', ('NotStacked',))
1062
client.add_expected_call(
1063
'Branch.get_tags_bytes', ('quack/',),
1065
transport.mkdir('quack')
1066
transport = transport.clone('quack')
1067
branch = self.make_remote_branch(transport, client)
1068
result = branch.tags.get_tag_dict()
1069
self.assertFinished(client)
1070
self.assertEqual({}, result)
1073
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1075
def test_trivial(self):
1076
transport = MemoryTransport()
1077
client = FakeClient(transport.base)
1078
client.add_expected_call(
1079
'Branch.get_stacked_on_url', ('quack/',),
1080
'error', ('NotStacked',))
1081
client.add_expected_call(
1082
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1084
transport.mkdir('quack')
1085
transport = transport.clone('quack')
1086
branch = self.make_remote_branch(transport, client)
1087
self.lock_remote_branch(branch)
1088
branch._set_tags_bytes('tags bytes')
1089
self.assertFinished(client)
1090
self.assertEqual('tags bytes', client._calls[-1][-1])
1092
def test_backwards_compatible(self):
1093
transport = MemoryTransport()
1094
client = FakeClient(transport.base)
1095
client.add_expected_call(
1096
'Branch.get_stacked_on_url', ('quack/',),
1097
'error', ('NotStacked',))
1098
client.add_expected_call(
1099
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1100
'unknown', ('Branch.set_tags_bytes',))
1101
transport.mkdir('quack')
1102
transport = transport.clone('quack')
1103
branch = self.make_remote_branch(transport, client)
1104
self.lock_remote_branch(branch)
1105
class StubRealBranch(object):
1108
def _set_tags_bytes(self, bytes):
1109
self.calls.append(('set_tags_bytes', bytes))
1110
real_branch = StubRealBranch()
1111
branch._real_branch = real_branch
1112
branch._set_tags_bytes('tags bytes')
1113
# Call a second time, to exercise the 'remote version already inferred'
1115
branch._set_tags_bytes('tags bytes')
1116
self.assertFinished(client)
1118
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1121
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1123
def test_empty_branch(self):
1124
# in an empty branch we decode the response properly
1125
transport = MemoryTransport()
1126
client = FakeClient(transport.base)
1127
client.add_expected_call(
1128
'Branch.get_stacked_on_url', ('quack/',),
1129
'error', ('NotStacked',))
1130
client.add_expected_call(
1131
'Branch.last_revision_info', ('quack/',),
1132
'success', ('ok', '0', 'null:'))
1133
transport.mkdir('quack')
1134
transport = transport.clone('quack')
1135
branch = self.make_remote_branch(transport, client)
1136
result = branch.last_revision_info()
1137
self.assertFinished(client)
1138
self.assertEqual((0, NULL_REVISION), result)
1140
def test_non_empty_branch(self):
1141
# in a non-empty branch we also decode the response properly
1142
revid = u'\xc8'.encode('utf8')
1143
transport = MemoryTransport()
1144
client = FakeClient(transport.base)
1145
client.add_expected_call(
1146
'Branch.get_stacked_on_url', ('kwaak/',),
1147
'error', ('NotStacked',))
1148
client.add_expected_call(
1149
'Branch.last_revision_info', ('kwaak/',),
1150
'success', ('ok', '2', revid))
1151
transport.mkdir('kwaak')
1152
transport = transport.clone('kwaak')
1153
branch = self.make_remote_branch(transport, client)
1154
result = branch.last_revision_info()
1155
self.assertEqual((2, revid), result)
1158
class TestBranch_get_stacked_on_url(TestRemote):
1159
"""Test Branch._get_stacked_on_url rpc"""
1161
def test_get_stacked_on_invalid_url(self):
1162
# test that asking for a stacked on url the server can't access works.
1163
# This isn't perfect, but then as we're in the same process there
1164
# really isn't anything we can do to be 100% sure that the server
1165
# doesn't just open in - this test probably needs to be rewritten using
1166
# a spawn()ed server.
1167
stacked_branch = self.make_branch('stacked', format='1.9')
1168
memory_branch = self.make_branch('base', format='1.9')
1169
vfs_url = self.get_vfs_only_url('base')
1170
stacked_branch.set_stacked_on_url(vfs_url)
1171
transport = stacked_branch.bzrdir.root_transport
1172
client = FakeClient(transport.base)
1173
client.add_expected_call(
1174
'Branch.get_stacked_on_url', ('stacked/',),
1175
'success', ('ok', vfs_url))
1176
# XXX: Multiple calls are bad, this second call documents what is
1178
client.add_expected_call(
1179
'Branch.get_stacked_on_url', ('stacked/',),
1180
'success', ('ok', vfs_url))
1181
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1183
repo_fmt = remote.RemoteRepositoryFormat()
1184
repo_fmt._custom_format = stacked_branch.repository._format
1185
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1187
result = branch.get_stacked_on_url()
1188
self.assertEqual(vfs_url, result)
1190
def test_backwards_compatible(self):
1191
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1192
base_branch = self.make_branch('base', format='1.6')
1193
stacked_branch = self.make_branch('stacked', format='1.6')
1194
stacked_branch.set_stacked_on_url('../base')
1195
client = FakeClient(self.get_url())
1196
branch_network_name = self.get_branch_format().network_name()
1197
client.add_expected_call(
1198
'BzrDir.open_branchV3', ('stacked/',),
1199
'success', ('branch', branch_network_name))
1200
client.add_expected_call(
1201
'BzrDir.find_repositoryV3', ('stacked/',),
1202
'success', ('ok', '', 'no', 'no', 'yes',
1203
stacked_branch.repository._format.network_name()))
1204
# called twice, once from constructor and then again by us
1205
client.add_expected_call(
1206
'Branch.get_stacked_on_url', ('stacked/',),
1207
'unknown', ('Branch.get_stacked_on_url',))
1208
client.add_expected_call(
1209
'Branch.get_stacked_on_url', ('stacked/',),
1210
'unknown', ('Branch.get_stacked_on_url',))
1211
# this will also do vfs access, but that goes direct to the transport
1212
# and isn't seen by the FakeClient.
1213
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1214
remote.RemoteBzrDirFormat(), _client=client)
1215
branch = bzrdir.open_branch()
1216
result = branch.get_stacked_on_url()
1217
self.assertEqual('../base', result)
1218
self.assertFinished(client)
1219
# it's in the fallback list both for the RemoteRepository and its vfs
1221
self.assertEqual(1, len(branch.repository._fallback_repositories))
1223
len(branch.repository._real_repository._fallback_repositories))
1225
def test_get_stacked_on_real_branch(self):
1226
base_branch = self.make_branch('base')
1227
stacked_branch = self.make_branch('stacked')
1228
stacked_branch.set_stacked_on_url('../base')
1229
reference_format = self.get_repo_format()
1230
network_name = reference_format.network_name()
1231
client = FakeClient(self.get_url())
1232
branch_network_name = self.get_branch_format().network_name()
1233
client.add_expected_call(
1234
'BzrDir.open_branchV3', ('stacked/',),
1235
'success', ('branch', branch_network_name))
1236
client.add_expected_call(
1237
'BzrDir.find_repositoryV3', ('stacked/',),
1238
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1239
# called twice, once from constructor and then again by us
1240
client.add_expected_call(
1241
'Branch.get_stacked_on_url', ('stacked/',),
1242
'success', ('ok', '../base'))
1243
client.add_expected_call(
1244
'Branch.get_stacked_on_url', ('stacked/',),
1245
'success', ('ok', '../base'))
1246
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1247
remote.RemoteBzrDirFormat(), _client=client)
1248
branch = bzrdir.open_branch()
1249
result = branch.get_stacked_on_url()
1250
self.assertEqual('../base', result)
1251
self.assertFinished(client)
1252
# it's in the fallback list both for the RemoteRepository.
1253
self.assertEqual(1, len(branch.repository._fallback_repositories))
1254
# And we haven't had to construct a real repository.
1255
self.assertEqual(None, branch.repository._real_repository)
1258
class TestBranchSetLastRevision(RemoteBranchTestCase):
1260
def test_set_empty(self):
1261
# set_revision_history([]) is translated to calling
1262
# Branch.set_last_revision(path, '') on the wire.
1263
transport = MemoryTransport()
1264
transport.mkdir('branch')
1265
transport = transport.clone('branch')
1267
client = FakeClient(transport.base)
1268
client.add_expected_call(
1269
'Branch.get_stacked_on_url', ('branch/',),
1270
'error', ('NotStacked',))
1271
client.add_expected_call(
1272
'Branch.lock_write', ('branch/', '', ''),
1273
'success', ('ok', 'branch token', 'repo token'))
1274
client.add_expected_call(
1275
'Branch.last_revision_info',
1277
'success', ('ok', '0', 'null:'))
1278
client.add_expected_call(
1279
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1281
client.add_expected_call(
1282
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1284
branch = self.make_remote_branch(transport, client)
1285
# This is a hack to work around the problem that RemoteBranch currently
1286
# unnecessarily invokes _ensure_real upon a call to lock_write.
1287
branch._ensure_real = lambda: None
1289
result = branch.set_revision_history([])
1291
self.assertEqual(None, result)
1292
self.assertFinished(client)
1294
def test_set_nonempty(self):
1295
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1296
# Branch.set_last_revision(path, rev-idN) on the wire.
1297
transport = MemoryTransport()
1298
transport.mkdir('branch')
1299
transport = transport.clone('branch')
1301
client = FakeClient(transport.base)
1302
client.add_expected_call(
1303
'Branch.get_stacked_on_url', ('branch/',),
1304
'error', ('NotStacked',))
1305
client.add_expected_call(
1306
'Branch.lock_write', ('branch/', '', ''),
1307
'success', ('ok', 'branch token', 'repo token'))
1308
client.add_expected_call(
1309
'Branch.last_revision_info',
1311
'success', ('ok', '0', 'null:'))
1313
encoded_body = bz2.compress('\n'.join(lines))
1314
client.add_success_response_with_body(encoded_body, 'ok')
1315
client.add_expected_call(
1316
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1318
client.add_expected_call(
1319
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1321
branch = self.make_remote_branch(transport, client)
1322
# This is a hack to work around the problem that RemoteBranch currently
1323
# unnecessarily invokes _ensure_real upon a call to lock_write.
1324
branch._ensure_real = lambda: None
1325
# Lock the branch, reset the record of remote calls.
1327
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1329
self.assertEqual(None, result)
1330
self.assertFinished(client)
1332
def test_no_such_revision(self):
1333
transport = MemoryTransport()
1334
transport.mkdir('branch')
1335
transport = transport.clone('branch')
1336
# A response of 'NoSuchRevision' is translated into an exception.
1337
client = FakeClient(transport.base)
1338
client.add_expected_call(
1339
'Branch.get_stacked_on_url', ('branch/',),
1340
'error', ('NotStacked',))
1341
client.add_expected_call(
1342
'Branch.lock_write', ('branch/', '', ''),
1343
'success', ('ok', 'branch token', 'repo token'))
1344
client.add_expected_call(
1345
'Branch.last_revision_info',
1347
'success', ('ok', '0', 'null:'))
1348
# get_graph calls to construct the revision history, for the set_rh
1351
encoded_body = bz2.compress('\n'.join(lines))
1352
client.add_success_response_with_body(encoded_body, 'ok')
1353
client.add_expected_call(
1354
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1355
'error', ('NoSuchRevision', 'rev-id'))
1356
client.add_expected_call(
1357
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1360
branch = self.make_remote_branch(transport, client)
1363
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1365
self.assertFinished(client)
1367
def test_tip_change_rejected(self):
1368
"""TipChangeRejected responses cause a TipChangeRejected exception to
1371
transport = MemoryTransport()
1372
transport.mkdir('branch')
1373
transport = transport.clone('branch')
1374
client = FakeClient(transport.base)
1375
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1376
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1377
client.add_expected_call(
1378
'Branch.get_stacked_on_url', ('branch/',),
1379
'error', ('NotStacked',))
1380
client.add_expected_call(
1381
'Branch.lock_write', ('branch/', '', ''),
1382
'success', ('ok', 'branch token', 'repo token'))
1383
client.add_expected_call(
1384
'Branch.last_revision_info',
1386
'success', ('ok', '0', 'null:'))
1388
encoded_body = bz2.compress('\n'.join(lines))
1389
client.add_success_response_with_body(encoded_body, 'ok')
1390
client.add_expected_call(
1391
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1392
'error', ('TipChangeRejected', rejection_msg_utf8))
1393
client.add_expected_call(
1394
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1396
branch = self.make_remote_branch(transport, client)
1397
branch._ensure_real = lambda: None
1399
# The 'TipChangeRejected' error response triggered by calling
1400
# set_revision_history causes a TipChangeRejected exception.
1401
err = self.assertRaises(
1402
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1403
# The UTF-8 message from the response has been decoded into a unicode
1405
self.assertIsInstance(err.msg, unicode)
1406
self.assertEqual(rejection_msg_unicode, err.msg)
1408
self.assertFinished(client)
1411
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1413
def test_set_last_revision_info(self):
1414
# set_last_revision_info(num, 'rev-id') is translated to calling
1415
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1416
transport = MemoryTransport()
1417
transport.mkdir('branch')
1418
transport = transport.clone('branch')
1419
client = FakeClient(transport.base)
1420
# get_stacked_on_url
1421
client.add_error_response('NotStacked')
1423
client.add_success_response('ok', 'branch token', 'repo token')
1424
# query the current revision
1425
client.add_success_response('ok', '0', 'null:')
1427
client.add_success_response('ok')
1429
client.add_success_response('ok')
1431
branch = self.make_remote_branch(transport, client)
1432
# Lock the branch, reset the record of remote calls.
1435
result = branch.set_last_revision_info(1234, 'a-revision-id')
1437
[('call', 'Branch.last_revision_info', ('branch/',)),
1438
('call', 'Branch.set_last_revision_info',
1439
('branch/', 'branch token', 'repo token',
1440
'1234', 'a-revision-id'))],
1442
self.assertEqual(None, result)
1444
def test_no_such_revision(self):
1445
# A response of 'NoSuchRevision' is translated into an exception.
1446
transport = MemoryTransport()
1447
transport.mkdir('branch')
1448
transport = transport.clone('branch')
1449
client = FakeClient(transport.base)
1450
# get_stacked_on_url
1451
client.add_error_response('NotStacked')
1453
client.add_success_response('ok', 'branch token', 'repo token')
1455
client.add_error_response('NoSuchRevision', 'revid')
1457
client.add_success_response('ok')
1459
branch = self.make_remote_branch(transport, client)
1460
# Lock the branch, reset the record of remote calls.
1465
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1468
def test_backwards_compatibility(self):
1469
"""If the server does not support the Branch.set_last_revision_info
1470
verb (which is new in 1.4), then the client falls back to VFS methods.
1472
# This test is a little messy. Unlike most tests in this file, it
1473
# doesn't purely test what a Remote* object sends over the wire, and
1474
# how it reacts to responses from the wire. It instead relies partly
1475
# on asserting that the RemoteBranch will call
1476
# self._real_branch.set_last_revision_info(...).
1478
# First, set up our RemoteBranch with a FakeClient that raises
1479
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1480
transport = MemoryTransport()
1481
transport.mkdir('branch')
1482
transport = transport.clone('branch')
1483
client = FakeClient(transport.base)
1484
client.add_expected_call(
1485
'Branch.get_stacked_on_url', ('branch/',),
1486
'error', ('NotStacked',))
1487
client.add_expected_call(
1488
'Branch.last_revision_info',
1490
'success', ('ok', '0', 'null:'))
1491
client.add_expected_call(
1492
'Branch.set_last_revision_info',
1493
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1494
'unknown', 'Branch.set_last_revision_info')
1496
branch = self.make_remote_branch(transport, client)
1497
class StubRealBranch(object):
1500
def set_last_revision_info(self, revno, revision_id):
1502
('set_last_revision_info', revno, revision_id))
1503
def _clear_cached_state(self):
1505
real_branch = StubRealBranch()
1506
branch._real_branch = real_branch
1507
self.lock_remote_branch(branch)
1509
# Call set_last_revision_info, and verify it behaved as expected.
1510
result = branch.set_last_revision_info(1234, 'a-revision-id')
1512
[('set_last_revision_info', 1234, 'a-revision-id')],
1514
self.assertFinished(client)
1516
def test_unexpected_error(self):
1517
# If the server sends an error the client doesn't understand, it gets
1518
# turned into an UnknownErrorFromSmartServer, which is presented as a
1519
# non-internal error to the user.
1520
transport = MemoryTransport()
1521
transport.mkdir('branch')
1522
transport = transport.clone('branch')
1523
client = FakeClient(transport.base)
1524
# get_stacked_on_url
1525
client.add_error_response('NotStacked')
1527
client.add_success_response('ok', 'branch token', 'repo token')
1529
client.add_error_response('UnexpectedError')
1531
client.add_success_response('ok')
1533
branch = self.make_remote_branch(transport, client)
1534
# Lock the branch, reset the record of remote calls.
1538
err = self.assertRaises(
1539
errors.UnknownErrorFromSmartServer,
1540
branch.set_last_revision_info, 123, 'revid')
1541
self.assertEqual(('UnexpectedError',), err.error_tuple)
1544
def test_tip_change_rejected(self):
1545
"""TipChangeRejected responses cause a TipChangeRejected exception to
1548
transport = MemoryTransport()
1549
transport.mkdir('branch')
1550
transport = transport.clone('branch')
1551
client = FakeClient(transport.base)
1552
# get_stacked_on_url
1553
client.add_error_response('NotStacked')
1555
client.add_success_response('ok', 'branch token', 'repo token')
1557
client.add_error_response('TipChangeRejected', 'rejection message')
1559
client.add_success_response('ok')
1561
branch = self.make_remote_branch(transport, client)
1562
# Lock the branch, reset the record of remote calls.
1564
self.addCleanup(branch.unlock)
1567
# The 'TipChangeRejected' error response triggered by calling
1568
# set_last_revision_info causes a TipChangeRejected exception.
1569
err = self.assertRaises(
1570
errors.TipChangeRejected,
1571
branch.set_last_revision_info, 123, 'revid')
1572
self.assertEqual('rejection message', err.msg)
1575
class TestBranchGetSetConfig(RemoteBranchTestCase):
1577
def test_get_branch_conf(self):
1578
# in an empty branch we decode the response properly
1579
client = FakeClient()
1580
client.add_expected_call(
1581
'Branch.get_stacked_on_url', ('memory:///',),
1582
'error', ('NotStacked',),)
1583
client.add_success_response_with_body('# config file body', 'ok')
1584
transport = MemoryTransport()
1585
branch = self.make_remote_branch(transport, client)
1586
config = branch.get_config()
1587
config.has_explicit_nickname()
1589
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1590
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1593
def test_get_multi_line_branch_conf(self):
1594
# Make sure that multiple-line branch.conf files are supported
1596
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1597
client = FakeClient()
1598
client.add_expected_call(
1599
'Branch.get_stacked_on_url', ('memory:///',),
1600
'error', ('NotStacked',),)
1601
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1602
transport = MemoryTransport()
1603
branch = self.make_remote_branch(transport, client)
1604
config = branch.get_config()
1605
self.assertEqual(u'2', config.get_user_option('b'))
1607
def test_set_option(self):
1608
client = FakeClient()
1609
client.add_expected_call(
1610
'Branch.get_stacked_on_url', ('memory:///',),
1611
'error', ('NotStacked',),)
1612
client.add_expected_call(
1613
'Branch.lock_write', ('memory:///', '', ''),
1614
'success', ('ok', 'branch token', 'repo token'))
1615
client.add_expected_call(
1616
'Branch.set_config_option', ('memory:///', 'branch token',
1617
'repo token', 'foo', 'bar', ''),
1619
client.add_expected_call(
1620
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1622
transport = MemoryTransport()
1623
branch = self.make_remote_branch(transport, client)
1625
config = branch._get_config()
1626
config.set_option('foo', 'bar')
1628
self.assertFinished(client)
1630
def test_backwards_compat_set_option(self):
1631
self.setup_smart_server_with_call_log()
1632
branch = self.make_branch('.')
1633
verb = 'Branch.set_config_option'
1634
self.disable_verb(verb)
1636
self.addCleanup(branch.unlock)
1637
self.reset_smart_call_log()
1638
branch._get_config().set_option('value', 'name')
1639
self.assertLength(10, self.hpss_calls)
1640
self.assertEqual('value', branch._get_config().get_option('name'))
1643
class TestBranchLockWrite(RemoteBranchTestCase):
1645
def test_lock_write_unlockable(self):
1646
transport = MemoryTransport()
1647
client = FakeClient(transport.base)
1648
client.add_expected_call(
1649
'Branch.get_stacked_on_url', ('quack/',),
1650
'error', ('NotStacked',),)
1651
client.add_expected_call(
1652
'Branch.lock_write', ('quack/', '', ''),
1653
'error', ('UnlockableTransport',))
1654
transport.mkdir('quack')
1655
transport = transport.clone('quack')
1656
branch = self.make_remote_branch(transport, client)
1657
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1658
self.assertFinished(client)
1661
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1663
def test__get_config(self):
1664
client = FakeClient()
1665
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1666
transport = MemoryTransport()
1667
bzrdir = self.make_remote_bzrdir(transport, client)
1668
config = bzrdir.get_config()
1669
self.assertEqual('/', config.get_default_stack_on())
1671
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1674
def test_set_option_uses_vfs(self):
1675
self.setup_smart_server_with_call_log()
1676
bzrdir = self.make_bzrdir('.')
1677
self.reset_smart_call_log()
1678
config = bzrdir.get_config()
1679
config.set_default_stack_on('/')
1680
self.assertLength(3, self.hpss_calls)
1682
def test_backwards_compat_get_option(self):
1683
self.setup_smart_server_with_call_log()
1684
bzrdir = self.make_bzrdir('.')
1685
verb = 'BzrDir.get_config_file'
1686
self.disable_verb(verb)
1687
self.reset_smart_call_log()
1688
self.assertEqual(None,
1689
bzrdir._get_config().get_option('default_stack_on'))
1690
self.assertLength(3, self.hpss_calls)
1693
class TestTransportIsReadonly(tests.TestCase):
1695
def test_true(self):
1696
client = FakeClient()
1697
client.add_success_response('yes')
1698
transport = RemoteTransport('bzr://example.com/', medium=False,
1700
self.assertEqual(True, transport.is_readonly())
1702
[('call', 'Transport.is_readonly', ())],
1705
def test_false(self):
1706
client = FakeClient()
1707
client.add_success_response('no')
1708
transport = RemoteTransport('bzr://example.com/', medium=False,
1710
self.assertEqual(False, transport.is_readonly())
1712
[('call', 'Transport.is_readonly', ())],
1715
def test_error_from_old_server(self):
1716
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1718
Clients should treat it as a "no" response, because is_readonly is only
1719
advisory anyway (a transport could be read-write, but then the
1720
underlying filesystem could be readonly anyway).
1722
client = FakeClient()
1723
client.add_unknown_method_response('Transport.is_readonly')
1724
transport = RemoteTransport('bzr://example.com/', medium=False,
1726
self.assertEqual(False, transport.is_readonly())
1728
[('call', 'Transport.is_readonly', ())],
1732
class TestTransportMkdir(tests.TestCase):
1734
def test_permissiondenied(self):
1735
client = FakeClient()
1736
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1737
transport = RemoteTransport('bzr://example.com/', medium=False,
1739
exc = self.assertRaises(
1740
errors.PermissionDenied, transport.mkdir, 'client path')
1741
expected_error = errors.PermissionDenied('/client path', 'extra')
1742
self.assertEqual(expected_error, exc)
1745
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1747
def test_defaults_to_none(self):
1748
t = RemoteSSHTransport('bzr+ssh://example.com')
1749
self.assertIs(None, t._get_credentials()[0])
1751
def test_uses_authentication_config(self):
1752
conf = config.AuthenticationConfig()
1753
conf._get_config().update(
1754
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1757
t = RemoteSSHTransport('bzr+ssh://example.com')
1758
self.assertEqual('bar', t._get_credentials()[0])
1761
class TestRemoteRepository(TestRemote):
1762
"""Base for testing RemoteRepository protocol usage.
1764
These tests contain frozen requests and responses. We want any changes to
1765
what is sent or expected to be require a thoughtful update to these tests
1766
because they might break compatibility with different-versioned servers.
1769
def setup_fake_client_and_repository(self, transport_path):
1770
"""Create the fake client and repository for testing with.
1772
There's no real server here; we just have canned responses sent
1775
:param transport_path: Path below the root of the MemoryTransport
1776
where the repository will be created.
1778
transport = MemoryTransport()
1779
transport.mkdir(transport_path)
1780
client = FakeClient(transport.base)
1781
transport = transport.clone(transport_path)
1782
# we do not want bzrdir to make any remote calls
1783
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1785
repo = RemoteRepository(bzrdir, None, _client=client)
1789
def remoted_description(format):
1790
return 'Remote: ' + format.get_format_description()
1793
class TestBranchFormat(tests.TestCase):
1795
def test_get_format_description(self):
1796
remote_format = RemoteBranchFormat()
1797
real_format = branch.BranchFormat.get_default_format()
1798
remote_format._network_name = real_format.network_name()
1799
self.assertEqual(remoted_description(real_format),
1800
remote_format.get_format_description())
1803
class TestRepositoryFormat(TestRemoteRepository):
1805
def test_fast_delta(self):
1806
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1807
true_format = RemoteRepositoryFormat()
1808
true_format._network_name = true_name
1809
self.assertEqual(True, true_format.fast_deltas)
1810
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1811
false_format = RemoteRepositoryFormat()
1812
false_format._network_name = false_name
1813
self.assertEqual(False, false_format.fast_deltas)
1815
def test_get_format_description(self):
1816
remote_repo_format = RemoteRepositoryFormat()
1817
real_format = repository.RepositoryFormat.get_default_format()
1818
remote_repo_format._network_name = real_format.network_name()
1819
self.assertEqual(remoted_description(real_format),
1820
remote_repo_format.get_format_description())
1823
class TestRepositoryGatherStats(TestRemoteRepository):
1825
def test_revid_none(self):
1826
# ('ok',), body with revisions and size
1827
transport_path = 'quack'
1828
repo, client = self.setup_fake_client_and_repository(transport_path)
1829
client.add_success_response_with_body(
1830
'revisions: 2\nsize: 18\n', 'ok')
1831
result = repo.gather_stats(None)
1833
[('call_expecting_body', 'Repository.gather_stats',
1834
('quack/','','no'))],
1836
self.assertEqual({'revisions': 2, 'size': 18}, result)
1838
def test_revid_no_committers(self):
1839
# ('ok',), body without committers
1840
body = ('firstrev: 123456.300 3600\n'
1841
'latestrev: 654231.400 0\n'
1844
transport_path = 'quick'
1845
revid = u'\xc8'.encode('utf8')
1846
repo, client = self.setup_fake_client_and_repository(transport_path)
1847
client.add_success_response_with_body(body, 'ok')
1848
result = repo.gather_stats(revid)
1850
[('call_expecting_body', 'Repository.gather_stats',
1851
('quick/', revid, 'no'))],
1853
self.assertEqual({'revisions': 2, 'size': 18,
1854
'firstrev': (123456.300, 3600),
1855
'latestrev': (654231.400, 0),},
1858
def test_revid_with_committers(self):
1859
# ('ok',), body with committers
1860
body = ('committers: 128\n'
1861
'firstrev: 123456.300 3600\n'
1862
'latestrev: 654231.400 0\n'
1865
transport_path = 'buick'
1866
revid = u'\xc8'.encode('utf8')
1867
repo, client = self.setup_fake_client_and_repository(transport_path)
1868
client.add_success_response_with_body(body, 'ok')
1869
result = repo.gather_stats(revid, True)
1871
[('call_expecting_body', 'Repository.gather_stats',
1872
('buick/', revid, 'yes'))],
1874
self.assertEqual({'revisions': 2, 'size': 18,
1876
'firstrev': (123456.300, 3600),
1877
'latestrev': (654231.400, 0),},
1881
class TestRepositoryGetGraph(TestRemoteRepository):
1883
def test_get_graph(self):
1884
# get_graph returns a graph with a custom parents provider.
1885
transport_path = 'quack'
1886
repo, client = self.setup_fake_client_and_repository(transport_path)
1887
graph = repo.get_graph()
1888
self.assertNotEqual(graph._parents_provider, repo)
1891
class TestRepositoryGetParentMap(TestRemoteRepository):
1893
def test_get_parent_map_caching(self):
1894
# get_parent_map returns from cache until unlock()
1895
# setup a reponse with two revisions
1896
r1 = u'\u0e33'.encode('utf8')
1897
r2 = u'\u0dab'.encode('utf8')
1898
lines = [' '.join([r2, r1]), r1]
1899
encoded_body = bz2.compress('\n'.join(lines))
1901
transport_path = 'quack'
1902
repo, client = self.setup_fake_client_and_repository(transport_path)
1903
client.add_success_response_with_body(encoded_body, 'ok')
1904
client.add_success_response_with_body(encoded_body, 'ok')
1906
graph = repo.get_graph()
1907
parents = graph.get_parent_map([r2])
1908
self.assertEqual({r2: (r1,)}, parents)
1909
# locking and unlocking deeper should not reset
1912
parents = graph.get_parent_map([r1])
1913
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1915
[('call_with_body_bytes_expecting_body',
1916
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1920
# now we call again, and it should use the second response.
1922
graph = repo.get_graph()
1923
parents = graph.get_parent_map([r1])
1924
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1926
[('call_with_body_bytes_expecting_body',
1927
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1929
('call_with_body_bytes_expecting_body',
1930
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1936
def test_get_parent_map_reconnects_if_unknown_method(self):
1937
transport_path = 'quack'
1938
rev_id = 'revision-id'
1939
repo, client = self.setup_fake_client_and_repository(transport_path)
1940
client.add_unknown_method_response('Repository.get_parent_map')
1941
client.add_success_response_with_body(rev_id, 'ok')
1942
self.assertFalse(client._medium._is_remote_before((1, 2)))
1943
parents = repo.get_parent_map([rev_id])
1945
[('call_with_body_bytes_expecting_body',
1946
'Repository.get_parent_map', ('quack/', 'include-missing:',
1948
('disconnect medium',),
1949
('call_expecting_body', 'Repository.get_revision_graph',
1952
# The medium is now marked as being connected to an older server
1953
self.assertTrue(client._medium._is_remote_before((1, 2)))
1954
self.assertEqual({rev_id: ('null:',)}, parents)
1956
def test_get_parent_map_fallback_parentless_node(self):
1957
"""get_parent_map falls back to get_revision_graph on old servers. The
1958
results from get_revision_graph are tweaked to match the get_parent_map
1961
Specifically, a {key: ()} result from get_revision_graph means "no
1962
parents" for that key, which in get_parent_map results should be
1963
represented as {key: ('null:',)}.
1965
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1967
rev_id = 'revision-id'
1968
transport_path = 'quack'
1969
repo, client = self.setup_fake_client_and_repository(transport_path)
1970
client.add_success_response_with_body(rev_id, 'ok')
1971
client._medium._remember_remote_is_before((1, 2))
1972
parents = repo.get_parent_map([rev_id])
1974
[('call_expecting_body', 'Repository.get_revision_graph',
1977
self.assertEqual({rev_id: ('null:',)}, parents)
1979
def test_get_parent_map_unexpected_response(self):
1980
repo, client = self.setup_fake_client_and_repository('path')
1981
client.add_success_response('something unexpected!')
1983
errors.UnexpectedSmartServerResponse,
1984
repo.get_parent_map, ['a-revision-id'])
1986
def test_get_parent_map_negative_caches_missing_keys(self):
1987
self.setup_smart_server_with_call_log()
1988
repo = self.make_repository('foo')
1989
self.assertIsInstance(repo, RemoteRepository)
1991
self.addCleanup(repo.unlock)
1992
self.reset_smart_call_log()
1993
graph = repo.get_graph()
1994
self.assertEqual({},
1995
graph.get_parent_map(['some-missing', 'other-missing']))
1996
self.assertLength(1, self.hpss_calls)
1997
# No call if we repeat this
1998
self.reset_smart_call_log()
1999
graph = repo.get_graph()
2000
self.assertEqual({},
2001
graph.get_parent_map(['some-missing', 'other-missing']))
2002
self.assertLength(0, self.hpss_calls)
2003
# Asking for more unknown keys makes a request.
2004
self.reset_smart_call_log()
2005
graph = repo.get_graph()
2006
self.assertEqual({},
2007
graph.get_parent_map(['some-missing', 'other-missing',
2009
self.assertLength(1, self.hpss_calls)
2011
def disableExtraResults(self):
2012
self.overrideAttr(SmartServerRepositoryGetParentMap,
2013
'no_extra_results', True)
2015
def test_null_cached_missing_and_stop_key(self):
2016
self.setup_smart_server_with_call_log()
2017
# Make a branch with a single revision.
2018
builder = self.make_branch_builder('foo')
2019
builder.start_series()
2020
builder.build_snapshot('first', None, [
2021
('add', ('', 'root-id', 'directory', ''))])
2022
builder.finish_series()
2023
branch = builder.get_branch()
2024
repo = branch.repository
2025
self.assertIsInstance(repo, RemoteRepository)
2026
# Stop the server from sending extra results.
2027
self.disableExtraResults()
2029
self.addCleanup(repo.unlock)
2030
self.reset_smart_call_log()
2031
graph = repo.get_graph()
2032
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2033
# 'first' it will be a candidate for the stop_keys of subsequent
2034
# requests, and because 'null:' was queried but not returned it will be
2035
# cached as missing.
2036
self.assertEqual({'first': ('null:',)},
2037
graph.get_parent_map(['first', 'null:']))
2038
# Now query for another key. This request will pass along a recipe of
2039
# start and stop keys describing the already cached results, and this
2040
# recipe's revision count must be correct (or else it will trigger an
2041
# error from the server).
2042
self.assertEqual({}, graph.get_parent_map(['another-key']))
2043
# This assertion guards against disableExtraResults silently failing to
2044
# work, thus invalidating the test.
2045
self.assertLength(2, self.hpss_calls)
2047
def test_get_parent_map_gets_ghosts_from_result(self):
2048
# asking for a revision should negatively cache close ghosts in its
2050
self.setup_smart_server_with_call_log()
2051
tree = self.make_branch_and_memory_tree('foo')
2054
builder = treebuilder.TreeBuilder()
2055
builder.start_tree(tree)
2057
builder.finish_tree()
2058
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2059
rev_id = tree.commit('')
2063
self.addCleanup(tree.unlock)
2064
repo = tree.branch.repository
2065
self.assertIsInstance(repo, RemoteRepository)
2067
repo.get_parent_map([rev_id])
2068
self.reset_smart_call_log()
2069
# Now asking for rev_id's ghost parent should not make calls
2070
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2071
self.assertLength(0, self.hpss_calls)
2074
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2076
def test_allows_new_revisions(self):
2077
"""get_parent_map's results can be updated by commit."""
2078
smart_server = test_server.SmartTCPServer_for_testing()
2079
self.start_server(smart_server)
2080
self.make_branch('branch')
2081
branch = Branch.open(smart_server.get_url() + '/branch')
2082
tree = branch.create_checkout('tree', lightweight=True)
2084
self.addCleanup(tree.unlock)
2085
graph = tree.branch.repository.get_graph()
2086
# This provides an opportunity for the missing rev-id to be cached.
2087
self.assertEqual({}, graph.get_parent_map(['rev1']))
2088
tree.commit('message', rev_id='rev1')
2089
graph = tree.branch.repository.get_graph()
2090
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2093
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2095
def test_null_revision(self):
2096
# a null revision has the predictable result {}, we should have no wire
2097
# traffic when calling it with this argument
2098
transport_path = 'empty'
2099
repo, client = self.setup_fake_client_and_repository(transport_path)
2100
client.add_success_response('notused')
2101
# actual RemoteRepository.get_revision_graph is gone, but there's an
2102
# equivalent private method for testing
2103
result = repo._get_revision_graph(NULL_REVISION)
2104
self.assertEqual([], client._calls)
2105
self.assertEqual({}, result)
2107
def test_none_revision(self):
2108
# with none we want the entire graph
2109
r1 = u'\u0e33'.encode('utf8')
2110
r2 = u'\u0dab'.encode('utf8')
2111
lines = [' '.join([r2, r1]), r1]
2112
encoded_body = '\n'.join(lines)
2114
transport_path = 'sinhala'
2115
repo, client = self.setup_fake_client_and_repository(transport_path)
2116
client.add_success_response_with_body(encoded_body, 'ok')
2117
# actual RemoteRepository.get_revision_graph is gone, but there's an
2118
# equivalent private method for testing
2119
result = repo._get_revision_graph(None)
2121
[('call_expecting_body', 'Repository.get_revision_graph',
2124
self.assertEqual({r1: (), r2: (r1, )}, result)
2126
def test_specific_revision(self):
2127
# with a specific revision we want the graph for that
2128
# with none we want the entire graph
2129
r11 = u'\u0e33'.encode('utf8')
2130
r12 = u'\xc9'.encode('utf8')
2131
r2 = u'\u0dab'.encode('utf8')
2132
lines = [' '.join([r2, r11, r12]), r11, r12]
2133
encoded_body = '\n'.join(lines)
2135
transport_path = 'sinhala'
2136
repo, client = self.setup_fake_client_and_repository(transport_path)
2137
client.add_success_response_with_body(encoded_body, 'ok')
2138
result = repo._get_revision_graph(r2)
2140
[('call_expecting_body', 'Repository.get_revision_graph',
2143
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2145
def test_no_such_revision(self):
2147
transport_path = 'sinhala'
2148
repo, client = self.setup_fake_client_and_repository(transport_path)
2149
client.add_error_response('nosuchrevision', revid)
2150
# also check that the right revision is reported in the error
2151
self.assertRaises(errors.NoSuchRevision,
2152
repo._get_revision_graph, revid)
2154
[('call_expecting_body', 'Repository.get_revision_graph',
2155
('sinhala/', revid))],
2158
def test_unexpected_error(self):
2160
transport_path = 'sinhala'
2161
repo, client = self.setup_fake_client_and_repository(transport_path)
2162
client.add_error_response('AnUnexpectedError')
2163
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2164
repo._get_revision_graph, revid)
2165
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2168
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2171
repo, client = self.setup_fake_client_and_repository('quack')
2172
client.add_expected_call(
2173
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2174
'success', ('ok', 'rev-five'))
2175
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2176
self.assertEqual((True, 'rev-five'), result)
2177
self.assertFinished(client)
2179
def test_history_incomplete(self):
2180
repo, client = self.setup_fake_client_and_repository('quack')
2181
client.add_expected_call(
2182
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2183
'success', ('history-incomplete', 10, 'rev-ten'))
2184
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2185
self.assertEqual((False, (10, 'rev-ten')), result)
2186
self.assertFinished(client)
2188
def test_history_incomplete_with_fallback(self):
2189
"""A 'history-incomplete' response causes the fallback repository to be
2190
queried too, if one is set.
2192
# Make a repo with a fallback repo, both using a FakeClient.
2193
format = remote.response_tuple_to_repo_format(
2194
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2195
repo, client = self.setup_fake_client_and_repository('quack')
2196
repo._format = format
2197
fallback_repo, ignored = self.setup_fake_client_and_repository(
2199
fallback_repo._client = client
2200
fallback_repo._format = format
2201
repo.add_fallback_repository(fallback_repo)
2202
# First the client should ask the primary repo
2203
client.add_expected_call(
2204
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2205
'success', ('history-incomplete', 2, 'rev-two'))
2206
# Then it should ask the fallback, using revno/revid from the
2207
# history-incomplete response as the known revno/revid.
2208
client.add_expected_call(
2209
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2210
'success', ('ok', 'rev-one'))
2211
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2212
self.assertEqual((True, 'rev-one'), result)
2213
self.assertFinished(client)
2215
def test_nosuchrevision(self):
2216
# 'nosuchrevision' is returned when the known-revid is not found in the
2217
# remote repo. The client translates that response to NoSuchRevision.
2218
repo, client = self.setup_fake_client_and_repository('quack')
2219
client.add_expected_call(
2220
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2221
'error', ('nosuchrevision', 'rev-foo'))
2223
errors.NoSuchRevision,
2224
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2225
self.assertFinished(client)
2227
def test_branch_fallback_locking(self):
2228
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2229
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2230
will be invoked, which will fail if the repo is unlocked.
2232
self.setup_smart_server_with_call_log()
2233
tree = self.make_branch_and_memory_tree('.')
2235
rev1 = tree.commit('First')
2236
rev2 = tree.commit('Second')
2238
branch = tree.branch
2239
self.assertFalse(branch.is_locked())
2240
self.reset_smart_call_log()
2241
verb = 'Repository.get_rev_id_for_revno'
2242
self.disable_verb(verb)
2243
self.assertEqual(rev1, branch.get_rev_id(1))
2244
self.assertLength(1, [call for call in self.hpss_calls if
2245
call.call.method == verb])
2248
class TestRepositoryIsShared(TestRemoteRepository):
2250
def test_is_shared(self):
2251
# ('yes', ) for Repository.is_shared -> 'True'.
2252
transport_path = 'quack'
2253
repo, client = self.setup_fake_client_and_repository(transport_path)
2254
client.add_success_response('yes')
2255
result = repo.is_shared()
2257
[('call', 'Repository.is_shared', ('quack/',))],
2259
self.assertEqual(True, result)
2261
def test_is_not_shared(self):
2262
# ('no', ) for Repository.is_shared -> 'False'.
2263
transport_path = 'qwack'
2264
repo, client = self.setup_fake_client_and_repository(transport_path)
2265
client.add_success_response('no')
2266
result = repo.is_shared()
2268
[('call', 'Repository.is_shared', ('qwack/',))],
2270
self.assertEqual(False, result)
2273
class TestRepositoryLockWrite(TestRemoteRepository):
2275
def test_lock_write(self):
2276
transport_path = 'quack'
2277
repo, client = self.setup_fake_client_and_repository(transport_path)
2278
client.add_success_response('ok', 'a token')
2279
result = repo.lock_write()
2281
[('call', 'Repository.lock_write', ('quack/', ''))],
2283
self.assertEqual('a token', result)
2285
def test_lock_write_already_locked(self):
2286
transport_path = 'quack'
2287
repo, client = self.setup_fake_client_and_repository(transport_path)
2288
client.add_error_response('LockContention')
2289
self.assertRaises(errors.LockContention, repo.lock_write)
2291
[('call', 'Repository.lock_write', ('quack/', ''))],
2294
def test_lock_write_unlockable(self):
2295
transport_path = 'quack'
2296
repo, client = self.setup_fake_client_and_repository(transport_path)
2297
client.add_error_response('UnlockableTransport')
2298
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2300
[('call', 'Repository.lock_write', ('quack/', ''))],
2304
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2306
def test_backwards_compat(self):
2307
self.setup_smart_server_with_call_log()
2308
repo = self.make_repository('.')
2309
self.reset_smart_call_log()
2310
verb = 'Repository.set_make_working_trees'
2311
self.disable_verb(verb)
2312
repo.set_make_working_trees(True)
2313
call_count = len([call for call in self.hpss_calls if
2314
call.call.method == verb])
2315
self.assertEqual(1, call_count)
2317
def test_current(self):
2318
transport_path = 'quack'
2319
repo, client = self.setup_fake_client_and_repository(transport_path)
2320
client.add_expected_call(
2321
'Repository.set_make_working_trees', ('quack/', 'True'),
2323
client.add_expected_call(
2324
'Repository.set_make_working_trees', ('quack/', 'False'),
2326
repo.set_make_working_trees(True)
2327
repo.set_make_working_trees(False)
2330
class TestRepositoryUnlock(TestRemoteRepository):
2332
def test_unlock(self):
2333
transport_path = 'quack'
2334
repo, client = self.setup_fake_client_and_repository(transport_path)
2335
client.add_success_response('ok', 'a token')
2336
client.add_success_response('ok')
2340
[('call', 'Repository.lock_write', ('quack/', '')),
2341
('call', 'Repository.unlock', ('quack/', 'a token'))],
2344
def test_unlock_wrong_token(self):
2345
# If somehow the token is wrong, unlock will raise TokenMismatch.
2346
transport_path = 'quack'
2347
repo, client = self.setup_fake_client_and_repository(transport_path)
2348
client.add_success_response('ok', 'a token')
2349
client.add_error_response('TokenMismatch')
2351
self.assertRaises(errors.TokenMismatch, repo.unlock)
2354
class TestRepositoryHasRevision(TestRemoteRepository):
2356
def test_none(self):
2357
# repo.has_revision(None) should not cause any traffic.
2358
transport_path = 'quack'
2359
repo, client = self.setup_fake_client_and_repository(transport_path)
2361
# The null revision is always there, so has_revision(None) == True.
2362
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2364
# The remote repo shouldn't be accessed.
2365
self.assertEqual([], client._calls)
2368
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2369
"""Base class for Repository.insert_stream and .insert_stream_1.19
2373
def checkInsertEmptyStream(self, repo, client):
2374
"""Insert an empty stream, checking the result.
2376
This checks that there are no resume_tokens or missing_keys, and that
2377
the client is finished.
2379
sink = repo._get_sink()
2380
fmt = repository.RepositoryFormat.get_default_format()
2381
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2382
self.assertEqual([], resume_tokens)
2383
self.assertEqual(set(), missing_keys)
2384
self.assertFinished(client)
2387
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2388
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2391
This test case is very similar to TestRepositoryInsertStream_1_19.
2395
TestRemoteRepository.setUp(self)
2396
self.disable_verb('Repository.insert_stream_1.19')
2398
def test_unlocked_repo(self):
2399
transport_path = 'quack'
2400
repo, client = self.setup_fake_client_and_repository(transport_path)
2401
client.add_expected_call(
2402
'Repository.insert_stream_1.19', ('quack/', ''),
2403
'unknown', ('Repository.insert_stream_1.19',))
2404
client.add_expected_call(
2405
'Repository.insert_stream', ('quack/', ''),
2407
client.add_expected_call(
2408
'Repository.insert_stream', ('quack/', ''),
2410
self.checkInsertEmptyStream(repo, client)
2412
def test_locked_repo_with_no_lock_token(self):
2413
transport_path = 'quack'
2414
repo, client = self.setup_fake_client_and_repository(transport_path)
2415
client.add_expected_call(
2416
'Repository.lock_write', ('quack/', ''),
2417
'success', ('ok', ''))
2418
client.add_expected_call(
2419
'Repository.insert_stream_1.19', ('quack/', ''),
2420
'unknown', ('Repository.insert_stream_1.19',))
2421
client.add_expected_call(
2422
'Repository.insert_stream', ('quack/', ''),
2424
client.add_expected_call(
2425
'Repository.insert_stream', ('quack/', ''),
2428
self.checkInsertEmptyStream(repo, client)
2430
def test_locked_repo_with_lock_token(self):
2431
transport_path = 'quack'
2432
repo, client = self.setup_fake_client_and_repository(transport_path)
2433
client.add_expected_call(
2434
'Repository.lock_write', ('quack/', ''),
2435
'success', ('ok', 'a token'))
2436
client.add_expected_call(
2437
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2438
'unknown', ('Repository.insert_stream_1.19',))
2439
client.add_expected_call(
2440
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2442
client.add_expected_call(
2443
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2446
self.checkInsertEmptyStream(repo, client)
2448
def test_stream_with_inventory_deltas(self):
2449
"""'inventory-deltas' substreams cannot be sent to the
2450
Repository.insert_stream verb, because not all servers that implement
2451
that verb will accept them. So when one is encountered the RemoteSink
2452
immediately stops using that verb and falls back to VFS insert_stream.
2454
transport_path = 'quack'
2455
repo, client = self.setup_fake_client_and_repository(transport_path)
2456
client.add_expected_call(
2457
'Repository.insert_stream_1.19', ('quack/', ''),
2458
'unknown', ('Repository.insert_stream_1.19',))
2459
client.add_expected_call(
2460
'Repository.insert_stream', ('quack/', ''),
2462
client.add_expected_call(
2463
'Repository.insert_stream', ('quack/', ''),
2465
# Create a fake real repository for insert_stream to fall back on, so
2466
# that we can directly see the records the RemoteSink passes to the
2471
def insert_stream(self, stream, src_format, resume_tokens):
2472
for substream_kind, substream in stream:
2473
self.records.append(
2474
(substream_kind, [record.key for record in substream]))
2475
return ['fake tokens'], ['fake missing keys']
2476
fake_real_sink = FakeRealSink()
2477
class FakeRealRepository:
2478
def _get_sink(self):
2479
return fake_real_sink
2480
def is_in_write_group(self):
2482
def refresh_data(self):
2484
repo._real_repository = FakeRealRepository()
2485
sink = repo._get_sink()
2486
fmt = repository.RepositoryFormat.get_default_format()
2487
stream = self.make_stream_with_inv_deltas(fmt)
2488
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2489
# Every record from the first inventory delta should have been sent to
2491
expected_records = [
2492
('inventory-deltas', [('rev2',), ('rev3',)]),
2493
('texts', [('some-rev', 'some-file')])]
2494
self.assertEqual(expected_records, fake_real_sink.records)
2495
# The return values from the real sink's insert_stream are propagated
2496
# back to the original caller.
2497
self.assertEqual(['fake tokens'], resume_tokens)
2498
self.assertEqual(['fake missing keys'], missing_keys)
2499
self.assertFinished(client)
2501
def make_stream_with_inv_deltas(self, fmt):
2502
"""Make a simple stream with an inventory delta followed by more
2503
records and more substreams to test that all records and substreams
2504
from that point on are used.
2506
This sends, in order:
2507
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2509
* texts substream: (some-rev, some-file)
2511
# Define a stream using generators so that it isn't rewindable.
2512
inv = inventory.Inventory(revision_id='rev1')
2513
inv.root.revision = 'rev1'
2514
def stream_with_inv_delta():
2515
yield ('inventories', inventories_substream())
2516
yield ('inventory-deltas', inventory_delta_substream())
2518
versionedfile.FulltextContentFactory(
2519
('some-rev', 'some-file'), (), None, 'content')])
2520
def inventories_substream():
2521
# An empty inventory fulltext. This will be streamed normally.
2522
text = fmt._serializer.write_inventory_to_string(inv)
2523
yield versionedfile.FulltextContentFactory(
2524
('rev1',), (), None, text)
2525
def inventory_delta_substream():
2526
# An inventory delta. This can't be streamed via this verb, so it
2527
# will trigger a fallback to VFS insert_stream.
2528
entry = inv.make_entry(
2529
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2530
entry.revision = 'ghost'
2531
delta = [(None, 'newdir', 'newdir-id', entry)]
2532
serializer = inventory_delta.InventoryDeltaSerializer(
2533
versioned_root=True, tree_references=False)
2534
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2535
yield versionedfile.ChunkedContentFactory(
2536
('rev2',), (('rev1',)), None, lines)
2538
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2539
yield versionedfile.ChunkedContentFactory(
2540
('rev3',), (('rev1',)), None, lines)
2541
return stream_with_inv_delta()
2544
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2546
def test_unlocked_repo(self):
2547
transport_path = 'quack'
2548
repo, client = self.setup_fake_client_and_repository(transport_path)
2549
client.add_expected_call(
2550
'Repository.insert_stream_1.19', ('quack/', ''),
2552
client.add_expected_call(
2553
'Repository.insert_stream_1.19', ('quack/', ''),
2555
self.checkInsertEmptyStream(repo, client)
2557
def test_locked_repo_with_no_lock_token(self):
2558
transport_path = 'quack'
2559
repo, client = self.setup_fake_client_and_repository(transport_path)
2560
client.add_expected_call(
2561
'Repository.lock_write', ('quack/', ''),
2562
'success', ('ok', ''))
2563
client.add_expected_call(
2564
'Repository.insert_stream_1.19', ('quack/', ''),
2566
client.add_expected_call(
2567
'Repository.insert_stream_1.19', ('quack/', ''),
2570
self.checkInsertEmptyStream(repo, client)
2572
def test_locked_repo_with_lock_token(self):
2573
transport_path = 'quack'
2574
repo, client = self.setup_fake_client_and_repository(transport_path)
2575
client.add_expected_call(
2576
'Repository.lock_write', ('quack/', ''),
2577
'success', ('ok', 'a token'))
2578
client.add_expected_call(
2579
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2581
client.add_expected_call(
2582
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2585
self.checkInsertEmptyStream(repo, client)
2588
class TestRepositoryTarball(TestRemoteRepository):
2590
# This is a canned tarball reponse we can validate against
2592
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2593
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2594
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2595
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2596
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2597
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2598
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2599
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2600
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2601
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2602
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2603
'nWQ7QH/F3JFOFCQ0aSPfA='
2606
def test_repository_tarball(self):
2607
# Test that Repository.tarball generates the right operations
2608
transport_path = 'repo'
2609
expected_calls = [('call_expecting_body', 'Repository.tarball',
2610
('repo/', 'bz2',),),
2612
repo, client = self.setup_fake_client_and_repository(transport_path)
2613
client.add_success_response_with_body(self.tarball_content, 'ok')
2614
# Now actually ask for the tarball
2615
tarball_file = repo._get_tarball('bz2')
2617
self.assertEqual(expected_calls, client._calls)
2618
self.assertEqual(self.tarball_content, tarball_file.read())
2620
tarball_file.close()
2623
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2624
"""RemoteRepository.copy_content_into optimizations"""
2626
def test_copy_content_remote_to_local(self):
2627
self.transport_server = test_server.SmartTCPServer_for_testing
2628
src_repo = self.make_repository('repo1')
2629
src_repo = repository.Repository.open(self.get_url('repo1'))
2630
# At the moment the tarball-based copy_content_into can't write back
2631
# into a smart server. It would be good if it could upload the
2632
# tarball; once that works we'd have to create repositories of
2633
# different formats. -- mbp 20070410
2634
dest_url = self.get_vfs_only_url('repo2')
2635
dest_bzrdir = BzrDir.create(dest_url)
2636
dest_repo = dest_bzrdir.create_repository()
2637
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2638
self.assertTrue(isinstance(src_repo, RemoteRepository))
2639
src_repo.copy_content_into(dest_repo)
2642
class _StubRealPackRepository(object):
2644
def __init__(self, calls):
2646
self._pack_collection = _StubPackCollection(calls)
2648
def is_in_write_group(self):
2651
def refresh_data(self):
2652
self.calls.append(('pack collection reload_pack_names',))
2655
class _StubPackCollection(object):
2657
def __init__(self, calls):
2661
self.calls.append(('pack collection autopack',))
2664
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2665
"""Tests for RemoteRepository.autopack implementation."""
2668
"""When the server returns 'ok' and there's no _real_repository, then
2669
nothing else happens: the autopack method is done.
2671
transport_path = 'quack'
2672
repo, client = self.setup_fake_client_and_repository(transport_path)
2673
client.add_expected_call(
2674
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2676
self.assertFinished(client)
2678
def test_ok_with_real_repo(self):
2679
"""When the server returns 'ok' and there is a _real_repository, then
2680
the _real_repository's reload_pack_name's method will be called.
2682
transport_path = 'quack'
2683
repo, client = self.setup_fake_client_and_repository(transport_path)
2684
client.add_expected_call(
2685
'PackRepository.autopack', ('quack/',),
2687
repo._real_repository = _StubRealPackRepository(client._calls)
2690
[('call', 'PackRepository.autopack', ('quack/',)),
2691
('pack collection reload_pack_names',)],
2694
def test_backwards_compatibility(self):
2695
"""If the server does not recognise the PackRepository.autopack verb,
2696
fallback to the real_repository's implementation.
2698
transport_path = 'quack'
2699
repo, client = self.setup_fake_client_and_repository(transport_path)
2700
client.add_unknown_method_response('PackRepository.autopack')
2701
def stub_ensure_real():
2702
client._calls.append(('_ensure_real',))
2703
repo._real_repository = _StubRealPackRepository(client._calls)
2704
repo._ensure_real = stub_ensure_real
2707
[('call', 'PackRepository.autopack', ('quack/',)),
2709
('pack collection autopack',)],
2713
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2714
"""Base class for unit tests for bzrlib.remote._translate_error."""
2716
def translateTuple(self, error_tuple, **context):
2717
"""Call _translate_error with an ErrorFromSmartServer built from the
2720
:param error_tuple: A tuple of a smart server response, as would be
2721
passed to an ErrorFromSmartServer.
2722
:kwargs context: context items to call _translate_error with.
2724
:returns: The error raised by _translate_error.
2726
# Raise the ErrorFromSmartServer before passing it as an argument,
2727
# because _translate_error may need to re-raise it with a bare 'raise'
2729
server_error = errors.ErrorFromSmartServer(error_tuple)
2730
translated_error = self.translateErrorFromSmartServer(
2731
server_error, **context)
2732
return translated_error
2734
def translateErrorFromSmartServer(self, error_object, **context):
2735
"""Like translateTuple, but takes an already constructed
2736
ErrorFromSmartServer rather than a tuple.
2740
except errors.ErrorFromSmartServer, server_error:
2741
translated_error = self.assertRaises(
2742
errors.BzrError, remote._translate_error, server_error,
2744
return translated_error
2747
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2748
"""Unit tests for bzrlib.remote._translate_error.
2750
Given an ErrorFromSmartServer (which has an error tuple from a smart
2751
server) and some context, _translate_error raises more specific errors from
2754
This test case covers the cases where _translate_error succeeds in
2755
translating an ErrorFromSmartServer to something better. See
2756
TestErrorTranslationRobustness for other cases.
2759
def test_NoSuchRevision(self):
2760
branch = self.make_branch('')
2762
translated_error = self.translateTuple(
2763
('NoSuchRevision', revid), branch=branch)
2764
expected_error = errors.NoSuchRevision(branch, revid)
2765
self.assertEqual(expected_error, translated_error)
2767
def test_nosuchrevision(self):
2768
repository = self.make_repository('')
2770
translated_error = self.translateTuple(
2771
('nosuchrevision', revid), repository=repository)
2772
expected_error = errors.NoSuchRevision(repository, revid)
2773
self.assertEqual(expected_error, translated_error)
2775
def test_nobranch(self):
2776
bzrdir = self.make_bzrdir('')
2777
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2778
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2779
self.assertEqual(expected_error, translated_error)
2781
def test_nobranch_one_arg(self):
2782
bzrdir = self.make_bzrdir('')
2783
translated_error = self.translateTuple(
2784
('nobranch', 'extra detail'), bzrdir=bzrdir)
2785
expected_error = errors.NotBranchError(
2786
path=bzrdir.root_transport.base,
2787
detail='extra detail')
2788
self.assertEqual(expected_error, translated_error)
2790
def test_LockContention(self):
2791
translated_error = self.translateTuple(('LockContention',))
2792
expected_error = errors.LockContention('(remote lock)')
2793
self.assertEqual(expected_error, translated_error)
2795
def test_UnlockableTransport(self):
2796
bzrdir = self.make_bzrdir('')
2797
translated_error = self.translateTuple(
2798
('UnlockableTransport',), bzrdir=bzrdir)
2799
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2800
self.assertEqual(expected_error, translated_error)
2802
def test_LockFailed(self):
2803
lock = 'str() of a server lock'
2804
why = 'str() of why'
2805
translated_error = self.translateTuple(('LockFailed', lock, why))
2806
expected_error = errors.LockFailed(lock, why)
2807
self.assertEqual(expected_error, translated_error)
2809
def test_TokenMismatch(self):
2810
token = 'a lock token'
2811
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2812
expected_error = errors.TokenMismatch(token, '(remote token)')
2813
self.assertEqual(expected_error, translated_error)
2815
def test_Diverged(self):
2816
branch = self.make_branch('a')
2817
other_branch = self.make_branch('b')
2818
translated_error = self.translateTuple(
2819
('Diverged',), branch=branch, other_branch=other_branch)
2820
expected_error = errors.DivergedBranches(branch, other_branch)
2821
self.assertEqual(expected_error, translated_error)
2823
def test_ReadError_no_args(self):
2825
translated_error = self.translateTuple(('ReadError',), path=path)
2826
expected_error = errors.ReadError(path)
2827
self.assertEqual(expected_error, translated_error)
2829
def test_ReadError(self):
2831
translated_error = self.translateTuple(('ReadError', path))
2832
expected_error = errors.ReadError(path)
2833
self.assertEqual(expected_error, translated_error)
2835
def test_IncompatibleRepositories(self):
2836
translated_error = self.translateTuple(('IncompatibleRepositories',
2837
"repo1", "repo2", "details here"))
2838
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
2840
self.assertEqual(expected_error, translated_error)
2842
def test_PermissionDenied_no_args(self):
2844
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2845
expected_error = errors.PermissionDenied(path)
2846
self.assertEqual(expected_error, translated_error)
2848
def test_PermissionDenied_one_arg(self):
2850
translated_error = self.translateTuple(('PermissionDenied', path))
2851
expected_error = errors.PermissionDenied(path)
2852
self.assertEqual(expected_error, translated_error)
2854
def test_PermissionDenied_one_arg_and_context(self):
2855
"""Given a choice between a path from the local context and a path on
2856
the wire, _translate_error prefers the path from the local context.
2858
local_path = 'local path'
2859
remote_path = 'remote path'
2860
translated_error = self.translateTuple(
2861
('PermissionDenied', remote_path), path=local_path)
2862
expected_error = errors.PermissionDenied(local_path)
2863
self.assertEqual(expected_error, translated_error)
2865
def test_PermissionDenied_two_args(self):
2867
extra = 'a string with extra info'
2868
translated_error = self.translateTuple(
2869
('PermissionDenied', path, extra))
2870
expected_error = errors.PermissionDenied(path, extra)
2871
self.assertEqual(expected_error, translated_error)
2874
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2875
"""Unit tests for bzrlib.remote._translate_error's robustness.
2877
TestErrorTranslationSuccess is for cases where _translate_error can
2878
translate successfully. This class about how _translate_err behaves when
2879
it fails to translate: it re-raises the original error.
2882
def test_unrecognised_server_error(self):
2883
"""If the error code from the server is not recognised, the original
2884
ErrorFromSmartServer is propagated unmodified.
2886
error_tuple = ('An unknown error tuple',)
2887
server_error = errors.ErrorFromSmartServer(error_tuple)
2888
translated_error = self.translateErrorFromSmartServer(server_error)
2889
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2890
self.assertEqual(expected_error, translated_error)
2892
def test_context_missing_a_key(self):
2893
"""In case of a bug in the client, or perhaps an unexpected response
2894
from a server, _translate_error returns the original error tuple from
2895
the server and mutters a warning.
2897
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2898
# in the context dict. So let's give it an empty context dict instead
2899
# to exercise its error recovery.
2901
error_tuple = ('NoSuchRevision', 'revid')
2902
server_error = errors.ErrorFromSmartServer(error_tuple)
2903
translated_error = self.translateErrorFromSmartServer(server_error)
2904
self.assertEqual(server_error, translated_error)
2905
# In addition to re-raising ErrorFromSmartServer, some debug info has
2906
# been muttered to the log file for developer to look at.
2907
self.assertContainsRe(
2909
"Missing key 'branch' in context")
2911
def test_path_missing(self):
2912
"""Some translations (PermissionDenied, ReadError) can determine the
2913
'path' variable from either the wire or the local context. If neither
2914
has it, then an error is raised.
2916
error_tuple = ('ReadError',)
2917
server_error = errors.ErrorFromSmartServer(error_tuple)
2918
translated_error = self.translateErrorFromSmartServer(server_error)
2919
self.assertEqual(server_error, translated_error)
2920
# In addition to re-raising ErrorFromSmartServer, some debug info has
2921
# been muttered to the log file for developer to look at.
2922
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
2925
class TestStacking(tests.TestCaseWithTransport):
2926
"""Tests for operations on stacked remote repositories.
2928
The underlying format type must support stacking.
2931
def test_access_stacked_remote(self):
2932
# based on <http://launchpad.net/bugs/261315>
2933
# make a branch stacked on another repository containing an empty
2934
# revision, then open it over hpss - we should be able to see that
2936
base_transport = self.get_transport()
2937
base_builder = self.make_branch_builder('base', format='1.9')
2938
base_builder.start_series()
2939
base_revid = base_builder.build_snapshot('rev-id', None,
2940
[('add', ('', None, 'directory', None))],
2942
base_builder.finish_series()
2943
stacked_branch = self.make_branch('stacked', format='1.9')
2944
stacked_branch.set_stacked_on_url('../base')
2945
# start a server looking at this
2946
smart_server = test_server.SmartTCPServer_for_testing()
2947
self.start_server(smart_server)
2948
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2949
# can get its branch and repository
2950
remote_branch = remote_bzrdir.open_branch()
2951
remote_repo = remote_branch.repository
2952
remote_repo.lock_read()
2954
# it should have an appropriate fallback repository, which should also
2955
# be a RemoteRepository
2956
self.assertLength(1, remote_repo._fallback_repositories)
2957
self.assertIsInstance(remote_repo._fallback_repositories[0],
2959
# and it has the revision committed to the underlying repository;
2960
# these have varying implementations so we try several of them
2961
self.assertTrue(remote_repo.has_revisions([base_revid]))
2962
self.assertTrue(remote_repo.has_revision(base_revid))
2963
self.assertEqual(remote_repo.get_revision(base_revid).message,
2966
remote_repo.unlock()
2968
def prepare_stacked_remote_branch(self):
2969
"""Get stacked_upon and stacked branches with content in each."""
2970
self.setup_smart_server_with_call_log()
2971
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2972
tree1.commit('rev1', rev_id='rev1')
2973
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2974
).open_workingtree()
2975
local_tree = tree2.branch.create_checkout('local')
2976
local_tree.commit('local changes make me feel good.')
2977
branch2 = Branch.open(self.get_url('tree2'))
2979
self.addCleanup(branch2.unlock)
2980
return tree1.branch, branch2
2982
def test_stacked_get_parent_map(self):
2983
# the public implementation of get_parent_map obeys stacking
2984
_, branch = self.prepare_stacked_remote_branch()
2985
repo = branch.repository
2986
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2988
def test_unstacked_get_parent_map(self):
2989
# _unstacked_provider.get_parent_map ignores stacking
2990
_, branch = self.prepare_stacked_remote_branch()
2991
provider = branch.repository._unstacked_provider
2992
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2994
def fetch_stream_to_rev_order(self, stream):
2996
for kind, substream in stream:
2997
if not kind == 'revisions':
3000
for content in substream:
3001
result.append(content.key[-1])
3004
def get_ordered_revs(self, format, order, branch_factory=None):
3005
"""Get a list of the revisions in a stream to format format.
3007
:param format: The format of the target.
3008
:param order: the order that target should have requested.
3009
:param branch_factory: A callable to create a trunk and stacked branch
3010
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3011
:result: The revision ids in the stream, in the order seen,
3012
the topological order of revisions in the source.
3014
unordered_format = bzrdir.format_registry.get(format)()
3015
target_repository_format = unordered_format.repository_format
3017
self.assertEqual(order, target_repository_format._fetch_order)
3018
if branch_factory is None:
3019
branch_factory = self.prepare_stacked_remote_branch
3020
_, stacked = branch_factory()
3021
source = stacked.repository._get_source(target_repository_format)
3022
tip = stacked.last_revision()
3023
revs = stacked.repository.get_ancestry(tip)
3024
search = graph.PendingAncestryResult([tip], stacked.repository)
3025
self.reset_smart_call_log()
3026
stream = source.get_stream(search)
3029
# We trust that if a revision is in the stream the rest of the new
3030
# content for it is too, as per our main fetch tests; here we are
3031
# checking that the revisions are actually included at all, and their
3033
return self.fetch_stream_to_rev_order(stream), revs
3035
def test_stacked_get_stream_unordered(self):
3036
# Repository._get_source.get_stream() from a stacked repository with
3037
# unordered yields the full data from both stacked and stacked upon
3039
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3040
self.assertEqual(set(expected_revs), set(rev_ord))
3041
# Getting unordered results should have made a streaming data request
3042
# from the server, then one from the backing branch.
3043
self.assertLength(2, self.hpss_calls)
3045
def test_stacked_on_stacked_get_stream_unordered(self):
3046
# Repository._get_source.get_stream() from a stacked repository which
3047
# is itself stacked yields the full data from all three sources.
3048
def make_stacked_stacked():
3049
_, stacked = self.prepare_stacked_remote_branch()
3050
tree = stacked.bzrdir.sprout('tree3', stacked=True
3051
).open_workingtree()
3052
local_tree = tree.branch.create_checkout('local-tree3')
3053
local_tree.commit('more local changes are better')
3054
branch = Branch.open(self.get_url('tree3'))
3056
self.addCleanup(branch.unlock)
3058
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3059
branch_factory=make_stacked_stacked)
3060
self.assertEqual(set(expected_revs), set(rev_ord))
3061
# Getting unordered results should have made a streaming data request
3062
# from the server, and one from each backing repo
3063
self.assertLength(3, self.hpss_calls)
3065
def test_stacked_get_stream_topological(self):
3066
# Repository._get_source.get_stream() from a stacked repository with
3067
# topological sorting yields the full data from both stacked and
3068
# stacked upon sources in topological order.
3069
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3070
self.assertEqual(expected_revs, rev_ord)
3071
# Getting topological sort requires VFS calls still - one of which is
3072
# pushing up from the bound branch.
3073
self.assertLength(13, self.hpss_calls)
3075
def test_stacked_get_stream_groupcompress(self):
3076
# Repository._get_source.get_stream() from a stacked repository with
3077
# groupcompress sorting yields the full data from both stacked and
3078
# stacked upon sources in groupcompress order.
3079
raise tests.TestSkipped('No groupcompress ordered format available')
3080
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3081
self.assertEqual(expected_revs, reversed(rev_ord))
3082
# Getting unordered results should have made a streaming data request
3083
# from the backing branch, and one from the stacked on branch.
3084
self.assertLength(2, self.hpss_calls)
3086
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3087
# When pulling some fixed amount of content that is more than the
3088
# source has (because some is coming from a fallback branch, no error
3089
# should be received. This was reported as bug 360791.
3090
# Need three branches: a trunk, a stacked branch, and a preexisting
3091
# branch pulling content from stacked and trunk.
3092
self.setup_smart_server_with_call_log()
3093
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3094
r1 = trunk.commit('start')
3095
stacked_branch = trunk.branch.create_clone_on_transport(
3096
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3097
local = self.make_branch('local', format='1.9-rich-root')
3098
local.repository.fetch(stacked_branch.repository,
3099
stacked_branch.last_revision())
3102
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3105
super(TestRemoteBranchEffort, self).setUp()
3106
# Create a smart server that publishes whatever the backing VFS server
3108
self.smart_server = test_server.SmartTCPServer_for_testing()
3109
self.start_server(self.smart_server, self.get_server())
3110
# Log all HPSS calls into self.hpss_calls.
3111
_SmartClient.hooks.install_named_hook(
3112
'call', self.capture_hpss_call, None)
3113
self.hpss_calls = []
3115
def capture_hpss_call(self, params):
3116
self.hpss_calls.append(params.method)
3118
def test_copy_content_into_avoids_revision_history(self):
3119
local = self.make_branch('local')
3120
remote_backing_tree = self.make_branch_and_tree('remote')
3121
remote_backing_tree.commit("Commit.")
3122
remote_branch_url = self.smart_server.get_url() + 'remote'
3123
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3124
local.repository.fetch(remote_branch.repository)
3125
self.hpss_calls = []
3126
remote_branch.copy_content_into(local)
3127
self.assertFalse('Branch.revision_history' in self.hpss_calls)